@expo/repack-app 0.0.5 → 0.1.0
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/assets/apktool_2.10.0.jar +0 -0
- package/build/android/apktool.d.ts +21 -0
- package/build/android/apktool.js +165 -0
- package/build/android/build-tools.d.ts +7 -14
- package/build/android/build-tools.js +26 -60
- package/build/android/index.js +45 -14
- package/build/android/resources.d.ts +54 -3
- package/build/android/resources.js +109 -114
- package/build/cli.js +18 -4
- package/build/expo.d.ts +11 -0
- package/build/expo.js +74 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +15 -0
- package/build/ios/build-tools.d.ts +5 -1
- package/build/ios/build-tools.js +37 -2
- package/build/ios/index.d.ts +1 -1
- package/build/ios/index.js +33 -15
- package/build/ios/resources.d.ts +19 -2
- package/build/ios/resources.js +50 -5
- package/build/types.d.ts +19 -2
- package/package.json +16 -4
- package/assets/Configuration.proto +0 -216
- package/assets/Resources.proto +0 -648
- package/build/android/Resources.types.d.ts +0 -93
- package/build/android/Resources.types.js +0 -5
|
@@ -3,176 +3,171 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.updateAndroidManifestAsync = exports.updateResourcesAsync = void 0;
|
|
6
|
+
exports.queryAppIdFromManifestAsync = exports.buildXmlFileAsync = exports.parseXmlFileAsync = exports.updateAndroidManifestAsync = exports.updateResourcesAsync = void 0;
|
|
7
7
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
8
8
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const
|
|
10
|
+
const xml2js_1 = require("xml2js");
|
|
11
11
|
const utils_1 = require("../utils");
|
|
12
12
|
/**
|
|
13
|
-
* Update resources
|
|
13
|
+
* Update resources in the decoded APK.
|
|
14
14
|
*/
|
|
15
|
-
async function updateResourcesAsync(config,
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
(0, node_assert_1.default)(config.android?.package, 'Expected android.package to be defined');
|
|
22
|
-
resourceTable.package[0].packageName = config.android.package;
|
|
23
|
-
const stringType = resourceTable.package[0].type.find((type) => type.name === 'string');
|
|
24
|
-
const stringEntries = stringType?.entry;
|
|
25
|
-
// [1] Update the `app_name` in **res/values/strings.xml**.
|
|
26
|
-
const appNameEntry = stringEntries?.find((entry) => entry.name === 'app_name');
|
|
27
|
-
(0, node_assert_1.default)(appNameEntry?.configValue?.[0].value?.item?.str?.value === 'HelloWorld', 'Expected app_name to be predefined "HelloWorld"');
|
|
28
|
-
appNameEntry.configValue[0].value.item.str.value = config.name;
|
|
29
|
-
await encodeProtoFile(resourceTableType, resourcesPbFilePath, resourceTable);
|
|
15
|
+
async function updateResourcesAsync({ config, decodedApkRoot, }) {
|
|
16
|
+
// [0] Update the `app_name` in **res/values/strings.xml**.
|
|
17
|
+
const stringsXmlPath = node_path_1.default.join(decodedApkRoot, 'res', 'values', 'strings.xml');
|
|
18
|
+
let stringsXml = await promises_1.default.readFile(stringsXmlPath, 'utf8');
|
|
19
|
+
stringsXml = stringsXml.replace(/(<string name="app_name">)(.+)(<\/string>)/, `$1${config.name}$3`);
|
|
20
|
+
await promises_1.default.writeFile(stringsXmlPath, stringsXml);
|
|
30
21
|
}
|
|
31
22
|
exports.updateResourcesAsync = updateResourcesAsync;
|
|
32
23
|
/**
|
|
33
24
|
* Update the proto-based AndroidManiest.xml.
|
|
34
25
|
*/
|
|
35
|
-
async function updateAndroidManifestAsync(config,
|
|
36
|
-
const root = await protobufjs_1.default.load(node_path_1.default.join(__dirname, '../../assets', 'Resources.proto'));
|
|
37
|
-
const xmlNodeType = root.lookupType('aapt.pb.XmlNode');
|
|
38
|
-
const rootNode = await decodeProtoFile(xmlNodeType, androidManiestFilePath);
|
|
26
|
+
async function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, updatesRuntimeVersion, }) {
|
|
39
27
|
// [0] Update the package name
|
|
40
|
-
|
|
28
|
+
const appId = (0, utils_1.requireNotNull)(config.android?.package);
|
|
29
|
+
androidManiestXml.manifest.$.package = appId;
|
|
30
|
+
replaceXmlAttributeValue(androidManiestXml, (value) => {
|
|
31
|
+
if (value.startsWith(originalAppId) && !dexClasses.has(value)) {
|
|
32
|
+
return value.replace(originalAppId, appId);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
});
|
|
41
36
|
// [1] Update the scheme in the intent-filters
|
|
42
|
-
const intentFilterViewActionNodes = findXmlNodes(
|
|
43
|
-
findXmlNodes(node, (node) => node
|
|
44
|
-
node.element?.attribute[0]?.value === 'android.intent.action.VIEW').length > 0);
|
|
37
|
+
const intentFilterViewActionNodes = findXmlNodes('manifest', androidManiestXml.manifest, (nodeName, node) => nodeName === 'intent-filter' &&
|
|
38
|
+
findXmlNodes(nodeName, node, (nodeName, node) => nodeName === 'action' && node[0].$?.['android:name'] === 'android.intent.action.VIEW').length > 0);
|
|
45
39
|
const firstScheme = Array.isArray(config.scheme) ? config.scheme[0] : config.scheme;
|
|
46
40
|
for (const node of intentFilterViewActionNodes) {
|
|
47
41
|
replaceXmlAttributeValue(node, (value) => value
|
|
48
42
|
// scheme in app.json
|
|
49
43
|
.replace(/^myapp$/g, (0, utils_1.requireNotNull)(firstScheme))
|
|
50
44
|
// android.package in app.json
|
|
51
|
-
.replace(
|
|
45
|
+
.replace(originalAppId, appId)
|
|
52
46
|
// default scheme generated from slug in app.json
|
|
53
|
-
.replace(/^exp
|
|
47
|
+
.replace(/^exp\+.+$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`));
|
|
54
48
|
}
|
|
55
49
|
// [2] expo-updates configuration
|
|
56
|
-
const mainApplicationNode =
|
|
57
|
-
|
|
58
|
-
(
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
const mainApplicationNode = androidManiestXml.manifest.application[0];
|
|
51
|
+
(0, node_assert_1.default)(mainApplicationNode.$?.['android:name']?.endsWith('.MainApplication'), 'Expected application node to be named as MainApplication');
|
|
52
|
+
if (updatesRuntimeVersion) {
|
|
53
|
+
mutateExpoUpdatesConfigAsync(mainApplicationNode, config, updatesRuntimeVersion);
|
|
54
|
+
}
|
|
61
55
|
}
|
|
62
56
|
exports.updateAndroidManifestAsync = updateAndroidManifestAsync;
|
|
57
|
+
/**
|
|
58
|
+
* Parse the XML file.
|
|
59
|
+
*/
|
|
60
|
+
async function parseXmlFileAsync(filePath) {
|
|
61
|
+
const contents = await promises_1.default.readFile(filePath, 'utf8');
|
|
62
|
+
const parser = new xml2js_1.Parser();
|
|
63
|
+
const xmlJs = await parser.parseStringPromise(contents);
|
|
64
|
+
return xmlJs;
|
|
65
|
+
}
|
|
66
|
+
exports.parseXmlFileAsync = parseXmlFileAsync;
|
|
67
|
+
/**
|
|
68
|
+
* Build the XML file from the given node.
|
|
69
|
+
*/
|
|
70
|
+
async function buildXmlFileAsync(rootNode, filePath) {
|
|
71
|
+
const builder = new xml2js_1.Builder();
|
|
72
|
+
const xmlString = builder.buildObject(rootNode);
|
|
73
|
+
await promises_1.default.writeFile(filePath, xmlString);
|
|
74
|
+
}
|
|
75
|
+
exports.buildXmlFileAsync = buildXmlFileAsync;
|
|
76
|
+
/**
|
|
77
|
+
* Query the app ID from the AndroidManiest.xml.
|
|
78
|
+
*/
|
|
79
|
+
async function queryAppIdFromManifestAsync(androidManiestXml) {
|
|
80
|
+
const packageAttr = androidManiestXml.manifest.$.package;
|
|
81
|
+
(0, node_assert_1.default)(packageAttr != null, 'Expected package attribute to be present in AndroidManifest.xml');
|
|
82
|
+
return packageAttr;
|
|
83
|
+
}
|
|
84
|
+
exports.queryAppIdFromManifestAsync = queryAppIdFromManifestAsync;
|
|
63
85
|
//#region Internals
|
|
64
86
|
/**
|
|
65
87
|
* Update the `expo-updates` configuration in the Android project.
|
|
66
88
|
*/
|
|
67
89
|
function mutateExpoUpdatesConfigAsync(mainApplicationNode, config, runtimeVersion) {
|
|
68
|
-
const
|
|
69
|
-
child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.ENABLED'));
|
|
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]);
|
|
90
|
+
const metaDataNodes = mainApplicationNode['meta-data'] ?? [];
|
|
72
91
|
// [0] expo.modules.updates.ENABLED
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
const updateEnabledNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.ENABLED');
|
|
93
|
+
if (updateEnabledNode != null) {
|
|
94
|
+
(0, node_assert_1.default)(updateEnabledNode.$ != null);
|
|
95
|
+
updateEnabledNode.$['android:value'] = 'true';
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
metaDataNodes.push({
|
|
99
|
+
$: {
|
|
100
|
+
'android:name': 'expo.modules.updates.ENABLED',
|
|
101
|
+
'android:value': 'true',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
76
105
|
// [1] expo.modules.updates.EXPO_RUNTIME_VERSION
|
|
77
|
-
const updateRuntimeVersionNode =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
const updateRuntimeVersionNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.EXPO_RUNTIME_VERSION');
|
|
107
|
+
if (updateRuntimeVersionNode != null) {
|
|
108
|
+
(0, node_assert_1.default)(updateRuntimeVersionNode.$ != null);
|
|
109
|
+
updateRuntimeVersionNode.$['android:value'] = runtimeVersion;
|
|
81
110
|
}
|
|
82
111
|
else {
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
metaDataNodes.push({
|
|
113
|
+
$: {
|
|
114
|
+
'android:name': 'expo.modules.updates.EXPO_RUNTIME_VERSION',
|
|
115
|
+
'android:value': runtimeVersion,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
85
118
|
}
|
|
86
119
|
// [2] expo.modules.updates.EXPO_UPDATE_URL
|
|
87
|
-
const updateUrlNode =
|
|
88
|
-
child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.EXPO_UPDATE_URL'));
|
|
120
|
+
const updateUrlNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.EXPO_UPDATE_URL');
|
|
89
121
|
const updateUrl = config.updates?.url;
|
|
90
122
|
(0, node_assert_1.default)(updateUrl);
|
|
91
|
-
if (updateUrlNode
|
|
92
|
-
|
|
123
|
+
if (updateUrlNode != null) {
|
|
124
|
+
(0, node_assert_1.default)(updateUrlNode.$ != null);
|
|
125
|
+
updateUrlNode.$['android:value'] = updateUrl;
|
|
93
126
|
}
|
|
94
127
|
else {
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
metaDataNodes.push({
|
|
129
|
+
$: {
|
|
130
|
+
'android:name': 'expo.modules.updates.EXPO_UPDATE_URL',
|
|
131
|
+
'android:value': updateUrl,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
97
134
|
}
|
|
98
135
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Decode the proto-encoded file from `filePath` with the given `protoTypeString` and return as JSON object.
|
|
101
|
-
*/
|
|
102
|
-
async function decodeProtoFile(protoType, filePath) {
|
|
103
|
-
const buffer = await promises_1.default.readFile(filePath);
|
|
104
|
-
const message = await protoType.decode(buffer);
|
|
105
|
-
const json = protoType.toObject(message, {
|
|
106
|
-
longs: String,
|
|
107
|
-
enums: String,
|
|
108
|
-
bytes: String,
|
|
109
|
-
defaults: true,
|
|
110
|
-
arrays: true,
|
|
111
|
-
objects: true,
|
|
112
|
-
oneofs: true,
|
|
113
|
-
});
|
|
114
|
-
return json;
|
|
115
|
-
}
|
|
116
|
-
async function encodeProtoFile(protoType, filePath, json) {
|
|
117
|
-
const updatedMessage = protoType.fromObject(json);
|
|
118
|
-
const updatedBuffer = protoType.encode(updatedMessage).finish();
|
|
119
|
-
await promises_1.default.writeFile(filePath, updatedBuffer);
|
|
120
|
-
}
|
|
121
136
|
/**
|
|
122
137
|
* Recursively find XML nodes that satisfy the given `predicate`.
|
|
123
138
|
*/
|
|
124
|
-
function findXmlNodes(rootNode, predicate) {
|
|
125
|
-
if (rootNode
|
|
139
|
+
function findXmlNodes(rootNodeName, rootNode, predicate) {
|
|
140
|
+
if (rootNode == null) {
|
|
126
141
|
return [];
|
|
127
142
|
}
|
|
128
|
-
if (predicate(rootNode)) {
|
|
143
|
+
if (predicate(rootNodeName, rootNode)) {
|
|
129
144
|
return [rootNode];
|
|
130
145
|
}
|
|
131
|
-
return
|
|
146
|
+
return Object.keys(rootNode)
|
|
147
|
+
.filter((key) => key !== '$' && key !== '_')
|
|
148
|
+
.flatMap((key) => {
|
|
149
|
+
const node = rootNode[key];
|
|
150
|
+
return findXmlNodes(key, node, predicate);
|
|
151
|
+
});
|
|
132
152
|
}
|
|
133
153
|
/**
|
|
134
154
|
* Recursively replace the attribute values in the XML nodes's attributes.
|
|
135
155
|
*/
|
|
136
156
|
function replaceXmlAttributeValue(node, replacer) {
|
|
137
|
-
if (node
|
|
157
|
+
if (node == null) {
|
|
138
158
|
return;
|
|
139
159
|
}
|
|
140
|
-
const attrs = node
|
|
141
|
-
for (const
|
|
142
|
-
|
|
160
|
+
const attrs = node.$ ?? {};
|
|
161
|
+
for (const [attrKey, attrValue] of Object.entries(attrs)) {
|
|
162
|
+
attrs[attrKey] = replacer(attrValue);
|
|
143
163
|
}
|
|
144
|
-
for (const
|
|
145
|
-
|
|
164
|
+
for (const [nodeKey, nodeValue] of Object.entries(node)) {
|
|
165
|
+
if (nodeKey === '$' || nodeKey === '_') {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
replaceXmlAttributeValue(nodeValue, replacer);
|
|
170
|
+
}
|
|
146
171
|
}
|
|
147
172
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Create a new `meta-data` node with the given `name` and `value`.
|
|
150
|
-
* We use the `updateEnabledNode` as the source to clone most of the properties.
|
|
151
|
-
*/
|
|
152
|
-
function createUpdateStringNode(updateEnabledNode, name, value) {
|
|
153
|
-
return {
|
|
154
|
-
...updateEnabledNode,
|
|
155
|
-
element: {
|
|
156
|
-
...(0, utils_1.requireNotNull)(updateEnabledNode.element),
|
|
157
|
-
attribute: [
|
|
158
|
-
{
|
|
159
|
-
namespaceUri: 'http://schemas.android.com/apk/res/android',
|
|
160
|
-
name: 'name',
|
|
161
|
-
value: name,
|
|
162
|
-
source: null,
|
|
163
|
-
resourceId: 16842755,
|
|
164
|
-
compiledItem: null,
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
namespaceUri: 'http://schemas.android.com/apk/res/android',
|
|
168
|
-
name: 'value',
|
|
169
|
-
value,
|
|
170
|
-
source: null,
|
|
171
|
-
resourceId: 16842788,
|
|
172
|
-
compiledItem: null,
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
173
|
//#endregion
|
package/build/cli.js
CHANGED
|
@@ -22,6 +22,9 @@ const program = new commander_1.Command('repack-app')
|
|
|
22
22
|
// iOS signing options
|
|
23
23
|
.option('--signing-identity <identity>', 'Signing identity')
|
|
24
24
|
.option('--provisioning-profile <path>', 'Path to the provisioning profile')
|
|
25
|
+
// export:embed options
|
|
26
|
+
.option('--embed-bundle-assets', 'Whether to execute export:embed to embed new bundle assets')
|
|
27
|
+
.option('--bundle-assets-sourcemap-output <path>', 'Paired with --embed-bundle-assets and generate the sourcemap to the specified path')
|
|
25
28
|
// arguments
|
|
26
29
|
.argument('<project-root>', 'Path to the project root')
|
|
27
30
|
.parse(process.argv);
|
|
@@ -29,6 +32,12 @@ async function runAsync() {
|
|
|
29
32
|
const logger = (0, logger_1.createLogger)({ name: 'repack-app' });
|
|
30
33
|
const platform = program.opts().platform;
|
|
31
34
|
const projectRoot = program.args[0];
|
|
35
|
+
const exportEmbedOptions = program.opts()
|
|
36
|
+
.embedBundleAssets
|
|
37
|
+
? {
|
|
38
|
+
sourcemapOutput: program.opts().bundleAssetsSourcemapOutput,
|
|
39
|
+
}
|
|
40
|
+
: undefined;
|
|
32
41
|
if (platform === 'android') {
|
|
33
42
|
const outputPath = await (0, index_1.repackAppAndroidAsync)({
|
|
34
43
|
platform: program.opts().platform,
|
|
@@ -37,6 +46,7 @@ async function runAsync() {
|
|
|
37
46
|
sourceAppPath: program.opts().sourceApp,
|
|
38
47
|
workingDirectory: program.opts().workingDirectory,
|
|
39
48
|
outputPath: program.opts().output,
|
|
49
|
+
exportEmbedOptions,
|
|
40
50
|
androidSigningOptions: {
|
|
41
51
|
keyStorePath: program.opts().ks,
|
|
42
52
|
keyStorePassword: program.opts().ksPass,
|
|
@@ -49,6 +59,12 @@ async function runAsync() {
|
|
|
49
59
|
logger.info(picocolors_1.default.green(`Updated APK created at ${outputPath}`));
|
|
50
60
|
}
|
|
51
61
|
else if (platform === 'ios') {
|
|
62
|
+
const iosSigningOptions = program.opts().signingIdentity && program.opts().provisioningProfile
|
|
63
|
+
? {
|
|
64
|
+
signingIdentity: program.opts().signingIdentity,
|
|
65
|
+
provisioningProfile: program.opts().provisioningProfile,
|
|
66
|
+
}
|
|
67
|
+
: undefined;
|
|
52
68
|
const options = {
|
|
53
69
|
platform: program.opts().platform,
|
|
54
70
|
projectRoot,
|
|
@@ -56,10 +72,8 @@ async function runAsync() {
|
|
|
56
72
|
sourceAppPath: program.opts().sourceApp,
|
|
57
73
|
workingDirectory: program.opts().workingDirectory,
|
|
58
74
|
outputPath: program.opts().output,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
provisioningProfile: program.opts().provisioningProfile,
|
|
62
|
-
},
|
|
75
|
+
exportEmbedOptions,
|
|
76
|
+
iosSigningOptions,
|
|
63
77
|
logger,
|
|
64
78
|
};
|
|
65
79
|
const outputPath = await (0, index_1.repackAppIosAsync)(options);
|
package/build/expo.d.ts
CHANGED
|
@@ -4,7 +4,18 @@ import type { NormalizedOptions } from './types';
|
|
|
4
4
|
* Generate the app.config file for the Android app.
|
|
5
5
|
*/
|
|
6
6
|
export declare function generateAppConfigAsync(options: NormalizedOptions, config: ExpoConfig): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns whether expo-updates is installed in the project.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isExpoUpdatesInstalled(projectRoot: string): boolean;
|
|
7
11
|
/**
|
|
8
12
|
* Resolve the `runtimeVersion` for expo-updates.
|
|
9
13
|
*/
|
|
10
14
|
export declare function resolveRuntimeVersionAsync(options: NormalizedOptions, config: ExpoConfig): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Generate JS bundle and assets for the app.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateBundleAssetsAsync(expoConfig: ExpoConfig, options: NormalizedOptions): Promise<{
|
|
19
|
+
bundleOutputPath: string;
|
|
20
|
+
assetRoot: string;
|
|
21
|
+
}>;
|
package/build/expo.js
CHANGED
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.resolveRuntimeVersionAsync = exports.generateAppConfigAsync = void 0;
|
|
6
|
+
exports.generateBundleAssetsAsync = exports.resolveRuntimeVersionAsync = exports.isExpoUpdatesInstalled = exports.generateAppConfigAsync = void 0;
|
|
7
|
+
const config_1 = require("@expo/config");
|
|
7
8
|
const steps_1 = require("@expo/steps");
|
|
8
9
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -18,6 +19,14 @@ async function generateAppConfigAsync(options, config) {
|
|
|
18
19
|
return appConfigPath;
|
|
19
20
|
}
|
|
20
21
|
exports.generateAppConfigAsync = generateAppConfigAsync;
|
|
22
|
+
/**
|
|
23
|
+
* Returns whether expo-updates is installed in the project.
|
|
24
|
+
*/
|
|
25
|
+
function isExpoUpdatesInstalled(projectRoot) {
|
|
26
|
+
const packageJson = (0, config_1.getPackageJson)(projectRoot);
|
|
27
|
+
return !!(packageJson.dependencies && 'expo-updates' in packageJson.dependencies);
|
|
28
|
+
}
|
|
29
|
+
exports.isExpoUpdatesInstalled = isExpoUpdatesInstalled;
|
|
21
30
|
/**
|
|
22
31
|
* Resolve the `runtimeVersion` for expo-updates.
|
|
23
32
|
*/
|
|
@@ -32,3 +41,67 @@ async function resolveRuntimeVersionAsync(options, config) {
|
|
|
32
41
|
return runtimeVersion ?? config.version ?? '1.0.0';
|
|
33
42
|
}
|
|
34
43
|
exports.resolveRuntimeVersionAsync = resolveRuntimeVersionAsync;
|
|
44
|
+
/**
|
|
45
|
+
* Generate JS bundle and assets for the app.
|
|
46
|
+
*/
|
|
47
|
+
async function generateBundleAssetsAsync(expoConfig, options) {
|
|
48
|
+
const { projectRoot, platform, workingDirectory } = options;
|
|
49
|
+
const bundleAssetRoot = node_path_1.default.resolve(workingDirectory, 'bundles');
|
|
50
|
+
await promises_1.default.mkdir(bundleAssetRoot, { recursive: true });
|
|
51
|
+
// [0] Resolve entry point
|
|
52
|
+
const { stdout: entryFile } = await (0, steps_1.spawnAsync)('node', ['-e', "require('expo/scripts/resolveAppEntry')", projectRoot, platform, 'absolute'], { cwd: projectRoot });
|
|
53
|
+
// [1] Execute export:embed
|
|
54
|
+
const isEnableHermes = isEnableHermesManaged(expoConfig, platform);
|
|
55
|
+
const bundleFileName = platform === 'android' ? 'index.android.bundle' : 'main.bundle';
|
|
56
|
+
const bundleOutputPath = node_path_1.default.join(bundleAssetRoot, bundleFileName);
|
|
57
|
+
const assetRoot = node_path_1.default.join(bundleAssetRoot, 'assets');
|
|
58
|
+
const exportEmbedArgs = [
|
|
59
|
+
'expo',
|
|
60
|
+
'export:embed',
|
|
61
|
+
'--platform',
|
|
62
|
+
platform,
|
|
63
|
+
'--entry-file',
|
|
64
|
+
entryFile.trim(),
|
|
65
|
+
'--bundle-output',
|
|
66
|
+
node_path_1.default.join(bundleAssetRoot, bundleFileName),
|
|
67
|
+
'--assets-dest',
|
|
68
|
+
node_path_1.default.join(bundleAssetRoot, 'assets'),
|
|
69
|
+
'--dev',
|
|
70
|
+
'false',
|
|
71
|
+
'--reset-cache',
|
|
72
|
+
];
|
|
73
|
+
if (isEnableHermes) {
|
|
74
|
+
exportEmbedArgs.push('--minify', 'false');
|
|
75
|
+
exportEmbedArgs.push('--bundle-bytecode', 'true');
|
|
76
|
+
exportEmbedArgs.push('--unstable-transform-profile', 'hermes');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
exportEmbedArgs.push('--minify', 'true');
|
|
80
|
+
}
|
|
81
|
+
if (options.exportEmbedOptions?.sourcemapOutput) {
|
|
82
|
+
exportEmbedArgs.push('--sourcemap-output', options.exportEmbedOptions.sourcemapOutput);
|
|
83
|
+
}
|
|
84
|
+
await (0, steps_1.spawnAsync)('npx', exportEmbedArgs, options.verbose
|
|
85
|
+
? { logger: options.logger, stdio: 'pipe', cwd: projectRoot }
|
|
86
|
+
: { cwd: projectRoot });
|
|
87
|
+
return {
|
|
88
|
+
bundleOutputPath,
|
|
89
|
+
assetRoot,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
exports.generateBundleAssetsAsync = generateBundleAssetsAsync;
|
|
93
|
+
/**
|
|
94
|
+
* Determine whether Hermes is enabled for the project.
|
|
95
|
+
*/
|
|
96
|
+
function isEnableHermesManaged(expoConfig, platform) {
|
|
97
|
+
switch (platform) {
|
|
98
|
+
case 'android': {
|
|
99
|
+
return (expoConfig.android?.jsEngine ?? expoConfig.jsEngine) !== 'jsc';
|
|
100
|
+
}
|
|
101
|
+
case 'ios': {
|
|
102
|
+
return (expoConfig.ios?.jsEngine ?? expoConfig.jsEngine) !== 'jsc';
|
|
103
|
+
}
|
|
104
|
+
default:
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
17
|
exports.repackAppIosAsync = exports.repackAppAndroidAsync = void 0;
|
|
4
18
|
var android_1 = require("./android");
|
|
5
19
|
Object.defineProperty(exports, "repackAppAndroidAsync", { enumerable: true, get: function () { return android_1.repackAppAndroidAsync; } });
|
|
6
20
|
var ios_1 = require("./ios");
|
|
7
21
|
Object.defineProperty(exports, "repackAppIosAsync", { enumerable: true, get: function () { return ios_1.repackAppIosAsync; } });
|
|
22
|
+
__exportStar(require("./types"), exports);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ExpoConfig } from '@expo/config';
|
|
2
|
-
import type { NormalizedOptions } from '../types';
|
|
2
|
+
import type { IosSigningOptions, NormalizedOptions } from '../types';
|
|
3
3
|
/**
|
|
4
4
|
* Unzip the IPA file.
|
|
5
5
|
*/
|
|
@@ -12,3 +12,7 @@ export declare function updateFilesAsync(config: ExpoConfig, appWorkingDirectory
|
|
|
12
12
|
* From the given working .app directory, create a new .ipa file.
|
|
13
13
|
*/
|
|
14
14
|
export declare function createIpaAsync(options: NormalizedOptions, appWorkingDirectory: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a Fastfile lane for resigning an IPA.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createResignLane(laneName: string, ipaPath: string, signingOptions: IosSigningOptions): string;
|
package/build/ios/build-tools.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createIpaAsync = exports.updateFilesAsync = exports.unzipIpaAsync = void 0;
|
|
6
|
+
exports.createResignLane = exports.createIpaAsync = exports.updateFilesAsync = exports.unzipIpaAsync = void 0;
|
|
7
7
|
const steps_1 = require("@expo/steps");
|
|
8
8
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
9
9
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
@@ -40,10 +40,45 @@ async function createIpaAsync(options, appWorkingDirectory) {
|
|
|
40
40
|
const { workingDirectory } = options;
|
|
41
41
|
await promises_1.default.mkdir(node_path_1.default.join(workingDirectory, 'Payload'), { recursive: true });
|
|
42
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.
|
|
43
|
+
const outputIpaPath = node_path_1.default.resolve(workingDirectory, 'repacked.ipa');
|
|
44
44
|
await (0, steps_1.spawnAsync)('zip', ['-r', outputIpaPath, 'Payload'], options.verbose
|
|
45
45
|
? { logger: options.logger, stdio: 'pipe', cwd: workingDirectory }
|
|
46
46
|
: { cwd: workingDirectory });
|
|
47
47
|
return outputIpaPath;
|
|
48
48
|
}
|
|
49
49
|
exports.createIpaAsync = createIpaAsync;
|
|
50
|
+
/**
|
|
51
|
+
* Create a Fastfile lane for resigning an IPA.
|
|
52
|
+
*/
|
|
53
|
+
function createResignLane(laneName, ipaPath, signingOptions) {
|
|
54
|
+
const keychainPath = signingOptions.keychainPath
|
|
55
|
+
? `keychain_path: "${signingOptions.keychainPath}",`
|
|
56
|
+
: '';
|
|
57
|
+
let provisioningProfile;
|
|
58
|
+
if (typeof signingOptions.provisioningProfile === 'string') {
|
|
59
|
+
provisioningProfile = `provisioning_profile: "${signingOptions.provisioningProfile}",`;
|
|
60
|
+
}
|
|
61
|
+
else if (typeof signingOptions.provisioningProfile === 'object' &&
|
|
62
|
+
!Array.isArray(signingOptions.provisioningProfile)) {
|
|
63
|
+
let profileHash = '';
|
|
64
|
+
for (const [key, value] of Object.entries(signingOptions.provisioningProfile)) {
|
|
65
|
+
profileHash += ` "${key}" => "${value}",\n`;
|
|
66
|
+
}
|
|
67
|
+
provisioningProfile = `provisioning_profile: {\n${profileHash}\n },`;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
provisioningProfile = '';
|
|
71
|
+
}
|
|
72
|
+
const fastfileContents = `\
|
|
73
|
+
lane :${laneName} do
|
|
74
|
+
resign(
|
|
75
|
+
ipa: "${ipaPath}",
|
|
76
|
+
signing_identity: "${signingOptions.signingIdentity}",
|
|
77
|
+
${keychainPath}
|
|
78
|
+
${provisioningProfile}
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
`;
|
|
82
|
+
return fastfileContents.replace(/^\s*[\r\n]/gm, '');
|
|
83
|
+
}
|
|
84
|
+
exports.createResignLane = createResignLane;
|
package/build/ios/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export declare function repackAppIosAsync(_options: Options): Promise<string>;
|
|
|
6
6
|
/**
|
|
7
7
|
* Resign an IPA by fastlane.
|
|
8
8
|
*/
|
|
9
|
-
export declare function resignIpaAsync(ipaPath: string, signingOptions: IosSigningOptions,
|
|
9
|
+
export declare function resignIpaAsync(ipaPath: string, signingOptions: IosSigningOptions, _options: Options): Promise<void>;
|
package/build/ios/index.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.resignIpaAsync = exports.repackAppIosAsync = void 0;
|
|
7
7
|
const config_1 = require("@expo/config");
|
|
8
8
|
const steps_1 = require("@expo/steps");
|
|
9
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
9
10
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
@@ -24,14 +25,31 @@ async function repackAppIosAsync(_options) {
|
|
|
24
25
|
isPublicConfig: true,
|
|
25
26
|
skipSDKVersionRequirement: true,
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
+
(0, node_assert_1.default)(exp.ios?.bundleIdentifier, 'Expected app ID (`ios.bundleIdentifier`) to be defined in app.json');
|
|
29
|
+
const updatesRuntimeVersion = (0, expo_1.isExpoUpdatesInstalled)(options.projectRoot)
|
|
30
|
+
? await (0, expo_1.resolveRuntimeVersionAsync)(options, exp)
|
|
31
|
+
: null;
|
|
28
32
|
logger.info(picocolors_1.default.dim(`Resolved runtime version: ${updatesRuntimeVersion}`));
|
|
29
33
|
logger.info(`Unzipping IPA`);
|
|
30
34
|
let appWorkingDirectory = await (0, build_tools_1.unzipIpaAsync)(options);
|
|
31
35
|
appWorkingDirectory = await (0, build_tools_1.updateFilesAsync)(exp, appWorkingDirectory);
|
|
36
|
+
const infoPlistPath = node_path_1.default.join(appWorkingDirectory, 'Info.plist');
|
|
37
|
+
const originalAppId = await (0, resources_1.queryAppIdFromPlistAsync)(infoPlistPath, options);
|
|
32
38
|
logger.info(`Finished unzipping IPA ✅`);
|
|
39
|
+
if (options.exportEmbedOptions != null) {
|
|
40
|
+
logger.info(`Generating bundle`);
|
|
41
|
+
const { assetRoot, bundleOutputPath } = await (0, expo_1.generateBundleAssetsAsync)(exp, options);
|
|
42
|
+
logger.info(`Finished generating bundle ✅`);
|
|
43
|
+
logger.info(`Adding bundled resources`);
|
|
44
|
+
await (0, resources_1.addBundleAssetsAsync)({
|
|
45
|
+
appWorkingDirectory,
|
|
46
|
+
assetRoot,
|
|
47
|
+
bundleOutputPath,
|
|
48
|
+
});
|
|
49
|
+
logger.info(`Finished adding bundled resources ✅`);
|
|
50
|
+
}
|
|
33
51
|
logger.info(`Updating Info.plist`);
|
|
34
|
-
await (0, resources_1.updateInfoPlistAsync)(exp,
|
|
52
|
+
await (0, resources_1.updateInfoPlistAsync)({ config: exp, infoPlistPath, originalAppId, options });
|
|
35
53
|
logger.info(`Finished updating Info.plist ✅`);
|
|
36
54
|
logger.info(`Updating Expo.plist`);
|
|
37
55
|
await (0, resources_1.updateExpoPlistAsync)(exp, node_path_1.default.join(appWorkingDirectory, 'Expo.plist'), updatesRuntimeVersion, options);
|
|
@@ -51,7 +69,7 @@ async function repackAppIosAsync(_options) {
|
|
|
51
69
|
await promises_1.default.rename(outputIpa, options.outputPath);
|
|
52
70
|
if (!options.skipWorkingDirCleanup) {
|
|
53
71
|
try {
|
|
54
|
-
await promises_1.default.
|
|
72
|
+
await promises_1.default.rm(workingDirectory, { recursive: true });
|
|
55
73
|
}
|
|
56
74
|
catch { }
|
|
57
75
|
}
|
|
@@ -61,17 +79,17 @@ exports.repackAppIosAsync = repackAppIosAsync;
|
|
|
61
79
|
/**
|
|
62
80
|
* Resign an IPA by fastlane.
|
|
63
81
|
*/
|
|
64
|
-
async function resignIpaAsync(ipaPath, signingOptions,
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
async function resignIpaAsync(ipaPath, signingOptions, _options) {
|
|
83
|
+
const options = await (0, utils_1.normalizeOptionAsync)(_options);
|
|
84
|
+
const resignWorkingDirectory = node_path_1.default.join(options.workingDirectory, 'fastlane');
|
|
85
|
+
await promises_1.default.mkdir(resignWorkingDirectory, { recursive: true });
|
|
86
|
+
const fastfileContents = (0, build_tools_1.createResignLane)('resign_ipa', ipaPath, signingOptions);
|
|
87
|
+
await promises_1.default.writeFile(node_path_1.default.join(resignWorkingDirectory, 'Fastfile'), fastfileContents);
|
|
88
|
+
await (0, steps_1.spawnAsync)('fastlane', ['resign_ipa'], {
|
|
89
|
+
logger: options.logger,
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
cwd: resignWorkingDirectory,
|
|
92
|
+
env: options.env,
|
|
93
|
+
});
|
|
76
94
|
}
|
|
77
95
|
exports.resignIpaAsync = resignIpaAsync;
|