@expo/repack-app 0.0.3 → 0.0.5
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.
- package/build/android/Resources.types.js +2 -1
- package/build/android/build-tools.d.ts +1 -1
- package/build/android/build-tools.js +58 -64
- package/build/android/index.d.ts +1 -1
- package/build/android/index.js +47 -39
- package/build/android/resources.d.ts +1 -1
- package/build/android/resources.js +34 -26
- package/build/cli.js +17 -15
- package/build/expo.d.ts +1 -1
- package/build/expo.js +19 -11
- package/build/index.d.ts +2 -2
- package/build/index.js +7 -2
- package/build/ios/build-tools.d.ts +1 -1
- package/build/ios/build-tools.js +31 -25
- package/build/ios/index.d.ts +1 -1
- package/build/ios/index.js +54 -42
- package/build/ios/resources.d.ts +1 -1
- package/build/ios/resources.js +26 -21
- package/build/types.d.ts +10 -0
- package/build/types.js +2 -1
- package/build/utils.js +19 -12
- package/package.json +8 -5
- package/build/cjs-wrapper.d.ts +0 -5
- package/build/cjs-wrapper.js +0 -5
- package/build/log.d.ts +0 -13
- package/build/log.js +0 -23
|
@@ -1,62 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findLatestBuildToolsDirAsync = exports.createResignedApkAsync = exports.createBinaryBasedResourcesAsync = exports.createProtoBasedResourcesAsync = exports.getAndroidBuildToolsAsync = void 0;
|
|
7
|
+
const steps_1 = require("@expo/steps");
|
|
8
|
+
const glob_1 = require("glob");
|
|
9
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
10
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
6
12
|
let cachedAndroidTools = null;
|
|
7
13
|
/**
|
|
8
14
|
* Get the paths to the Android build-tools.
|
|
9
15
|
*/
|
|
10
|
-
|
|
16
|
+
async function getAndroidBuildToolsAsync(options) {
|
|
11
17
|
if (cachedAndroidTools == null) {
|
|
12
18
|
const androidBuildToolsDir = options?.androidBuildToolsDir ?? (await findLatestBuildToolsDirAsync());
|
|
13
|
-
|
|
19
|
+
(0, node_assert_1.default)(androidBuildToolsDir != null, 'Unable to find the Android build-tools directory.');
|
|
14
20
|
cachedAndroidTools = {
|
|
15
|
-
aapt2Path:
|
|
16
|
-
apksignerPath:
|
|
17
|
-
zipalignPath:
|
|
21
|
+
aapt2Path: node_path_1.default.join(androidBuildToolsDir, 'aapt2'),
|
|
22
|
+
apksignerPath: node_path_1.default.join(androidBuildToolsDir, 'apksigner'),
|
|
23
|
+
zipalignPath: node_path_1.default.join(androidBuildToolsDir, 'zipalign'),
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
return cachedAndroidTools;
|
|
21
27
|
}
|
|
28
|
+
exports.getAndroidBuildToolsAsync = getAndroidBuildToolsAsync;
|
|
22
29
|
/**
|
|
23
30
|
* Create an APK with proto-based resources.
|
|
24
31
|
*/
|
|
25
|
-
|
|
32
|
+
async function createProtoBasedResourcesAsync(options) {
|
|
26
33
|
const { sourceAppPath, workingDirectory } = options;
|
|
27
34
|
const { aapt2Path } = await getAndroidBuildToolsAsync(options);
|
|
28
|
-
const protoApkPath =
|
|
29
|
-
await spawnAsync(aapt2Path, ['convert', '-o', protoApkPath, '--output-format', 'proto', '--keep-raw-values', sourceAppPath], {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await spawnAsync('unzip', [protoApkPath, '-d', workingDirectory], {
|
|
33
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
34
|
-
});
|
|
35
|
-
const removeFiles = await glob('**/*', {
|
|
35
|
+
const protoApkPath = node_path_1.default.join(workingDirectory, 'proto.apk');
|
|
36
|
+
await (0, steps_1.spawnAsync)(aapt2Path, ['convert', '-o', protoApkPath, '--output-format', 'proto', '--keep-raw-values', sourceAppPath], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
37
|
+
await (0, steps_1.spawnAsync)('unzip', [protoApkPath, '-d', workingDirectory], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
38
|
+
const removeFiles = await (0, glob_1.glob)('**/*', {
|
|
36
39
|
cwd: workingDirectory,
|
|
37
40
|
maxDepth: 1,
|
|
38
41
|
ignore: ['resources.pb', 'AndroidManifest.xml', 'res/**'],
|
|
39
42
|
absolute: true,
|
|
40
43
|
});
|
|
41
|
-
await Promise.all(removeFiles.map((file) =>
|
|
44
|
+
await Promise.all(removeFiles.map((file) => promises_1.default.rm(file, { recursive: true })));
|
|
42
45
|
return {
|
|
43
|
-
androidManiestFilePath:
|
|
44
|
-
resourcesPbFilePath:
|
|
46
|
+
androidManiestFilePath: node_path_1.default.join(workingDirectory, 'AndroidManifest.xml'),
|
|
47
|
+
resourcesPbFilePath: node_path_1.default.join(workingDirectory, 'resources.pb'),
|
|
45
48
|
};
|
|
46
49
|
}
|
|
50
|
+
exports.createProtoBasedResourcesAsync = createProtoBasedResourcesAsync;
|
|
47
51
|
/**
|
|
48
52
|
* Create an APK with binary-based resources.
|
|
49
53
|
*/
|
|
50
|
-
|
|
54
|
+
async function createBinaryBasedResourcesAsync(options) {
|
|
51
55
|
const { workingDirectory } = options;
|
|
52
56
|
const { aapt2Path } = await getAndroidBuildToolsAsync(options);
|
|
53
|
-
const protoUpdatedApkPath =
|
|
54
|
-
const binaryApkPath =
|
|
55
|
-
await spawnAsync('zip', ['-r', '-0', protoUpdatedApkPath, 'resources.pb', 'AndroidManifest.xml', 'res'],
|
|
56
|
-
cwd: workingDirectory
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await spawnAsync(aapt2Path, [
|
|
57
|
+
const protoUpdatedApkPath = node_path_1.default.join(workingDirectory, 'proto-updated.apk');
|
|
58
|
+
const binaryApkPath = node_path_1.default.join(workingDirectory, 'binary.apk');
|
|
59
|
+
await (0, steps_1.spawnAsync)('zip', ['-r', '-0', protoUpdatedApkPath, 'resources.pb', 'AndroidManifest.xml', 'res'], options.verbose
|
|
60
|
+
? { logger: options.logger, stdio: 'pipe', cwd: workingDirectory }
|
|
61
|
+
: { cwd: workingDirectory });
|
|
62
|
+
await (0, steps_1.spawnAsync)(aapt2Path, [
|
|
60
63
|
'convert',
|
|
61
64
|
'-o',
|
|
62
65
|
binaryApkPath,
|
|
@@ -64,42 +67,33 @@ export async function createBinaryBasedResourcesAsync(options) {
|
|
|
64
67
|
'binary',
|
|
65
68
|
'--keep-raw-values',
|
|
66
69
|
protoUpdatedApkPath,
|
|
67
|
-
], {
|
|
68
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
69
|
-
});
|
|
70
|
+
], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
70
71
|
return binaryApkPath;
|
|
71
72
|
}
|
|
73
|
+
exports.createBinaryBasedResourcesAsync = createBinaryBasedResourcesAsync;
|
|
72
74
|
/**
|
|
73
75
|
* Create a resigned & updated APK.
|
|
74
76
|
* @param binaryApkPath The path to the binary-based resources APK returned from `createBinaryBasedResourcesAsync()`.
|
|
75
77
|
* @param appConfigPath The path to the app.config file returned from `generateAppConfigAsync()`.
|
|
76
78
|
* @returns
|
|
77
79
|
*/
|
|
78
|
-
|
|
80
|
+
async function createResignedApkAsync(binaryApkPath, appConfigPath, options, signingOptions) {
|
|
79
81
|
const { workingDirectory } = options;
|
|
80
82
|
const { apksignerPath, zipalignPath } = await getAndroidBuildToolsAsync(options);
|
|
81
|
-
const unzipWorkingDirectory =
|
|
82
|
-
const resignedApkPath =
|
|
83
|
-
const resignedApkUnalignedPath =
|
|
84
|
-
await
|
|
85
|
-
await spawnAsync('unzip', [options.sourceAppPath, '-d', unzipWorkingDirectory], {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await spawnAsync('
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await
|
|
92
|
-
|
|
93
|
-
cwd: unzipWorkingDirectory
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
await spawnAsync('zip', ['-r', resignedApkUnalignedPath, '.', '-x', 'lib/*', '-x', 'resources.arsc'], {
|
|
97
|
-
cwd: unzipWorkingDirectory,
|
|
98
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
99
|
-
});
|
|
100
|
-
await spawnAsync(zipalignPath, ['-v', '-p', '4', resignedApkUnalignedPath, resignedApkPath], {
|
|
101
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
102
|
-
});
|
|
83
|
+
const unzipWorkingDirectory = node_path_1.default.join(workingDirectory, 'unzip');
|
|
84
|
+
const resignedApkPath = node_path_1.default.join(workingDirectory, 'resigned.apk');
|
|
85
|
+
const resignedApkUnalignedPath = node_path_1.default.join(workingDirectory, 'resigned-unaligned.apk');
|
|
86
|
+
await promises_1.default.mkdir(unzipWorkingDirectory, { recursive: true });
|
|
87
|
+
await (0, steps_1.spawnAsync)('unzip', [options.sourceAppPath, '-d', unzipWorkingDirectory], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
88
|
+
await (0, steps_1.spawnAsync)('unzip', ['-o', binaryApkPath, '-d', unzipWorkingDirectory], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
89
|
+
await promises_1.default.copyFile(appConfigPath, node_path_1.default.join(unzipWorkingDirectory, 'assets', 'app.config'));
|
|
90
|
+
await (0, steps_1.spawnAsync)('zip', ['-r', '-0', resignedApkUnalignedPath, 'lib', 'resources.arsc'], options.verbose
|
|
91
|
+
? { logger: options.logger, stdio: 'pipe', cwd: unzipWorkingDirectory }
|
|
92
|
+
: { cwd: unzipWorkingDirectory });
|
|
93
|
+
await (0, steps_1.spawnAsync)('zip', ['-r', resignedApkUnalignedPath, '.', '-x', 'lib/*', '-x', 'resources.arsc'], options.verbose
|
|
94
|
+
? { logger: options.logger, stdio: 'pipe', cwd: unzipWorkingDirectory }
|
|
95
|
+
: { cwd: unzipWorkingDirectory });
|
|
96
|
+
await (0, steps_1.spawnAsync)(zipalignPath, ['-v', '-p', '4', resignedApkUnalignedPath, resignedApkPath], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
103
97
|
const signerArgs = [
|
|
104
98
|
'sign',
|
|
105
99
|
'--ks',
|
|
@@ -114,20 +108,19 @@ export async function createResignedApkAsync(binaryApkPath, appConfigPath, optio
|
|
|
114
108
|
signerArgs.push('--key-pass', signingOptions.keyPassword);
|
|
115
109
|
}
|
|
116
110
|
signerArgs.push(resignedApkPath);
|
|
117
|
-
await spawnAsync(apksignerPath, signerArgs, {
|
|
118
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
119
|
-
});
|
|
111
|
+
await (0, steps_1.spawnAsync)(apksignerPath, signerArgs, options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
120
112
|
return resignedApkPath;
|
|
121
113
|
}
|
|
114
|
+
exports.createResignedApkAsync = createResignedApkAsync;
|
|
122
115
|
/**
|
|
123
116
|
* Find the latest build-tools directory in the `ANDROID_SDK_ROOT` directory.
|
|
124
117
|
*/
|
|
125
|
-
|
|
118
|
+
async function findLatestBuildToolsDirAsync() {
|
|
126
119
|
const androidSdkRoot = process.env['ANDROID_SDK_ROOT'];
|
|
127
|
-
|
|
128
|
-
const buildToolsDir =
|
|
120
|
+
(0, node_assert_1.default)(androidSdkRoot != null, 'ANDROID_SDK_ROOT environment variable is not set');
|
|
121
|
+
const buildToolsDir = node_path_1.default.join(androidSdkRoot, 'build-tools');
|
|
129
122
|
try {
|
|
130
|
-
const entries = await
|
|
123
|
+
const entries = await promises_1.default.readdir(buildToolsDir, { withFileTypes: true });
|
|
131
124
|
const dirs = entries
|
|
132
125
|
.filter((entry) => entry.isDirectory())
|
|
133
126
|
.map((dir) => dir.name)
|
|
@@ -143,7 +136,7 @@ export async function findLatestBuildToolsDirAsync() {
|
|
|
143
136
|
return versionB.length - versionA.length;
|
|
144
137
|
});
|
|
145
138
|
if (dirs.length > 0) {
|
|
146
|
-
return
|
|
139
|
+
return node_path_1.default.join(buildToolsDir, dirs[0]);
|
|
147
140
|
}
|
|
148
141
|
else {
|
|
149
142
|
console.error('No build-tools directories found.');
|
|
@@ -155,3 +148,4 @@ export async function findLatestBuildToolsDirAsync() {
|
|
|
155
148
|
return null;
|
|
156
149
|
}
|
|
157
150
|
}
|
|
151
|
+
exports.findLatestBuildToolsDirAsync = findLatestBuildToolsDirAsync;
|
package/build/android/index.d.ts
CHANGED
package/build/android/index.js
CHANGED
|
@@ -1,53 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.repackAppAndroidAsync = void 0;
|
|
7
|
+
const config_1 = require("@expo/config");
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
11
|
+
const build_tools_1 = require("./build-tools");
|
|
12
|
+
const resources_1 = require("./resources");
|
|
13
|
+
const expo_1 = require("../expo");
|
|
14
|
+
const utils_1 = require("../utils");
|
|
10
15
|
/**
|
|
11
16
|
* Repack an Android app.
|
|
12
17
|
*/
|
|
13
|
-
|
|
14
|
-
const options = await normalizeOptionAsync(_options);
|
|
15
|
-
const { workingDirectory } = options;
|
|
16
|
-
await
|
|
17
|
-
const { exp } = getConfig(options.projectRoot, {
|
|
18
|
+
async function repackAppAndroidAsync(_options) {
|
|
19
|
+
const options = await (0, utils_1.normalizeOptionAsync)(_options);
|
|
20
|
+
const { workingDirectory, logger } = options;
|
|
21
|
+
await promises_1.default.mkdir(workingDirectory, { recursive: true });
|
|
22
|
+
const { exp } = (0, config_1.getConfig)(options.projectRoot, {
|
|
18
23
|
isPublicConfig: true,
|
|
19
24
|
skipSDKVersionRequirement: true,
|
|
20
25
|
});
|
|
21
|
-
const updatesRuntimeVersion = await resolveRuntimeVersionAsync(options, exp);
|
|
22
|
-
logger.
|
|
23
|
-
logger.
|
|
24
|
-
const { androidManiestFilePath, resourcesPbFilePath } = await createProtoBasedResourcesAsync(options);
|
|
25
|
-
logger.
|
|
26
|
-
logger.
|
|
27
|
-
await updateAndroidManifestAsync(exp, androidManiestFilePath, options, updatesRuntimeVersion);
|
|
28
|
-
logger.
|
|
29
|
-
logger.
|
|
30
|
-
await updateResourcesAsync(exp, resourcesPbFilePath);
|
|
31
|
-
logger.
|
|
32
|
-
logger.
|
|
33
|
-
const binaryApkPath = await createBinaryBasedResourcesAsync(options);
|
|
34
|
-
logger.
|
|
35
|
-
logger.
|
|
36
|
-
const appConfigPath = await generateAppConfigAsync(options, exp);
|
|
37
|
-
logger.
|
|
38
|
-
logger.
|
|
39
|
-
const outputApk = await createResignedApkAsync(binaryApkPath, appConfigPath, options, {
|
|
26
|
+
const updatesRuntimeVersion = await (0, expo_1.resolveRuntimeVersionAsync)(options, exp);
|
|
27
|
+
logger.info(picocolors_1.default.dim(`Resolved runtime version: ${updatesRuntimeVersion}`));
|
|
28
|
+
logger.info(`Unzipping APK and creating proto based resources`);
|
|
29
|
+
const { androidManiestFilePath, resourcesPbFilePath } = await (0, build_tools_1.createProtoBasedResourcesAsync)(options);
|
|
30
|
+
logger.info(`Finished unzipping APK and creating proto based resources ✅`);
|
|
31
|
+
logger.info(`Updating Androidmanifest.xml`);
|
|
32
|
+
await (0, resources_1.updateAndroidManifestAsync)(exp, androidManiestFilePath, options, updatesRuntimeVersion);
|
|
33
|
+
logger.info(`Finished updating Androidmanifest.xml ✅`);
|
|
34
|
+
logger.info(`Updating resources.pb`);
|
|
35
|
+
await (0, resources_1.updateResourcesAsync)(exp, resourcesPbFilePath);
|
|
36
|
+
logger.info(`Finished updating resources.pb ✅`);
|
|
37
|
+
logger.info(`Creating binary based resources`);
|
|
38
|
+
const binaryApkPath = await (0, build_tools_1.createBinaryBasedResourcesAsync)(options);
|
|
39
|
+
logger.info(`Finished creating binary based resources ✅`);
|
|
40
|
+
logger.info(`Generating app config`);
|
|
41
|
+
const appConfigPath = await (0, expo_1.generateAppConfigAsync)(options, exp);
|
|
42
|
+
logger.info(`Finished generating app config ✅`);
|
|
43
|
+
logger.info(`Creating updated apk`);
|
|
44
|
+
const outputApk = await (0, build_tools_1.createResignedApkAsync)(binaryApkPath, appConfigPath, options, {
|
|
40
45
|
keyStorePath: options.androidSigningOptions?.keyStorePath ??
|
|
41
|
-
|
|
46
|
+
node_path_1.default.resolve(__dirname, '../assets/debug.keystore'),
|
|
42
47
|
keyStorePassword: options.androidSigningOptions?.keyStorePassword ?? 'android',
|
|
43
48
|
keyAlias: options.androidSigningOptions?.keyAlias,
|
|
44
49
|
keyPassword: options.androidSigningOptions?.keyPassword,
|
|
45
50
|
});
|
|
46
|
-
logger.
|
|
47
|
-
await
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
logger.info(`Finished creating updated apk ✅`);
|
|
52
|
+
await promises_1.default.rename(outputApk, options.outputPath);
|
|
53
|
+
if (!options.skipWorkingDirCleanup) {
|
|
54
|
+
try {
|
|
55
|
+
await promises_1.default.rmdir(workingDirectory, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
50
58
|
}
|
|
51
|
-
catch { }
|
|
52
59
|
return options.outputPath;
|
|
53
60
|
}
|
|
61
|
+
exports.repackAppAndroidAsync = repackAppAndroidAsync;
|
|
@@ -1,36 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.updateAndroidManifestAsync = exports.updateResourcesAsync = void 0;
|
|
7
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const protobufjs_1 = __importDefault(require("protobufjs"));
|
|
11
|
+
const utils_1 = require("../utils");
|
|
6
12
|
/**
|
|
7
13
|
* Update resources inside the **resources.pb** file.
|
|
8
14
|
*/
|
|
9
|
-
|
|
10
|
-
const root = await
|
|
15
|
+
async function updateResourcesAsync(config, resourcesPbFilePath) {
|
|
16
|
+
const root = await protobufjs_1.default.load(node_path_1.default.join(__dirname, '../../assets', 'Resources.proto'));
|
|
11
17
|
const resourceTableType = root.lookupType('aapt.pb.ResourceTable');
|
|
12
18
|
const resourceTable = await decodeProtoFile(resourceTableType, resourcesPbFilePath);
|
|
13
19
|
// [0] Update the package name
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
(0, node_assert_1.default)(resourceTable.package.length === 1, 'Expected only one package');
|
|
21
|
+
(0, node_assert_1.default)(config.android?.package, 'Expected android.package to be defined');
|
|
16
22
|
resourceTable.package[0].packageName = config.android.package;
|
|
17
23
|
const stringType = resourceTable.package[0].type.find((type) => type.name === 'string');
|
|
18
24
|
const stringEntries = stringType?.entry;
|
|
19
25
|
// [1] Update the `app_name` in **res/values/strings.xml**.
|
|
20
26
|
const appNameEntry = stringEntries?.find((entry) => entry.name === 'app_name');
|
|
21
|
-
|
|
27
|
+
(0, node_assert_1.default)(appNameEntry?.configValue?.[0].value?.item?.str?.value === 'HelloWorld', 'Expected app_name to be predefined "HelloWorld"');
|
|
22
28
|
appNameEntry.configValue[0].value.item.str.value = config.name;
|
|
23
29
|
await encodeProtoFile(resourceTableType, resourcesPbFilePath, resourceTable);
|
|
24
30
|
}
|
|
31
|
+
exports.updateResourcesAsync = updateResourcesAsync;
|
|
25
32
|
/**
|
|
26
33
|
* Update the proto-based AndroidManiest.xml.
|
|
27
34
|
*/
|
|
28
|
-
|
|
29
|
-
const root = await
|
|
35
|
+
async function updateAndroidManifestAsync(config, androidManiestFilePath, options, updatesRuntimeVersion) {
|
|
36
|
+
const root = await protobufjs_1.default.load(node_path_1.default.join(__dirname, '../../assets', 'Resources.proto'));
|
|
30
37
|
const xmlNodeType = root.lookupType('aapt.pb.XmlNode');
|
|
31
38
|
const rootNode = await decodeProtoFile(xmlNodeType, androidManiestFilePath);
|
|
32
39
|
// [0] Update the package name
|
|
33
|
-
replaceXmlAttributeValue(rootNode, (value) => value.replace(/dev\.expo\.templatedefault\.appid/g, requireNotNull(config.android?.package)));
|
|
40
|
+
replaceXmlAttributeValue(rootNode, (value) => value.replace(/dev\.expo\.templatedefault\.appid/g, (0, utils_1.requireNotNull)(config.android?.package)));
|
|
34
41
|
// [1] Update the scheme in the intent-filters
|
|
35
42
|
const intentFilterViewActionNodes = findXmlNodes(rootNode, (node) => node.element?.name === 'intent-filter' &&
|
|
36
43
|
findXmlNodes(node, (node) => node.element?.name === 'action' &&
|
|
@@ -39,19 +46,20 @@ export async function updateAndroidManifestAsync(config, androidManiestFilePath,
|
|
|
39
46
|
for (const node of intentFilterViewActionNodes) {
|
|
40
47
|
replaceXmlAttributeValue(node, (value) => value
|
|
41
48
|
// scheme in app.json
|
|
42
|
-
.replace(/^myapp$/g, requireNotNull(firstScheme))
|
|
49
|
+
.replace(/^myapp$/g, (0, utils_1.requireNotNull)(firstScheme))
|
|
43
50
|
// android.package in app.json
|
|
44
|
-
.replace(/^dev\.expo\.templatedefault$/g, requireNotNull(config.android?.package))
|
|
51
|
+
.replace(/^dev\.expo\.templatedefault$/g, (0, utils_1.requireNotNull)(config.android?.package))
|
|
45
52
|
// default scheme generated from slug in app.json
|
|
46
|
-
.replace(/^exp\+expo-template-default$/g, `exp+${requireNotNull(config.slug)}`));
|
|
53
|
+
.replace(/^exp\+expo-template-default$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`));
|
|
47
54
|
}
|
|
48
55
|
// [2] expo-updates configuration
|
|
49
56
|
const mainApplicationNode = findXmlNodes(rootNode, (node) => node.element?.name === 'application' &&
|
|
50
57
|
node.element?.attribute.find((attr) => attr.name === 'name' && attr.value.endsWith('.MainApplication')) != null)[0];
|
|
51
|
-
|
|
58
|
+
(0, node_assert_1.default)(mainApplicationNode != null, 'Expected application node to be present');
|
|
52
59
|
mutateExpoUpdatesConfigAsync(mainApplicationNode, config, updatesRuntimeVersion);
|
|
53
60
|
await encodeProtoFile(xmlNodeType, androidManiestFilePath, rootNode);
|
|
54
61
|
}
|
|
62
|
+
exports.updateAndroidManifestAsync = updateAndroidManifestAsync;
|
|
55
63
|
//#region Internals
|
|
56
64
|
/**
|
|
57
65
|
* Update the `expo-updates` configuration in the Android project.
|
|
@@ -59,11 +67,11 @@ export async function updateAndroidManifestAsync(config, androidManiestFilePath,
|
|
|
59
67
|
function mutateExpoUpdatesConfigAsync(mainApplicationNode, config, runtimeVersion) {
|
|
60
68
|
const updateEnabledNodeIndex = mainApplicationNode.element?.child.findIndex((child) => child.element?.name === 'meta-data' &&
|
|
61
69
|
child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.ENABLED'));
|
|
62
|
-
|
|
63
|
-
const updateEnabledNode = requireNotNull(mainApplicationNode.element?.child[updateEnabledNodeIndex]);
|
|
70
|
+
(0, node_assert_1.default)(updateEnabledNodeIndex != null && updateEnabledNodeIndex >= 0, `Expected 'expo.modules.updates.ENABLED' node to be present`);
|
|
71
|
+
const updateEnabledNode = (0, utils_1.requireNotNull)(mainApplicationNode.element?.child[updateEnabledNodeIndex]);
|
|
64
72
|
// [0] expo.modules.updates.ENABLED
|
|
65
73
|
const updateEnabledPrimValue = updateEnabledNode.element?.attribute?.[1]?.compiledItem?.prim;
|
|
66
|
-
|
|
74
|
+
(0, node_assert_1.default)(updateEnabledPrimValue != null, 'Expected updateEnabledPrimValue to be present');
|
|
67
75
|
updateEnabledPrimValue.booleanValue = true;
|
|
68
76
|
// [1] expo.modules.updates.EXPO_RUNTIME_VERSION
|
|
69
77
|
const updateRuntimeVersionNode = mainApplicationNode.element?.child.find((child) => child.element?.name === 'meta-data' &&
|
|
@@ -72,19 +80,19 @@ function mutateExpoUpdatesConfigAsync(mainApplicationNode, config, runtimeVersio
|
|
|
72
80
|
mainApplicationNode.element?.child.splice(updateEnabledNodeIndex + 1, 0, createUpdateStringNode(updateEnabledNode, 'expo.modules.updates.EXPO_RUNTIME_VERSION', runtimeVersion));
|
|
73
81
|
}
|
|
74
82
|
else {
|
|
75
|
-
|
|
83
|
+
(0, node_assert_1.default)(updateRuntimeVersionNode.element != null);
|
|
76
84
|
updateRuntimeVersionNode.element.attribute[1].value = runtimeVersion;
|
|
77
85
|
}
|
|
78
86
|
// [2] expo.modules.updates.EXPO_UPDATE_URL
|
|
79
87
|
const updateUrlNode = mainApplicationNode.element?.child.find((child) => child.element?.name === 'meta-data' &&
|
|
80
88
|
child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.EXPO_UPDATE_URL'));
|
|
81
89
|
const updateUrl = config.updates?.url;
|
|
82
|
-
|
|
90
|
+
(0, node_assert_1.default)(updateUrl);
|
|
83
91
|
if (updateUrlNode == null) {
|
|
84
92
|
mainApplicationNode.element?.child.splice(updateEnabledNodeIndex + 1, 0, createUpdateStringNode(updateEnabledNode, 'expo.modules.updates.EXPO_UPDATE_URL', updateUrl));
|
|
85
93
|
}
|
|
86
94
|
else {
|
|
87
|
-
|
|
95
|
+
(0, node_assert_1.default)(updateUrlNode.element != null);
|
|
88
96
|
updateUrlNode.element.attribute[1].value = updateUrl;
|
|
89
97
|
}
|
|
90
98
|
}
|
|
@@ -92,7 +100,7 @@ function mutateExpoUpdatesConfigAsync(mainApplicationNode, config, runtimeVersio
|
|
|
92
100
|
* Decode the proto-encoded file from `filePath` with the given `protoTypeString` and return as JSON object.
|
|
93
101
|
*/
|
|
94
102
|
async function decodeProtoFile(protoType, filePath) {
|
|
95
|
-
const buffer = await
|
|
103
|
+
const buffer = await promises_1.default.readFile(filePath);
|
|
96
104
|
const message = await protoType.decode(buffer);
|
|
97
105
|
const json = protoType.toObject(message, {
|
|
98
106
|
longs: String,
|
|
@@ -108,7 +116,7 @@ async function decodeProtoFile(protoType, filePath) {
|
|
|
108
116
|
async function encodeProtoFile(protoType, filePath, json) {
|
|
109
117
|
const updatedMessage = protoType.fromObject(json);
|
|
110
118
|
const updatedBuffer = protoType.encode(updatedMessage).finish();
|
|
111
|
-
await
|
|
119
|
+
await promises_1.default.writeFile(filePath, updatedBuffer);
|
|
112
120
|
}
|
|
113
121
|
/**
|
|
114
122
|
* Recursively find XML nodes that satisfy the given `predicate`.
|
|
@@ -145,7 +153,7 @@ function createUpdateStringNode(updateEnabledNode, name, value) {
|
|
|
145
153
|
return {
|
|
146
154
|
...updateEnabledNode,
|
|
147
155
|
element: {
|
|
148
|
-
...requireNotNull(updateEnabledNode.element),
|
|
156
|
+
...(0, utils_1.requireNotNull)(updateEnabledNode.element),
|
|
149
157
|
attribute: [
|
|
150
158
|
{
|
|
151
159
|
namespaceUri: 'http://schemas.android.com/apk/res/android',
|
package/build/cli.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const logger_1 = require("@expo/logger");
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const index_1 = require("./index");
|
|
10
|
+
const program = new commander_1.Command('repack-app')
|
|
7
11
|
.requiredOption('-p, --platform <platform>', 'Platform to repack the app for')
|
|
8
12
|
.requiredOption('--source-app <path>', 'Path to the source app file')
|
|
9
13
|
.option('--android-build-tools-dir <path>', 'Path to the Android build tools directory')
|
|
@@ -22,10 +26,11 @@ const program = new Command('repack-app')
|
|
|
22
26
|
.argument('<project-root>', 'Path to the project root')
|
|
23
27
|
.parse(process.argv);
|
|
24
28
|
async function runAsync() {
|
|
29
|
+
const logger = (0, logger_1.createLogger)({ name: 'repack-app' });
|
|
25
30
|
const platform = program.opts().platform;
|
|
26
31
|
const projectRoot = program.args[0];
|
|
27
32
|
if (platform === 'android') {
|
|
28
|
-
const outputPath = await repackAppAndroidAsync({
|
|
33
|
+
const outputPath = await (0, index_1.repackAppAndroidAsync)({
|
|
29
34
|
platform: program.opts().platform,
|
|
30
35
|
projectRoot,
|
|
31
36
|
verbose: !!program.opts().verbose,
|
|
@@ -39,8 +44,9 @@ async function runAsync() {
|
|
|
39
44
|
keyPassword: program.opts().ksKeyPass,
|
|
40
45
|
},
|
|
41
46
|
androidBuildToolsDir: program.opts().androidBuildToolsDir,
|
|
47
|
+
logger,
|
|
42
48
|
});
|
|
43
|
-
logger.
|
|
49
|
+
logger.info(picocolors_1.default.green(`Updated APK created at ${outputPath}`));
|
|
44
50
|
}
|
|
45
51
|
else if (platform === 'ios') {
|
|
46
52
|
const options = {
|
|
@@ -54,14 +60,10 @@ async function runAsync() {
|
|
|
54
60
|
signingIdentity: program.opts().signingIdentity,
|
|
55
61
|
provisioningProfile: program.opts().provisioningProfile,
|
|
56
62
|
},
|
|
63
|
+
logger,
|
|
57
64
|
};
|
|
58
|
-
const outputPath = await repackAppIosAsync(options);
|
|
59
|
-
logger.
|
|
60
|
-
if (options.iosSigningOptions?.signingIdentity &&
|
|
61
|
-
options.iosSigningOptions?.provisioningProfile) {
|
|
62
|
-
logger.log(pico.green(`Resigning the IPA at ${outputPath}`));
|
|
63
|
-
await resignIpaAsync(outputPath, options.iosSigningOptions, options);
|
|
64
|
-
}
|
|
65
|
+
const outputPath = await (0, index_1.repackAppIosAsync)(options);
|
|
66
|
+
logger.info(picocolors_1.default.green(`Updated IPA created at ${outputPath}`));
|
|
65
67
|
}
|
|
66
68
|
else {
|
|
67
69
|
throw new Error(`Unsupported platform: ${platform}`);
|
package/build/expo.d.ts
CHANGED
package/build/expo.js
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveRuntimeVersionAsync = exports.generateAppConfigAsync = void 0;
|
|
7
|
+
const steps_1 = require("@expo/steps");
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const resolve_from_1 = __importDefault(require("resolve-from"));
|
|
5
11
|
/**
|
|
6
12
|
* Generate the app.config file for the Android app.
|
|
7
13
|
*/
|
|
8
|
-
|
|
14
|
+
async function generateAppConfigAsync(options, config) {
|
|
9
15
|
const { workingDirectory } = options;
|
|
10
|
-
const appConfigPath =
|
|
11
|
-
await
|
|
16
|
+
const appConfigPath = node_path_1.default.join(workingDirectory, 'app.config');
|
|
17
|
+
await promises_1.default.writeFile(appConfigPath, JSON.stringify(config));
|
|
12
18
|
return appConfigPath;
|
|
13
19
|
}
|
|
20
|
+
exports.generateAppConfigAsync = generateAppConfigAsync;
|
|
14
21
|
/**
|
|
15
22
|
* Resolve the `runtimeVersion` for expo-updates.
|
|
16
23
|
*/
|
|
17
|
-
|
|
24
|
+
async function resolveRuntimeVersionAsync(options, config) {
|
|
18
25
|
const { projectRoot } = options;
|
|
19
|
-
const cli =
|
|
20
|
-
|
|
21
|
-
const proc = await spawnAsync(cli, ['runtimeversion:resolve', '--platform', 'android', '--workflow', 'managed'], {
|
|
26
|
+
const cli = resolve_from_1.default.silent(projectRoot, 'expo-updates/bin/cli') ??
|
|
27
|
+
(0, resolve_from_1.default)(projectRoot, 'expo-updates/bin/cli.js');
|
|
28
|
+
const proc = await (0, steps_1.spawnAsync)(cli, ['runtimeversion:resolve', '--platform', 'android', '--workflow', 'managed'], {
|
|
22
29
|
cwd: projectRoot,
|
|
23
30
|
});
|
|
24
31
|
const runtimeVersion = JSON.parse(proc.stdout).runtimeVersion;
|
|
25
32
|
return runtimeVersion ?? config.version ?? '1.0.0';
|
|
26
33
|
}
|
|
34
|
+
exports.resolveRuntimeVersionAsync = resolveRuntimeVersionAsync;
|
package/build/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { repackAppAndroidAsync } from './android
|
|
2
|
-
export { repackAppIosAsync } from './ios
|
|
1
|
+
export { repackAppAndroidAsync } from './android';
|
|
2
|
+
export { repackAppIosAsync } from './ios';
|
package/build/index.js
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.repackAppIosAsync = exports.repackAppAndroidAsync = void 0;
|
|
4
|
+
var android_1 = require("./android");
|
|
5
|
+
Object.defineProperty(exports, "repackAppAndroidAsync", { enumerable: true, get: function () { return android_1.repackAppAndroidAsync; } });
|
|
6
|
+
var ios_1 = require("./ios");
|
|
7
|
+
Object.defineProperty(exports, "repackAppIosAsync", { enumerable: true, get: function () { return ios_1.repackAppIosAsync; } });
|
package/build/ios/build-tools.js
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createIpaAsync = exports.updateFilesAsync = exports.unzipIpaAsync = void 0;
|
|
7
|
+
const steps_1 = require("@expo/steps");
|
|
8
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const utils_1 = require("../utils");
|
|
6
12
|
/**
|
|
7
13
|
* Unzip the IPA file.
|
|
8
14
|
*/
|
|
9
|
-
|
|
10
|
-
const unzipWorkingDirectory =
|
|
11
|
-
await spawnAsync('unzip', [options.sourceAppPath, '-d', unzipWorkingDirectory], {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const appWorkingDirectory = path.join(unzipWorkingDirectory, 'Payload', 'HelloWorld.app');
|
|
15
|
-
assert(await directoryExistsAsync(appWorkingDirectory));
|
|
15
|
+
async function unzipIpaAsync(options) {
|
|
16
|
+
const unzipWorkingDirectory = node_path_1.default.join(options.workingDirectory, 'unzip');
|
|
17
|
+
await (0, steps_1.spawnAsync)('unzip', [options.sourceAppPath, '-d', unzipWorkingDirectory], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
18
|
+
const appWorkingDirectory = node_path_1.default.join(unzipWorkingDirectory, 'Payload', 'HelloWorld.app');
|
|
19
|
+
(0, node_assert_1.default)(await (0, utils_1.directoryExistsAsync)(appWorkingDirectory));
|
|
16
20
|
return appWorkingDirectory;
|
|
17
21
|
}
|
|
22
|
+
exports.unzipIpaAsync = unzipIpaAsync;
|
|
18
23
|
/**
|
|
19
24
|
* Update some binary files.
|
|
20
25
|
*/
|
|
21
|
-
|
|
22
|
-
const parentDir =
|
|
23
|
-
const newAppWorkingDirectory =
|
|
26
|
+
async function updateFilesAsync(config, appWorkingDirectory) {
|
|
27
|
+
const parentDir = node_path_1.default.dirname(appWorkingDirectory);
|
|
28
|
+
const newAppWorkingDirectory = node_path_1.default.join(parentDir, `${config.name}.app`);
|
|
24
29
|
// [0] Update the .app directory
|
|
25
|
-
await
|
|
30
|
+
await promises_1.default.rename(node_path_1.default.join(parentDir, 'HelloWorld.app'), newAppWorkingDirectory);
|
|
26
31
|
// [1] Rename the executable
|
|
27
|
-
await
|
|
32
|
+
await promises_1.default.rename(node_path_1.default.join(newAppWorkingDirectory, 'HelloWorld'), node_path_1.default.join(newAppWorkingDirectory, config.name));
|
|
28
33
|
return newAppWorkingDirectory;
|
|
29
34
|
}
|
|
35
|
+
exports.updateFilesAsync = updateFilesAsync;
|
|
30
36
|
/**
|
|
31
37
|
* From the given working .app directory, create a new .ipa file.
|
|
32
38
|
*/
|
|
33
|
-
|
|
39
|
+
async function createIpaAsync(options, appWorkingDirectory) {
|
|
34
40
|
const { workingDirectory } = options;
|
|
35
|
-
await
|
|
36
|
-
await
|
|
37
|
-
const outputIpaPath =
|
|
38
|
-
await spawnAsync('zip', ['-r', outputIpaPath, 'Payload'],
|
|
39
|
-
cwd: workingDirectory
|
|
40
|
-
|
|
41
|
-
});
|
|
41
|
+
await promises_1.default.mkdir(node_path_1.default.join(workingDirectory, 'Payload'), { recursive: true });
|
|
42
|
+
await promises_1.default.rename(appWorkingDirectory, node_path_1.default.join(workingDirectory, 'Payload', node_path_1.default.basename(appWorkingDirectory)));
|
|
43
|
+
const outputIpaPath = node_path_1.default.join(workingDirectory, 'repacked.ipa');
|
|
44
|
+
await (0, steps_1.spawnAsync)('zip', ['-r', outputIpaPath, 'Payload'], options.verbose
|
|
45
|
+
? { logger: options.logger, stdio: 'pipe', cwd: workingDirectory }
|
|
46
|
+
: { cwd: workingDirectory });
|
|
42
47
|
return outputIpaPath;
|
|
43
48
|
}
|
|
49
|
+
exports.createIpaAsync = createIpaAsync;
|
package/build/ios/index.d.ts
CHANGED
package/build/ios/index.js
CHANGED
|
@@ -1,54 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resignIpaAsync = exports.repackAppIosAsync = void 0;
|
|
7
|
+
const config_1 = require("@expo/config");
|
|
8
|
+
const steps_1 = require("@expo/steps");
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
12
|
+
const build_tools_1 = require("./build-tools");
|
|
13
|
+
const resources_1 = require("./resources");
|
|
14
|
+
const expo_1 = require("../expo");
|
|
15
|
+
const utils_1 = require("../utils");
|
|
11
16
|
/**
|
|
12
17
|
* Repack an iOS app.
|
|
13
18
|
*/
|
|
14
|
-
|
|
15
|
-
const options = await normalizeOptionAsync(_options);
|
|
16
|
-
const { workingDirectory } = options;
|
|
17
|
-
await
|
|
18
|
-
const { exp } = getConfig(options.projectRoot, {
|
|
19
|
+
async function repackAppIosAsync(_options) {
|
|
20
|
+
const options = await (0, utils_1.normalizeOptionAsync)(_options);
|
|
21
|
+
const { workingDirectory, logger } = options;
|
|
22
|
+
await promises_1.default.mkdir(workingDirectory, { recursive: true });
|
|
23
|
+
const { exp } = (0, config_1.getConfig)(options.projectRoot, {
|
|
19
24
|
isPublicConfig: true,
|
|
20
25
|
skipSDKVersionRequirement: true,
|
|
21
26
|
});
|
|
22
|
-
const updatesRuntimeVersion = await resolveRuntimeVersionAsync(options, exp);
|
|
23
|
-
logger.
|
|
24
|
-
logger.
|
|
25
|
-
let appWorkingDirectory = await unzipIpaAsync(options);
|
|
26
|
-
appWorkingDirectory = await updateFilesAsync(exp, appWorkingDirectory);
|
|
27
|
-
logger.
|
|
28
|
-
logger.
|
|
29
|
-
await updateInfoPlistAsync(exp,
|
|
30
|
-
logger.
|
|
31
|
-
logger.
|
|
32
|
-
await updateExpoPlistAsync(exp,
|
|
33
|
-
logger.
|
|
34
|
-
logger.
|
|
35
|
-
const appConfigPath = await generateAppConfigAsync(options, exp);
|
|
36
|
-
await
|
|
37
|
-
logger.
|
|
38
|
-
logger.
|
|
39
|
-
const outputIpa = await createIpaAsync(options, appWorkingDirectory);
|
|
40
|
-
logger.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
await
|
|
27
|
+
const updatesRuntimeVersion = await (0, expo_1.resolveRuntimeVersionAsync)(options, exp);
|
|
28
|
+
logger.info(picocolors_1.default.dim(`Resolved runtime version: ${updatesRuntimeVersion}`));
|
|
29
|
+
logger.info(`Unzipping IPA`);
|
|
30
|
+
let appWorkingDirectory = await (0, build_tools_1.unzipIpaAsync)(options);
|
|
31
|
+
appWorkingDirectory = await (0, build_tools_1.updateFilesAsync)(exp, appWorkingDirectory);
|
|
32
|
+
logger.info(`Finished unzipping IPA ✅`);
|
|
33
|
+
logger.info(`Updating Info.plist`);
|
|
34
|
+
await (0, resources_1.updateInfoPlistAsync)(exp, node_path_1.default.join(appWorkingDirectory, 'Info.plist'), options);
|
|
35
|
+
logger.info(`Finished updating Info.plist ✅`);
|
|
36
|
+
logger.info(`Updating Expo.plist`);
|
|
37
|
+
await (0, resources_1.updateExpoPlistAsync)(exp, node_path_1.default.join(appWorkingDirectory, 'Expo.plist'), updatesRuntimeVersion, options);
|
|
38
|
+
logger.info(`Finished updating Expo.plist ✅`);
|
|
39
|
+
logger.info(`Generating app.config`);
|
|
40
|
+
const appConfigPath = await (0, expo_1.generateAppConfigAsync)(options, exp);
|
|
41
|
+
await promises_1.default.copyFile(appConfigPath, node_path_1.default.join(appWorkingDirectory, 'EXConstants.bundle', 'app.config'));
|
|
42
|
+
logger.info(`Finished generating app.config ✅`);
|
|
43
|
+
logger.info(`Creating updated ipa`);
|
|
44
|
+
const outputIpa = await (0, build_tools_1.createIpaAsync)(options, appWorkingDirectory);
|
|
45
|
+
logger.info(`Finished creating updated ipa ✅`);
|
|
46
|
+
if (options.iosSigningOptions) {
|
|
47
|
+
logger.info(`Resigning the IPA at ${outputIpa}`);
|
|
48
|
+
await resignIpaAsync(outputIpa, options.iosSigningOptions, options);
|
|
49
|
+
logger.info(`Finished resigning the IPA ✅`);
|
|
50
|
+
}
|
|
51
|
+
await promises_1.default.rename(outputIpa, options.outputPath);
|
|
52
|
+
if (!options.skipWorkingDirCleanup) {
|
|
53
|
+
try {
|
|
54
|
+
await promises_1.default.rmdir(workingDirectory, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
44
57
|
}
|
|
45
|
-
catch { }
|
|
46
58
|
return options.outputPath;
|
|
47
59
|
}
|
|
60
|
+
exports.repackAppIosAsync = repackAppIosAsync;
|
|
48
61
|
/**
|
|
49
62
|
* Resign an IPA by fastlane.
|
|
50
63
|
*/
|
|
51
|
-
|
|
64
|
+
async function resignIpaAsync(ipaPath, signingOptions, options) {
|
|
52
65
|
const args = [
|
|
53
66
|
'run',
|
|
54
67
|
'resign',
|
|
@@ -59,7 +72,6 @@ export async function resignIpaAsync(ipaPath, signingOptions, options) {
|
|
|
59
72
|
if (signingOptions.keychainPath) {
|
|
60
73
|
args.push(`keychain_path:${signingOptions.keychainPath}`);
|
|
61
74
|
}
|
|
62
|
-
await spawnAsync('fastlane', args, {
|
|
63
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
64
|
-
});
|
|
75
|
+
await (0, steps_1.spawnAsync)('fastlane', args, { logger: options.logger, stdio: 'pipe' });
|
|
65
76
|
}
|
|
77
|
+
exports.resignIpaAsync = resignIpaAsync;
|
package/build/ios/resources.d.ts
CHANGED
package/build/ios/resources.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.updateExpoPlistAsync = exports.updateInfoPlistAsync = void 0;
|
|
7
|
+
const plist_1 = __importDefault(require("@expo/plist"));
|
|
8
|
+
const steps_1 = require("@expo/steps");
|
|
9
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
10
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
11
|
+
const utils_1 = require("../utils");
|
|
5
12
|
/**
|
|
6
13
|
* Update the Info.plist file.
|
|
7
14
|
*/
|
|
8
|
-
|
|
15
|
+
async function updateInfoPlistAsync(config, infoPlistPath, options) {
|
|
9
16
|
const bundleIdentifier = config.ios?.bundleIdentifier;
|
|
10
|
-
|
|
17
|
+
(0, node_assert_1.default)(bundleIdentifier, 'The `bundleIdentifier` must be specified in the `ios` config.');
|
|
11
18
|
const firstScheme = Array.isArray(config.scheme) ? config.scheme[0] : config.scheme;
|
|
12
19
|
await updateBinaryPlistAsync(infoPlistPath, options, (data) => {
|
|
13
20
|
const urlTypes = data.CFBundleURLTypes.slice();
|
|
@@ -16,11 +23,11 @@ export async function updateInfoPlistAsync(config, infoPlistPath, options) {
|
|
|
16
23
|
const schemes = urlType.CFBundleURLSchemes.map((scheme) => {
|
|
17
24
|
return (scheme
|
|
18
25
|
// scheme in app.json
|
|
19
|
-
.replace(/^myapp$/g, requireNotNull(firstScheme))
|
|
26
|
+
.replace(/^myapp$/g, (0, utils_1.requireNotNull)(firstScheme))
|
|
20
27
|
// ios.bundleIdentifier in app.json
|
|
21
|
-
.replace(/^dev.expo.templatedefault$/g, requireNotNull(bundleIdentifier))
|
|
28
|
+
.replace(/^dev.expo.templatedefault$/g, (0, utils_1.requireNotNull)(bundleIdentifier))
|
|
22
29
|
// default scheme generated from slug in app.json
|
|
23
|
-
.replace(/^exp\+expo-template-default$/g, `exp+${requireNotNull(config.slug)}`));
|
|
30
|
+
.replace(/^exp\+expo-template-default$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`));
|
|
24
31
|
});
|
|
25
32
|
urlType.CFBundleURLSchemes = schemes;
|
|
26
33
|
}
|
|
@@ -36,13 +43,14 @@ export async function updateInfoPlistAsync(config, infoPlistPath, options) {
|
|
|
36
43
|
};
|
|
37
44
|
});
|
|
38
45
|
}
|
|
46
|
+
exports.updateInfoPlistAsync = updateInfoPlistAsync;
|
|
39
47
|
/**
|
|
40
48
|
* Update the Expo.plist file.
|
|
41
49
|
*/
|
|
42
|
-
|
|
50
|
+
async function updateExpoPlistAsync(config, expoPlistPath, runtimeVersion, options) {
|
|
43
51
|
await updateBinaryPlistAsync(expoPlistPath, options, (data) => {
|
|
44
52
|
const updateUrl = config.updates?.url;
|
|
45
|
-
|
|
53
|
+
(0, node_assert_1.default)(updateUrl);
|
|
46
54
|
return {
|
|
47
55
|
...data,
|
|
48
56
|
EXUpdatesEnabled: true,
|
|
@@ -51,18 +59,15 @@ export async function updateExpoPlistAsync(config, expoPlistPath, runtimeVersion
|
|
|
51
59
|
};
|
|
52
60
|
});
|
|
53
61
|
}
|
|
62
|
+
exports.updateExpoPlistAsync = updateExpoPlistAsync;
|
|
54
63
|
//#region Internals
|
|
55
64
|
async function updateBinaryPlistAsync(plistPath, options, updater) {
|
|
56
|
-
await spawnAsync('plutil', ['-convert', 'xml1', plistPath], {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const contents = await fs.readFile(plistPath, 'utf8');
|
|
60
|
-
const data = await plist.parse(contents);
|
|
65
|
+
await (0, steps_1.spawnAsync)('plutil', ['-convert', 'xml1', plistPath], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
66
|
+
const contents = await promises_1.default.readFile(plistPath, 'utf8');
|
|
67
|
+
const data = await plist_1.default.parse(contents);
|
|
61
68
|
const updatedData = updater(data);
|
|
62
|
-
const updatedContents =
|
|
63
|
-
await
|
|
64
|
-
await spawnAsync('plutil', ['-convert', 'binary1', plistPath], {
|
|
65
|
-
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
66
|
-
});
|
|
69
|
+
const updatedContents = plist_1.default.build(updatedData);
|
|
70
|
+
await promises_1.default.writeFile(plistPath, updatedContents);
|
|
71
|
+
await (0, steps_1.spawnAsync)('plutil', ['-convert', 'binary1', plistPath], options.verbose ? { logger: options.logger, stdio: 'pipe' } : undefined);
|
|
67
72
|
}
|
|
68
73
|
//#endregion
|
package/build/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="bunyan" />
|
|
2
|
+
import type { bunyan } from '@expo/logger';
|
|
1
3
|
export interface Options {
|
|
2
4
|
/**
|
|
3
5
|
* The platform to repack the app for.
|
|
@@ -11,6 +13,10 @@ export interface Options {
|
|
|
11
13
|
* The prebuilt app file path that acts as a source for repacking.
|
|
12
14
|
*/
|
|
13
15
|
sourceAppPath: string;
|
|
16
|
+
/**
|
|
17
|
+
* The bunyan logger instance.
|
|
18
|
+
*/
|
|
19
|
+
logger: bunyan;
|
|
14
20
|
/**
|
|
15
21
|
* Working directory.
|
|
16
22
|
*/
|
|
@@ -37,6 +43,10 @@ export interface Options {
|
|
|
37
43
|
* The options for signing the ios app.
|
|
38
44
|
*/
|
|
39
45
|
iosSigningOptions?: IosSigningOptions;
|
|
46
|
+
/**
|
|
47
|
+
* Skip the working directory cleanup.
|
|
48
|
+
*/
|
|
49
|
+
skipWorkingDirCleanup?: boolean;
|
|
40
50
|
}
|
|
41
51
|
export interface NormalizedOptions extends Options {
|
|
42
52
|
workingDirectory: NonNullable<Options['workingDirectory']>;
|
package/build/types.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/build/utils.js
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeOptionAsync = exports.requireNotNull = exports.directoryExistsAsync = void 0;
|
|
7
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
6
10
|
/**
|
|
7
11
|
* Check if a directory exists.
|
|
8
12
|
*/
|
|
9
|
-
|
|
10
|
-
return (await
|
|
13
|
+
async function directoryExistsAsync(file) {
|
|
14
|
+
return (await promises_1.default.stat(file).catch(() => null))?.isDirectory() ?? false;
|
|
11
15
|
}
|
|
16
|
+
exports.directoryExistsAsync = directoryExistsAsync;
|
|
12
17
|
/**
|
|
13
18
|
* Return the non-null value.
|
|
14
19
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
function requireNotNull(value) {
|
|
21
|
+
(0, node_assert_1.default)(value != null, 'Expected value to be non-null');
|
|
17
22
|
return value;
|
|
18
23
|
}
|
|
24
|
+
exports.requireNotNull = requireNotNull;
|
|
19
25
|
/**
|
|
20
26
|
* Normalize the options.
|
|
21
27
|
*/
|
|
22
|
-
|
|
28
|
+
async function normalizeOptionAsync(options) {
|
|
23
29
|
const fileExt = options.platform === 'android' ? '.apk' : '.ipa';
|
|
24
30
|
return {
|
|
25
31
|
...options,
|
|
26
|
-
workingDirectory: options.workingDirectory ?? (await
|
|
27
|
-
outputPath: options.outputPath ??
|
|
32
|
+
workingDirectory: options.workingDirectory ?? (await promises_1.default.mkdtemp(node_path_1.default.join(require('temp-dir'), 'repack-app-'))),
|
|
33
|
+
outputPath: options.outputPath ?? node_path_1.default.join(options.projectRoot, `repacked${fileExt}`),
|
|
28
34
|
};
|
|
29
35
|
}
|
|
36
|
+
exports.normalizeOptionAsync = normalizeOptionAsync;
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/repack-app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Repacking tool for Expo apps",
|
|
5
5
|
"main": "build/index.js",
|
|
6
|
-
"module": "build/index.js",
|
|
7
6
|
"types": "build/index.d.ts",
|
|
8
|
-
"type": "
|
|
7
|
+
"type": "commonjs",
|
|
9
8
|
"scripts": {
|
|
10
9
|
"build": "expo-module build",
|
|
11
10
|
"clean": "expo-module clean",
|
|
@@ -27,8 +26,9 @@
|
|
|
27
26
|
"license": "BUSL-1.1",
|
|
28
27
|
"dependencies": {
|
|
29
28
|
"@expo/config": "^8.5.6",
|
|
30
|
-
"@expo/
|
|
31
|
-
"
|
|
29
|
+
"@expo/logger": "^1.0.57",
|
|
30
|
+
"@expo/steps": "^1.0.108",
|
|
31
|
+
"commander": "^11.0.0",
|
|
32
32
|
"glob": "^10.3.12",
|
|
33
33
|
"picocolors": "^1.0.0",
|
|
34
34
|
"protobufjs": "^7.2.6",
|
|
@@ -40,5 +40,8 @@
|
|
|
40
40
|
"eslint": "^8.57.0",
|
|
41
41
|
"expo-module-scripts": "^3.4.2",
|
|
42
42
|
"typescript": "^5.4.5"
|
|
43
|
+
},
|
|
44
|
+
"volta": {
|
|
45
|
+
"node": "22.1.0"
|
|
43
46
|
}
|
|
44
47
|
}
|
package/build/cjs-wrapper.d.ts
DELETED
package/build/cjs-wrapper.js
DELETED
package/build/log.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function log(...message: string[]): void;
|
|
2
|
-
export declare function warn(...message: string[]): void;
|
|
3
|
-
export declare function error(...message: string[]): void;
|
|
4
|
-
export declare function time(label?: string): void;
|
|
5
|
-
export declare function timeEnd(label?: string): void;
|
|
6
|
-
declare const _default: {
|
|
7
|
-
log: typeof log;
|
|
8
|
-
warn: typeof warn;
|
|
9
|
-
error: typeof error;
|
|
10
|
-
time: typeof time;
|
|
11
|
-
timeEnd: typeof timeEnd;
|
|
12
|
-
};
|
|
13
|
-
export default _default;
|
package/build/log.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import pico from 'picocolors';
|
|
2
|
-
export function log(...message) {
|
|
3
|
-
console.log(...message);
|
|
4
|
-
}
|
|
5
|
-
export function warn(...message) {
|
|
6
|
-
console.warn(...message.map((value) => pico.yellow(value)));
|
|
7
|
-
}
|
|
8
|
-
export function error(...message) {
|
|
9
|
-
console.error(...message);
|
|
10
|
-
}
|
|
11
|
-
export function time(label) {
|
|
12
|
-
console.time(label);
|
|
13
|
-
}
|
|
14
|
-
export function timeEnd(label) {
|
|
15
|
-
console.timeEnd(label);
|
|
16
|
-
}
|
|
17
|
-
export default {
|
|
18
|
-
log,
|
|
19
|
-
warn,
|
|
20
|
-
error,
|
|
21
|
-
time,
|
|
22
|
-
timeEnd,
|
|
23
|
-
};
|