@expo/repack-app 0.1.7 → 0.1.9
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
|
|
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
|
-
|
|
90
|
+
logger.error('No build-tools directories found.');
|
|
90
91
|
return null;
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
catch (error) {
|
|
94
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
}
|
package/build/android/index.js
CHANGED
|
@@ -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 =
|
|
57
|
-
const
|
|
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
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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/build/ios/build-tools.js
CHANGED
|
@@ -12,6 +12,7 @@ const glob_1 = require("glob");
|
|
|
12
12
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
13
13
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const slugify_1 = __importDefault(require("slugify"));
|
|
15
16
|
const utils_1 = require("../utils");
|
|
16
17
|
/**
|
|
17
18
|
* Extract the given iOS artifact and return the path to the .app directory.
|
|
@@ -46,11 +47,12 @@ async function extractIosArtifactAsync(options) {
|
|
|
46
47
|
*/
|
|
47
48
|
async function updateFilesAsync(config, appWorkingDirectory) {
|
|
48
49
|
const { dir: parentDir, name } = node_path_1.default.parse(appWorkingDirectory);
|
|
49
|
-
const
|
|
50
|
+
const sanitizedAppName = sanitizedName(config.name);
|
|
51
|
+
const newAppWorkingDirectory = node_path_1.default.join(parentDir, `${sanitizedAppName}.app`);
|
|
50
52
|
// [0] Update the .app directory
|
|
51
53
|
await promises_1.default.rename(node_path_1.default.join(parentDir, `${name}.app`), newAppWorkingDirectory);
|
|
52
54
|
// [1] Rename the executable
|
|
53
|
-
await promises_1.default.rename(node_path_1.default.join(newAppWorkingDirectory, name), node_path_1.default.join(newAppWorkingDirectory,
|
|
55
|
+
await promises_1.default.rename(node_path_1.default.join(newAppWorkingDirectory, name), node_path_1.default.join(newAppWorkingDirectory, sanitizedAppName));
|
|
54
56
|
return newAppWorkingDirectory;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
@@ -106,3 +108,16 @@ end
|
|
|
106
108
|
`;
|
|
107
109
|
return fastfileContents.replace(/^\s*[\r\n]/gm, '');
|
|
108
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Synchronous logic as prebuild to sanitize the name of the app.
|
|
113
|
+
*/
|
|
114
|
+
function sanitizedName(name) {
|
|
115
|
+
// Default to the name `app` when every safe character has been sanitized
|
|
116
|
+
return sanitizedNameForProjects(name) || sanitizedNameForProjects((0, slugify_1.default)(name)) || 'app';
|
|
117
|
+
}
|
|
118
|
+
function sanitizedNameForProjects(name) {
|
|
119
|
+
return name
|
|
120
|
+
.replace(/[\W_]+/g, '')
|
|
121
|
+
.normalize('NFD')
|
|
122
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/repack-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Repacking tool for Expo apps",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"glob": "^11.0.0",
|
|
32
32
|
"picocolors": "^1.1.1",
|
|
33
33
|
"resolve-from": "^5.0.0",
|
|
34
|
+
"slugify": "^1.6.6",
|
|
34
35
|
"temp-dir": "^2.0.0",
|
|
35
36
|
"xml2js": "^0.6.2"
|
|
36
37
|
},
|