@expo/repack-app 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ export interface AndroidTools {
8
8
  /**
9
9
  * Get the paths to the Android build-tools.
10
10
  */
11
- export declare function getAndroidBuildToolsAsync(options?: NormalizedOptions): Promise<AndroidTools>;
11
+ export declare function getAndroidBuildToolsAsync(options: NormalizedOptions): Promise<AndroidTools>;
12
12
  /**
13
13
  * Create a resigned & updated APK.
14
14
  * @param unzippedApkRoot The root directory of the unzipped APK working directory.
@@ -18,8 +18,8 @@ export declare function createResignedApkAsync(unzippedApkRoot: string, options:
18
18
  /**
19
19
  * Find the latest build-tools directory in the `ANDROID_SDK_ROOT` directory.
20
20
  */
21
- export declare function findLatestBuildToolsDirAsync(): Promise<string | null>;
21
+ export declare function findLatestBuildToolsDirAsync(options: NormalizedOptions): Promise<string | null>;
22
22
  /**
23
23
  * Search for classes in the APK with the given app ID pattern passing to grep.
24
24
  */
25
- export declare function searchDexClassesAsync(unzipApkRoot: string, grepAppIdPattern: string, options: NormalizedOptions): Promise<Set<string>>;
25
+ export declare function searchDexClassesAsync(unzipApkRoot: string, grepAppIdPattern: string, options: NormalizedOptions): Promise<Set<string> | null>;
@@ -16,7 +16,7 @@ let cachedAndroidTools = null;
16
16
  */
17
17
  async function getAndroidBuildToolsAsync(options) {
18
18
  if (cachedAndroidTools == null) {
19
- const androidBuildToolsDir = options?.androidBuildToolsDir ?? (await findLatestBuildToolsDirAsync());
19
+ const androidBuildToolsDir = options?.androidBuildToolsDir ?? (await findLatestBuildToolsDirAsync(options));
20
20
  (0, node_assert_1.default)(androidBuildToolsDir != null, 'Unable to find the Android build-tools directory.');
21
21
  cachedAndroidTools = {
22
22
  aapt2Path: node_path_1.default.join(androidBuildToolsDir, 'aapt2'),
@@ -62,7 +62,8 @@ async function createResignedApkAsync(unzippedApkRoot, options, signingOptions)
62
62
  /**
63
63
  * Find the latest build-tools directory in the `ANDROID_SDK_ROOT` directory.
64
64
  */
65
- async function findLatestBuildToolsDirAsync() {
65
+ async function findLatestBuildToolsDirAsync(options) {
66
+ const { logger } = options;
66
67
  const androidSdkRoot = process.env['ANDROID_SDK_ROOT'];
67
68
  (0, node_assert_1.default)(androidSdkRoot != null, 'ANDROID_SDK_ROOT environment variable is not set');
68
69
  const buildToolsDir = node_path_1.default.join(androidSdkRoot, 'build-tools');
@@ -86,12 +87,12 @@ async function findLatestBuildToolsDirAsync() {
86
87
  return node_path_1.default.join(buildToolsDir, dirs[0]);
87
88
  }
88
89
  else {
89
- console.error('No build-tools directories found.');
90
+ logger.error('No build-tools directories found.');
90
91
  return null;
91
92
  }
92
93
  }
93
94
  catch (error) {
94
- console.error(`Failed to read the build-tools directory: ${error}`);
95
+ logger.error(`Failed to read the build-tools directory: ${error}`);
95
96
  return null;
96
97
  }
97
98
  }
@@ -99,17 +100,23 @@ async function findLatestBuildToolsDirAsync() {
99
100
  * Search for classes in the APK with the given app ID pattern passing to grep.
100
101
  */
101
102
  async function searchDexClassesAsync(unzipApkRoot, grepAppIdPattern, options) {
102
- const { spawnAsync } = options;
103
- const { dexdumpPath } = await getAndroidBuildToolsAsync();
103
+ const { logger, spawnAsync } = options;
104
+ const { dexdumpPath } = await getAndroidBuildToolsAsync(options);
104
105
  const grepPattern = `"^ Class descriptor : 'L${grepAppIdPattern.replace(/\./g, '/')}\\/"`;
105
- const { stdout } = await spawnAsync(dexdumpPath, ['classes*.dex', '|', 'grep', grepPattern], {
106
- cwd: unzipApkRoot,
107
- shell: true,
108
- stdio: 'pipe',
109
- });
110
- const classes = stdout
111
- .split('\n')
112
- .map((line) => line.match(/Class descriptor\s+?:\s+?'L(.+);'/)?.[1].replace(/\//g, '.'))
113
- .filter(Boolean);
114
- return new Set(classes);
106
+ try {
107
+ const { stdout } = await spawnAsync(dexdumpPath, ['classes*.dex', '|', 'grep', grepPattern], {
108
+ cwd: unzipApkRoot,
109
+ shell: true,
110
+ stdio: 'pipe',
111
+ });
112
+ const classes = stdout
113
+ .split('\n')
114
+ .map((line) => line.match(/Class descriptor\s+?:\s+?'L(.+);'/)?.[1].replace(/\//g, '.'))
115
+ .filter(Boolean);
116
+ return new Set(classes);
117
+ }
118
+ catch (error) {
119
+ logger.info(`Failed to search for classes in the APK - grepAppIdPattern[${grepAppIdPattern}]: ${error}`);
120
+ return null;
121
+ }
115
122
  }
@@ -53,13 +53,20 @@ async function repackAppAndroidAsync(_options) {
53
53
  logger.time(`Updating Androidmanifest.xml`);
54
54
  const androidManiestFilePath = node_path_1.default.join(decodedApkRoot, 'AndroidManifest.xml');
55
55
  const androidManiestXml = await (0, resources_1.parseXmlFileAsync)(androidManiestFilePath);
56
- const originalAppId = await (0, resources_1.queryAppIdFromManifestAsync)(androidManiestXml);
57
- const dexClasses = await (0, build_tools_1.searchDexClassesAsync)(decodedApkRoot, originalAppId, options);
56
+ const originalAppId = (0, resources_1.queryAppIdFromManifest)(androidManiestXml);
57
+ const grepAppIdPatterns = new Set([originalAppId]);
58
+ const originalAppIdFallback = (0, resources_1.queryAppIdFallbackFromManifest)(androidManiestXml, originalAppId);
59
+ if (originalAppIdFallback != null) {
60
+ grepAppIdPatterns.add(originalAppIdFallback);
61
+ }
62
+ const dexClasses = await Promise.any(Array.from(grepAppIdPatterns).map((appId) => (0, build_tools_1.searchDexClassesAsync)(decodedApkRoot, appId, options)));
63
+ (0, node_assert_1.default)(dexClasses != null, 'Failed to search for dex classes with appId in the APK');
58
64
  await (0, resources_1.updateAndroidManifestAsync)({
59
65
  config: exp,
60
66
  androidManiestXml,
61
67
  dexClasses,
62
68
  originalAppId,
69
+ originalAppIdFallback,
63
70
  options,
64
71
  updatesRuntimeVersion,
65
72
  });
@@ -39,11 +39,12 @@ export declare function updateResourcesAsync({ config, decodedApkRoot, }: {
39
39
  /**
40
40
  * Update the proto-based AndroidManiest.xml.
41
41
  */
42
- export declare function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, updatesRuntimeVersion, }: {
42
+ export declare function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, originalAppIdFallback, updatesRuntimeVersion, }: {
43
43
  config: ExpoConfig;
44
44
  androidManiestXml: AndroidManifestType;
45
45
  dexClasses: Set<string>;
46
46
  originalAppId: string;
47
+ originalAppIdFallback: string | null;
47
48
  options: NormalizedOptions;
48
49
  updatesRuntimeVersion: string | null;
49
50
  }): Promise<void>;
@@ -58,4 +59,11 @@ export declare function buildXmlFileAsync(rootNode: XmlNode, filePath: string):
58
59
  /**
59
60
  * Query the app ID from the AndroidManiest.xml.
60
61
  */
61
- export declare function queryAppIdFromManifestAsync(androidManiestXml: XmlNode): Promise<string>;
62
+ export declare function queryAppIdFromManifest(androidManiestXml: XmlNode): string;
63
+ /**
64
+ * Query the fallback original app ID from the AndroidManiest.xml.
65
+ * Sometimes if the app were using `appIdSuffix` or overriding `applicationId` from build variants,
66
+ * the app ID in the `package` attribute will not be the original app ID for dex files
67
+ * As a fallback, we use a heuristic to retrieve original app ID from application class name.
68
+ */
69
+ export declare function queryAppIdFallbackFromManifest(androidManiestXml: XmlNode, appId: string): string | null;
@@ -7,7 +7,8 @@ exports.updateResourcesAsync = updateResourcesAsync;
7
7
  exports.updateAndroidManifestAsync = updateAndroidManifestAsync;
8
8
  exports.parseXmlFileAsync = parseXmlFileAsync;
9
9
  exports.buildXmlFileAsync = buildXmlFileAsync;
10
- exports.queryAppIdFromManifestAsync = queryAppIdFromManifestAsync;
10
+ exports.queryAppIdFromManifest = queryAppIdFromManifest;
11
+ exports.queryAppIdFallbackFromManifest = queryAppIdFallbackFromManifest;
11
12
  const node_assert_1 = __importDefault(require("node:assert"));
12
13
  const promises_1 = __importDefault(require("node:fs/promises"));
13
14
  const node_path_1 = __importDefault(require("node:path"));
@@ -26,7 +27,7 @@ async function updateResourcesAsync({ config, decodedApkRoot, }) {
26
27
  /**
27
28
  * Update the proto-based AndroidManiest.xml.
28
29
  */
29
- async function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, updatesRuntimeVersion, }) {
30
+ async function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, originalAppIdFallback, updatesRuntimeVersion, }) {
30
31
  // [0] Update the package name
31
32
  const appId = (0, utils_1.requireNotNull)(config.android?.package);
32
33
  androidManiestXml.manifest.$.package = appId;
@@ -49,7 +50,11 @@ async function updateAndroidManifestAsync({ config, androidManiestXml, dexClasse
49
50
  .replace(/^exp\+.+$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`);
50
51
  // scheme in app.json
51
52
  if (firstScheme) {
52
- newValue = value.replace(/^myapp$/g, firstScheme);
53
+ newValue = newValue.replace(/^myapp$/g, firstScheme);
54
+ }
55
+ // android.package in app.json (fallback for applicationIdSuffix)
56
+ if (originalAppIdFallback != null) {
57
+ newValue = newValue.replace(originalAppIdFallback, appId);
53
58
  }
54
59
  return newValue;
55
60
  });
@@ -81,11 +86,27 @@ async function buildXmlFileAsync(rootNode, filePath) {
81
86
  /**
82
87
  * Query the app ID from the AndroidManiest.xml.
83
88
  */
84
- async function queryAppIdFromManifestAsync(androidManiestXml) {
89
+ function queryAppIdFromManifest(androidManiestXml) {
85
90
  const packageAttr = androidManiestXml.manifest.$.package;
86
91
  (0, node_assert_1.default)(packageAttr != null, 'Expected package attribute to be present in AndroidManifest.xml');
87
92
  return packageAttr;
88
93
  }
94
+ /**
95
+ * Query the fallback original app ID from the AndroidManiest.xml.
96
+ * Sometimes if the app were using `appIdSuffix` or overriding `applicationId` from build variants,
97
+ * the app ID in the `package` attribute will not be the original app ID for dex files
98
+ * As a fallback, we use a heuristic to retrieve original app ID from application class name.
99
+ */
100
+ function queryAppIdFallbackFromManifest(androidManiestXml, appId) {
101
+ const applicationClassName = androidManiestXml.manifest.application?.[0]?.$?.['android:name'];
102
+ if (applicationClassName) {
103
+ const appIdWithoutSuffix = applicationClassName.replace(/\.[^.]+$/, '');
104
+ if (appIdWithoutSuffix !== applicationClassName && appIdWithoutSuffix !== appId) {
105
+ return appIdWithoutSuffix;
106
+ }
107
+ }
108
+ return null;
109
+ }
89
110
  //#region Internals
90
111
  /**
91
112
  * Update the `expo-updates` configuration in the Android project.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/repack-app",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Repacking tool for Expo apps",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",