1082815dcSEvan Baconimport assert from 'assert';
2082815dcSEvan Baconimport fs from 'fs';
3082815dcSEvan Baconimport { sync as globSync } from 'glob';
4082815dcSEvan Baconimport * as path from 'path';
5082815dcSEvan Bacon
6*8a424bebSJames Ideimport { ResourceKind } from './Resources';
7082815dcSEvan Baconimport { UnexpectedError } from '../utils/errors';
8082815dcSEvan Baconimport { directoryExistsAsync } from '../utils/modules';
9082815dcSEvan Bacon
10082815dcSEvan Baconexport interface ProjectFile<L extends string = string> {
11082815dcSEvan Bacon  path: string;
12082815dcSEvan Bacon  language: L;
13082815dcSEvan Bacon  contents: string;
14082815dcSEvan Bacon}
15082815dcSEvan Bacon
16082815dcSEvan Baconexport type ApplicationProjectFile = ProjectFile<'java' | 'kt'>;
17082815dcSEvan Baconexport type GradleProjectFile = ProjectFile<'groovy' | 'kt'>;
18082815dcSEvan Bacon
19082815dcSEvan Baconexport function getProjectFilePath(projectRoot: string, name: string): string {
20082815dcSEvan Bacon  const filePath = globSync(
21082815dcSEvan Bacon    path.join(projectRoot, `android/app/src/main/java/**/${name}.@(java|kt)`)
22082815dcSEvan Bacon  )[0];
23082815dcSEvan Bacon  assert(
24082815dcSEvan Bacon    filePath,
25082815dcSEvan Bacon    `Project file "${name}" does not exist in android project for root "${projectRoot}"`
26082815dcSEvan Bacon  );
27082815dcSEvan Bacon
28082815dcSEvan Bacon  return filePath;
29082815dcSEvan Bacon}
30082815dcSEvan Bacon
31082815dcSEvan Baconfunction getLanguage(filePath: string): 'java' | 'groovy' | 'kt' {
32082815dcSEvan Bacon  const extension = path.extname(filePath);
33082815dcSEvan Bacon  switch (extension) {
34082815dcSEvan Bacon    case '.java':
35082815dcSEvan Bacon      return 'java';
36082815dcSEvan Bacon    case '.kts':
37082815dcSEvan Bacon    case '.kt':
38082815dcSEvan Bacon      return 'kt';
39082815dcSEvan Bacon    case '.groovy':
40082815dcSEvan Bacon    case '.gradle':
41082815dcSEvan Bacon      return 'groovy';
42082815dcSEvan Bacon    default:
43082815dcSEvan Bacon      throw new UnexpectedError(`Unexpected Android file extension: ${extension}`);
44082815dcSEvan Bacon  }
45082815dcSEvan Bacon}
46082815dcSEvan Bacon
47082815dcSEvan Baconexport function getFileInfo(filePath: string) {
48082815dcSEvan Bacon  return {
49082815dcSEvan Bacon    path: path.normalize(filePath),
50082815dcSEvan Bacon    contents: fs.readFileSync(filePath, 'utf8'),
51082815dcSEvan Bacon    language: getLanguage(filePath) as any,
52082815dcSEvan Bacon  };
53082815dcSEvan Bacon}
54082815dcSEvan Bacon
55082815dcSEvan Baconexport async function getMainApplicationAsync(
56082815dcSEvan Bacon  projectRoot: string
57082815dcSEvan Bacon): Promise<ApplicationProjectFile> {
58082815dcSEvan Bacon  const filePath = getProjectFilePath(projectRoot, 'MainApplication');
59082815dcSEvan Bacon  return getFileInfo(filePath);
60082815dcSEvan Bacon}
61082815dcSEvan Bacon
62082815dcSEvan Baconexport async function getMainActivityAsync(projectRoot: string): Promise<ApplicationProjectFile> {
63082815dcSEvan Bacon  const filePath = getProjectFilePath(projectRoot, 'MainActivity');
64082815dcSEvan Bacon  return getFileInfo(filePath);
65082815dcSEvan Bacon}
66082815dcSEvan Bacon
67082815dcSEvan Baconexport function getGradleFilePath(projectRoot: string, gradleName: string): string {
68082815dcSEvan Bacon  const groovyPath = path.resolve(projectRoot, `${gradleName}.gradle`);
69082815dcSEvan Bacon  const ktPath = path.resolve(projectRoot, `${gradleName}.gradle.kts`);
70082815dcSEvan Bacon
71082815dcSEvan Bacon  const isGroovy = fs.existsSync(groovyPath);
72082815dcSEvan Bacon  const isKotlin = !isGroovy && fs.existsSync(ktPath);
73082815dcSEvan Bacon
74082815dcSEvan Bacon  if (!isGroovy && !isKotlin) {
75082815dcSEvan Bacon    throw new Error(`Failed to find '${gradleName}.gradle' file for project: ${projectRoot}.`);
76082815dcSEvan Bacon  }
77082815dcSEvan Bacon  const filePath = isGroovy ? groovyPath : ktPath;
78082815dcSEvan Bacon  return filePath;
79082815dcSEvan Bacon}
80082815dcSEvan Bacon
81082815dcSEvan Baconexport function getProjectBuildGradleFilePath(projectRoot: string): string {
82082815dcSEvan Bacon  return getGradleFilePath(path.join(projectRoot, 'android'), 'build');
83082815dcSEvan Bacon}
84082815dcSEvan Bacon
85082815dcSEvan Baconexport async function getProjectBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
86082815dcSEvan Bacon  return getFileInfo(getProjectBuildGradleFilePath(projectRoot));
87082815dcSEvan Bacon}
88082815dcSEvan Bacon
89082815dcSEvan Baconexport function getSettingsGradleFilePath(projectRoot: string): string {
90082815dcSEvan Bacon  return getGradleFilePath(path.join(projectRoot, 'android'), 'settings');
91082815dcSEvan Bacon}
92082815dcSEvan Bacon
93082815dcSEvan Baconexport async function getSettingsGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
94082815dcSEvan Bacon  return getFileInfo(getSettingsGradleFilePath(projectRoot));
95082815dcSEvan Bacon}
96082815dcSEvan Bacon
97082815dcSEvan Baconexport function getAppBuildGradleFilePath(projectRoot: string): string {
98082815dcSEvan Bacon  return getGradleFilePath(path.join(projectRoot, 'android', 'app'), 'build');
99082815dcSEvan Bacon}
100082815dcSEvan Bacon
101082815dcSEvan Baconexport async function getAppBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
102082815dcSEvan Bacon  return getFileInfo(getAppBuildGradleFilePath(projectRoot));
103082815dcSEvan Bacon}
104082815dcSEvan Bacon
105082815dcSEvan Baconexport async function getProjectPathOrThrowAsync(projectRoot: string): Promise<string> {
106082815dcSEvan Bacon  const projectPath = path.join(projectRoot, 'android');
107082815dcSEvan Bacon  if (await directoryExistsAsync(projectPath)) {
108082815dcSEvan Bacon    return projectPath;
109082815dcSEvan Bacon  }
110082815dcSEvan Bacon  throw new Error(`Android project folder is missing in project: ${projectRoot}`);
111082815dcSEvan Bacon}
112082815dcSEvan Bacon
113082815dcSEvan Baconexport async function getAndroidManifestAsync(projectRoot: string): Promise<string> {
114082815dcSEvan Bacon  const projectPath = await getProjectPathOrThrowAsync(projectRoot);
115082815dcSEvan Bacon  const filePath = path.join(projectPath, 'app/src/main/AndroidManifest.xml');
116082815dcSEvan Bacon  return filePath;
117082815dcSEvan Bacon}
118082815dcSEvan Bacon
119082815dcSEvan Baconexport async function getResourceFolderAsync(projectRoot: string): Promise<string> {
120082815dcSEvan Bacon  const projectPath = await getProjectPathOrThrowAsync(projectRoot);
121082815dcSEvan Bacon  return path.join(projectPath, `app/src/main/res`);
122082815dcSEvan Bacon}
123082815dcSEvan Bacon
124082815dcSEvan Baconexport async function getResourceXMLPathAsync(
125082815dcSEvan Bacon  projectRoot: string,
126082815dcSEvan Bacon  { kind = 'values', name }: { kind?: ResourceKind; name: 'colors' | 'strings' | 'styles' | string }
127082815dcSEvan Bacon): Promise<string> {
128082815dcSEvan Bacon  const resourcePath = await getResourceFolderAsync(projectRoot);
129082815dcSEvan Bacon
130082815dcSEvan Bacon  const filePath = path.join(resourcePath, `${kind}/${name}.xml`);
131082815dcSEvan Bacon  return filePath;
132082815dcSEvan Bacon}
133