1import assert from 'assert';
2import fs from 'fs';
3import { sync as globSync } from 'glob';
4import * as path from 'path';
5
6import { ResourceKind } from './Resources';
7import { UnexpectedError } from '../utils/errors';
8import { directoryExistsAsync } from '../utils/modules';
9
10export interface ProjectFile<L extends string = string> {
11  path: string;
12  language: L;
13  contents: string;
14}
15
16export type ApplicationProjectFile = ProjectFile<'java' | 'kt'>;
17export type GradleProjectFile = ProjectFile<'groovy' | 'kt'>;
18
19export function getProjectFilePath(projectRoot: string, name: string): string {
20  const filePath = globSync(
21    path.join(projectRoot, `android/app/src/main/java/**/${name}.@(java|kt)`)
22  )[0];
23  assert(
24    filePath,
25    `Project file "${name}" does not exist in android project for root "${projectRoot}"`
26  );
27
28  return filePath;
29}
30
31function getLanguage(filePath: string): 'java' | 'groovy' | 'kt' {
32  const extension = path.extname(filePath);
33  switch (extension) {
34    case '.java':
35      return 'java';
36    case '.kts':
37    case '.kt':
38      return 'kt';
39    case '.groovy':
40    case '.gradle':
41      return 'groovy';
42    default:
43      throw new UnexpectedError(`Unexpected Android file extension: ${extension}`);
44  }
45}
46
47export function getFileInfo(filePath: string) {
48  return {
49    path: path.normalize(filePath),
50    contents: fs.readFileSync(filePath, 'utf8'),
51    language: getLanguage(filePath) as any,
52  };
53}
54
55export async function getMainApplicationAsync(
56  projectRoot: string
57): Promise<ApplicationProjectFile> {
58  const filePath = getProjectFilePath(projectRoot, 'MainApplication');
59  return getFileInfo(filePath);
60}
61
62export async function getMainActivityAsync(projectRoot: string): Promise<ApplicationProjectFile> {
63  const filePath = getProjectFilePath(projectRoot, 'MainActivity');
64  return getFileInfo(filePath);
65}
66
67export function getGradleFilePath(projectRoot: string, gradleName: string): string {
68  const groovyPath = path.resolve(projectRoot, `${gradleName}.gradle`);
69  const ktPath = path.resolve(projectRoot, `${gradleName}.gradle.kts`);
70
71  const isGroovy = fs.existsSync(groovyPath);
72  const isKotlin = !isGroovy && fs.existsSync(ktPath);
73
74  if (!isGroovy && !isKotlin) {
75    throw new Error(`Failed to find '${gradleName}.gradle' file for project: ${projectRoot}.`);
76  }
77  const filePath = isGroovy ? groovyPath : ktPath;
78  return filePath;
79}
80
81export function getProjectBuildGradleFilePath(projectRoot: string): string {
82  return getGradleFilePath(path.join(projectRoot, 'android'), 'build');
83}
84
85export async function getProjectBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
86  return getFileInfo(getProjectBuildGradleFilePath(projectRoot));
87}
88
89export function getSettingsGradleFilePath(projectRoot: string): string {
90  return getGradleFilePath(path.join(projectRoot, 'android'), 'settings');
91}
92
93export async function getSettingsGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
94  return getFileInfo(getSettingsGradleFilePath(projectRoot));
95}
96
97export function getAppBuildGradleFilePath(projectRoot: string): string {
98  return getGradleFilePath(path.join(projectRoot, 'android', 'app'), 'build');
99}
100
101export async function getAppBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> {
102  return getFileInfo(getAppBuildGradleFilePath(projectRoot));
103}
104
105export async function getProjectPathOrThrowAsync(projectRoot: string): Promise<string> {
106  const projectPath = path.join(projectRoot, 'android');
107  if (await directoryExistsAsync(projectPath)) {
108    return projectPath;
109  }
110  throw new Error(`Android project folder is missing in project: ${projectRoot}`);
111}
112
113export async function getAndroidManifestAsync(projectRoot: string): Promise<string> {
114  const projectPath = await getProjectPathOrThrowAsync(projectRoot);
115  const filePath = path.join(projectPath, 'app/src/main/AndroidManifest.xml');
116  return filePath;
117}
118
119export async function getResourceFolderAsync(projectRoot: string): Promise<string> {
120  const projectPath = await getProjectPathOrThrowAsync(projectRoot);
121  return path.join(projectPath, `app/src/main/res`);
122}
123
124export async function getResourceXMLPathAsync(
125  projectRoot: string,
126  { kind = 'values', name }: { kind?: ResourceKind; name: 'colors' | 'strings' | 'styles' | string }
127): Promise<string> {
128  const resourcePath = await getResourceFolderAsync(projectRoot);
129
130  const filePath = path.join(resourcePath, `${kind}/${name}.xml`);
131  return filePath;
132}
133