@expo/repack-app 0.0.6 → 0.1.1

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.
@@ -3,176 +3,170 @@ 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.updateResourcesAsync = updateResourcesAsync;
7
+ exports.updateAndroidManifestAsync = updateAndroidManifestAsync;
8
+ exports.parseXmlFileAsync = parseXmlFileAsync;
9
+ exports.buildXmlFileAsync = buildXmlFileAsync;
10
+ exports.queryAppIdFromManifestAsync = queryAppIdFromManifestAsync;
7
11
  const node_assert_1 = __importDefault(require("node:assert"));
8
12
  const promises_1 = __importDefault(require("node:fs/promises"));
9
13
  const node_path_1 = __importDefault(require("node:path"));
10
- const protobufjs_1 = __importDefault(require("protobufjs"));
14
+ const xml2js_1 = require("xml2js");
11
15
  const utils_1 = require("../utils");
12
16
  /**
13
- * Update resources inside the **resources.pb** file.
17
+ * Update resources in the decoded APK.
14
18
  */
15
- async function updateResourcesAsync(config, resourcesPbFilePath) {
16
- const root = await protobufjs_1.default.load(node_path_1.default.join(__dirname, '../../assets', 'Resources.proto'));
17
- const resourceTableType = root.lookupType('aapt.pb.ResourceTable');
18
- const resourceTable = await decodeProtoFile(resourceTableType, resourcesPbFilePath);
19
- // [0] Update the package name
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');
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);
19
+ async function updateResourcesAsync({ config, decodedApkRoot, }) {
20
+ // [0] Update the `app_name` in **res/values/strings.xml**.
21
+ const stringsXmlPath = node_path_1.default.join(decodedApkRoot, 'res', 'values', 'strings.xml');
22
+ let stringsXml = await promises_1.default.readFile(stringsXmlPath, 'utf8');
23
+ stringsXml = stringsXml.replace(/(<string name="app_name">)(.+)(<\/string>)/, `$1${config.name}$3`);
24
+ await promises_1.default.writeFile(stringsXmlPath, stringsXml);
30
25
  }
31
- exports.updateResourcesAsync = updateResourcesAsync;
32
26
  /**
33
27
  * Update the proto-based AndroidManiest.xml.
34
28
  */
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'));
37
- const xmlNodeType = root.lookupType('aapt.pb.XmlNode');
38
- const rootNode = await decodeProtoFile(xmlNodeType, androidManiestFilePath);
29
+ async function updateAndroidManifestAsync({ config, androidManiestXml, dexClasses, originalAppId, updatesRuntimeVersion, }) {
39
30
  // [0] Update the package name
40
- replaceXmlAttributeValue(rootNode, (value) => value.replace(/dev\.expo\.templatedefault\.appid/g, (0, utils_1.requireNotNull)(config.android?.package)));
31
+ const appId = (0, utils_1.requireNotNull)(config.android?.package);
32
+ androidManiestXml.manifest.$.package = appId;
33
+ replaceXmlAttributeValue(androidManiestXml, (value) => {
34
+ if (value.startsWith(originalAppId) && !dexClasses.has(value)) {
35
+ return value.replace(originalAppId, appId);
36
+ }
37
+ return value;
38
+ });
41
39
  // [1] Update the scheme in the intent-filters
42
- const intentFilterViewActionNodes = findXmlNodes(rootNode, (node) => node.element?.name === 'intent-filter' &&
43
- findXmlNodes(node, (node) => node.element?.name === 'action' &&
44
- node.element?.attribute[0]?.value === 'android.intent.action.VIEW').length > 0);
40
+ const intentFilterViewActionNodes = findXmlNodes('manifest', androidManiestXml.manifest, (nodeName, node) => nodeName === 'intent-filter' &&
41
+ findXmlNodes(nodeName, node, (nodeName, node) => nodeName === 'action' && node[0].$?.['android:name'] === 'android.intent.action.VIEW').length > 0);
45
42
  const firstScheme = Array.isArray(config.scheme) ? config.scheme[0] : config.scheme;
46
43
  for (const node of intentFilterViewActionNodes) {
47
44
  replaceXmlAttributeValue(node, (value) => value
48
45
  // scheme in app.json
49
46
  .replace(/^myapp$/g, (0, utils_1.requireNotNull)(firstScheme))
50
47
  // android.package in app.json
51
- .replace(/^dev\.expo\.templatedefault$/g, (0, utils_1.requireNotNull)(config.android?.package))
48
+ .replace(originalAppId, appId)
52
49
  // default scheme generated from slug in app.json
53
- .replace(/^exp\+expo-template-default$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`));
50
+ .replace(/^exp\+.+$/g, `exp+${(0, utils_1.requireNotNull)(config.slug)}`));
54
51
  }
55
52
  // [2] expo-updates configuration
56
- const mainApplicationNode = findXmlNodes(rootNode, (node) => node.element?.name === 'application' &&
57
- node.element?.attribute.find((attr) => attr.name === 'name' && attr.value.endsWith('.MainApplication')) != null)[0];
58
- (0, node_assert_1.default)(mainApplicationNode != null, 'Expected application node to be present');
59
- mutateExpoUpdatesConfigAsync(mainApplicationNode, config, updatesRuntimeVersion);
60
- await encodeProtoFile(xmlNodeType, androidManiestFilePath, rootNode);
53
+ const mainApplicationNode = androidManiestXml.manifest.application[0];
54
+ (0, node_assert_1.default)(mainApplicationNode.$?.['android:name']?.endsWith('.MainApplication'), 'Expected application node to be named as MainApplication');
55
+ if (updatesRuntimeVersion) {
56
+ mutateExpoUpdatesConfigAsync(mainApplicationNode, config, updatesRuntimeVersion);
57
+ }
58
+ }
59
+ /**
60
+ * Parse the XML file.
61
+ */
62
+ async function parseXmlFileAsync(filePath) {
63
+ const contents = await promises_1.default.readFile(filePath, 'utf8');
64
+ const parser = new xml2js_1.Parser();
65
+ const xmlJs = await parser.parseStringPromise(contents);
66
+ return xmlJs;
67
+ }
68
+ /**
69
+ * Build the XML file from the given node.
70
+ */
71
+ async function buildXmlFileAsync(rootNode, filePath) {
72
+ const builder = new xml2js_1.Builder();
73
+ const xmlString = builder.buildObject(rootNode);
74
+ await promises_1.default.writeFile(filePath, xmlString);
75
+ }
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;
61
83
  }
62
- exports.updateAndroidManifestAsync = updateAndroidManifestAsync;
63
84
  //#region Internals
64
85
  /**
65
86
  * Update the `expo-updates` configuration in the Android project.
66
87
  */
67
88
  function mutateExpoUpdatesConfigAsync(mainApplicationNode, config, runtimeVersion) {
68
- const updateEnabledNodeIndex = mainApplicationNode.element?.child.findIndex((child) => child.element?.name === 'meta-data' &&
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]);
89
+ const metaDataNodes = mainApplicationNode['meta-data'] ?? [];
72
90
  // [0] expo.modules.updates.ENABLED
73
- const updateEnabledPrimValue = updateEnabledNode.element?.attribute?.[1]?.compiledItem?.prim;
74
- (0, node_assert_1.default)(updateEnabledPrimValue != null, 'Expected updateEnabledPrimValue to be present');
75
- updateEnabledPrimValue.booleanValue = true;
91
+ const updateEnabledNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.ENABLED');
92
+ if (updateEnabledNode != null) {
93
+ (0, node_assert_1.default)(updateEnabledNode.$ != null);
94
+ updateEnabledNode.$['android:value'] = 'true';
95
+ }
96
+ else {
97
+ metaDataNodes.push({
98
+ $: {
99
+ 'android:name': 'expo.modules.updates.ENABLED',
100
+ 'android:value': 'true',
101
+ },
102
+ });
103
+ }
76
104
  // [1] expo.modules.updates.EXPO_RUNTIME_VERSION
77
- const updateRuntimeVersionNode = mainApplicationNode.element?.child.find((child) => child.element?.name === 'meta-data' &&
78
- child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.EXPO_RUNTIME_VERSION'));
79
- if (updateRuntimeVersionNode == null) {
80
- mainApplicationNode.element?.child.splice(updateEnabledNodeIndex + 1, 0, createUpdateStringNode(updateEnabledNode, 'expo.modules.updates.EXPO_RUNTIME_VERSION', runtimeVersion));
105
+ const updateRuntimeVersionNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.EXPO_RUNTIME_VERSION');
106
+ if (updateRuntimeVersionNode != null) {
107
+ (0, node_assert_1.default)(updateRuntimeVersionNode.$ != null);
108
+ updateRuntimeVersionNode.$['android:value'] = runtimeVersion;
81
109
  }
82
110
  else {
83
- (0, node_assert_1.default)(updateRuntimeVersionNode.element != null);
84
- updateRuntimeVersionNode.element.attribute[1].value = runtimeVersion;
111
+ metaDataNodes.push({
112
+ $: {
113
+ 'android:name': 'expo.modules.updates.EXPO_RUNTIME_VERSION',
114
+ 'android:value': runtimeVersion,
115
+ },
116
+ });
85
117
  }
86
118
  // [2] expo.modules.updates.EXPO_UPDATE_URL
87
- const updateUrlNode = mainApplicationNode.element?.child.find((child) => child.element?.name === 'meta-data' &&
88
- child.element?.attribute.find((attr) => attr.name === 'name' && attr.value === 'expo.modules.updates.EXPO_UPDATE_URL'));
119
+ const updateUrlNode = metaDataNodes.find((node) => node.$?.['android:name'] === 'expo.modules.updates.EXPO_UPDATE_URL');
89
120
  const updateUrl = config.updates?.url;
90
121
  (0, node_assert_1.default)(updateUrl);
91
- if (updateUrlNode == null) {
92
- mainApplicationNode.element?.child.splice(updateEnabledNodeIndex + 1, 0, createUpdateStringNode(updateEnabledNode, 'expo.modules.updates.EXPO_UPDATE_URL', updateUrl));
122
+ if (updateUrlNode != null) {
123
+ (0, node_assert_1.default)(updateUrlNode.$ != null);
124
+ updateUrlNode.$['android:value'] = updateUrl;
93
125
  }
94
126
  else {
95
- (0, node_assert_1.default)(updateUrlNode.element != null);
96
- updateUrlNode.element.attribute[1].value = updateUrl;
127
+ metaDataNodes.push({
128
+ $: {
129
+ 'android:name': 'expo.modules.updates.EXPO_UPDATE_URL',
130
+ 'android:value': updateUrl,
131
+ },
132
+ });
97
133
  }
98
134
  }
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
135
  /**
122
136
  * Recursively find XML nodes that satisfy the given `predicate`.
123
137
  */
124
- function findXmlNodes(rootNode, predicate) {
125
- if (rootNode.element == null) {
138
+ function findXmlNodes(rootNodeName, rootNode, predicate) {
139
+ if (rootNode == null) {
126
140
  return [];
127
141
  }
128
- if (predicate(rootNode)) {
142
+ if (predicate(rootNodeName, rootNode)) {
129
143
  return [rootNode];
130
144
  }
131
- return rootNode.element.child.flatMap((child) => findXmlNodes(child, predicate));
145
+ return Object.keys(rootNode)
146
+ .filter((key) => key !== '$' && key !== '_')
147
+ .flatMap((key) => {
148
+ const node = rootNode[key];
149
+ return findXmlNodes(key, node, predicate);
150
+ });
132
151
  }
133
152
  /**
134
153
  * Recursively replace the attribute values in the XML nodes's attributes.
135
154
  */
136
155
  function replaceXmlAttributeValue(node, replacer) {
137
- if (node.element == null) {
156
+ if (node == null) {
138
157
  return;
139
158
  }
140
- const attrs = node.element.attribute;
141
- for (const attr of attrs) {
142
- attr.value = replacer(attr.value);
159
+ const attrs = node.$ ?? {};
160
+ for (const [attrKey, attrValue] of Object.entries(attrs)) {
161
+ attrs[attrKey] = replacer(attrValue);
143
162
  }
144
- for (const child of node.element.child) {
145
- replaceXmlAttributeValue(child, replacer);
163
+ for (const [nodeKey, nodeValue] of Object.entries(node)) {
164
+ if (nodeKey === '$' || nodeKey === '_') {
165
+ continue;
166
+ }
167
+ else {
168
+ replaceXmlAttributeValue(nodeValue, replacer);
169
+ }
146
170
  }
147
171
  }
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
172
  //#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
- iosSigningOptions: {
60
- signingIdentity: program.opts().signingIdentity,
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,23 @@ 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
+ * A wrapper around `@expo/config`'s `getConfig` function
17
+ * that uses the `@expo/config` package from the project's transitive dependencies.
18
+ */
19
+ export declare function getExpoConfig(projectRoot: string, options: Parameters<typeof import('@expo/config').getConfig>[1]): ReturnType<typeof import('@expo/config').getConfig>;
20
+ /**
21
+ * Generate JS bundle and assets for the app.
22
+ */
23
+ export declare function generateBundleAssetsAsync(expoConfig: ExpoConfig, options: NormalizedOptions): Promise<{
24
+ bundleOutputPath: string;
25
+ assetRoot: string;
26
+ }>;
package/build/expo.js CHANGED
@@ -3,8 +3,13 @@ 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.generateAppConfigAsync = generateAppConfigAsync;
7
+ exports.isExpoUpdatesInstalled = isExpoUpdatesInstalled;
8
+ exports.resolveRuntimeVersionAsync = resolveRuntimeVersionAsync;
9
+ exports.getExpoConfig = getExpoConfig;
10
+ exports.generateBundleAssetsAsync = generateBundleAssetsAsync;
7
11
  const steps_1 = require("@expo/steps");
12
+ const node_assert_1 = __importDefault(require("node:assert"));
8
13
  const promises_1 = __importDefault(require("node:fs/promises"));
9
14
  const node_path_1 = __importDefault(require("node:path"));
10
15
  const resolve_from_1 = __importDefault(require("resolve-from"));
@@ -17,7 +22,16 @@ async function generateAppConfigAsync(options, config) {
17
22
  await promises_1.default.writeFile(appConfigPath, JSON.stringify(config));
18
23
  return appConfigPath;
19
24
  }
20
- exports.generateAppConfigAsync = generateAppConfigAsync;
25
+ /**
26
+ * Returns whether expo-updates is installed in the project.
27
+ */
28
+ function isExpoUpdatesInstalled(projectRoot) {
29
+ const expoConfigPath = resolveExpoConfigPath(projectRoot);
30
+ (0, node_assert_1.default)(expoConfigPath, `Failed to resolve '@expo/config' package in the project`);
31
+ const { getPackageJson } = require(expoConfigPath);
32
+ const packageJson = getPackageJson(projectRoot);
33
+ return !!(packageJson.dependencies && 'expo-updates' in packageJson.dependencies);
34
+ }
21
35
  /**
22
36
  * Resolve the `runtimeVersion` for expo-updates.
23
37
  */
@@ -31,4 +45,86 @@ async function resolveRuntimeVersionAsync(options, config) {
31
45
  const runtimeVersion = JSON.parse(proc.stdout).runtimeVersion;
32
46
  return runtimeVersion ?? config.version ?? '1.0.0';
33
47
  }
34
- exports.resolveRuntimeVersionAsync = resolveRuntimeVersionAsync;
48
+ /**
49
+ * Resolves the path to the `@expo/config` package in the project.
50
+ */
51
+ function resolveExpoConfigPath(projectRoot) {
52
+ const expoPackageRoot = resolve_from_1.default.silent(projectRoot, 'expo/package.json');
53
+ if (expoPackageRoot) {
54
+ return resolve_from_1.default.silent(node_path_1.default.dirname(expoPackageRoot), '@expo/config') ?? null;
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * A wrapper around `@expo/config`'s `getConfig` function
60
+ * that uses the `@expo/config` package from the project's transitive dependencies.
61
+ */
62
+ function getExpoConfig(projectRoot, options) {
63
+ const expoConfigPath = resolveExpoConfigPath(projectRoot);
64
+ (0, node_assert_1.default)(expoConfigPath, `Failed to resolve '@expo/config' package in the project`);
65
+ const { getConfig } = require(expoConfigPath);
66
+ return getConfig(projectRoot, options);
67
+ }
68
+ /**
69
+ * Generate JS bundle and assets for the app.
70
+ */
71
+ async function generateBundleAssetsAsync(expoConfig, options) {
72
+ const { projectRoot, platform, workingDirectory } = options;
73
+ const bundleAssetRoot = node_path_1.default.resolve(workingDirectory, 'bundles');
74
+ await promises_1.default.mkdir(bundleAssetRoot, { recursive: true });
75
+ // [0] Resolve entry point
76
+ const { stdout: entryFile } = await (0, steps_1.spawnAsync)('node', ['-e', "require('expo/scripts/resolveAppEntry')", projectRoot, platform, 'absolute'], { cwd: projectRoot });
77
+ // [1] Execute export:embed
78
+ const isEnableHermes = isEnableHermesManaged(expoConfig, platform);
79
+ const bundleFileName = platform === 'android' ? 'index.android.bundle' : 'main.bundle';
80
+ const bundleOutputPath = node_path_1.default.join(bundleAssetRoot, bundleFileName);
81
+ const assetRoot = node_path_1.default.join(bundleAssetRoot, 'assets');
82
+ const exportEmbedArgs = [
83
+ 'expo',
84
+ 'export:embed',
85
+ '--platform',
86
+ platform,
87
+ '--entry-file',
88
+ entryFile.trim(),
89
+ '--bundle-output',
90
+ node_path_1.default.join(bundleAssetRoot, bundleFileName),
91
+ '--assets-dest',
92
+ node_path_1.default.join(bundleAssetRoot, 'assets'),
93
+ '--dev',
94
+ 'false',
95
+ '--reset-cache',
96
+ ];
97
+ if (isEnableHermes) {
98
+ exportEmbedArgs.push('--minify', 'false');
99
+ exportEmbedArgs.push('--bundle-bytecode', 'true');
100
+ exportEmbedArgs.push('--unstable-transform-profile', 'hermes');
101
+ }
102
+ else {
103
+ exportEmbedArgs.push('--minify', 'true');
104
+ }
105
+ if (options.exportEmbedOptions?.sourcemapOutput) {
106
+ exportEmbedArgs.push('--sourcemap-output', options.exportEmbedOptions.sourcemapOutput);
107
+ }
108
+ await (0, steps_1.spawnAsync)('npx', exportEmbedArgs, options.verbose
109
+ ? { logger: options.logger, stdio: 'pipe', cwd: projectRoot }
110
+ : { cwd: projectRoot });
111
+ return {
112
+ bundleOutputPath,
113
+ assetRoot,
114
+ };
115
+ }
116
+ /**
117
+ * Determine whether Hermes is enabled for the project.
118
+ */
119
+ function isEnableHermesManaged(expoConfig, platform) {
120
+ switch (platform) {
121
+ case 'android': {
122
+ return (expoConfig.android?.jsEngine ?? expoConfig.jsEngine) !== 'jsc';
123
+ }
124
+ case 'ios': {
125
+ return (expoConfig.ios?.jsEngine ?? expoConfig.jsEngine) !== 'jsc';
126
+ }
127
+ default:
128
+ return false;
129
+ }
130
+ }
package/build/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { repackAppAndroidAsync } from './android';
2
2
  export { repackAppIosAsync } from './ios';
3
+ export * from './types';
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;
@@ -3,7 +3,10 @@ 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.unzipIpaAsync = unzipIpaAsync;
7
+ exports.updateFilesAsync = updateFilesAsync;
8
+ exports.createIpaAsync = createIpaAsync;
9
+ exports.createResignLane = createResignLane;
7
10
  const steps_1 = require("@expo/steps");
8
11
  const node_assert_1 = __importDefault(require("node:assert"));
9
12
  const promises_1 = __importDefault(require("node:fs/promises"));
@@ -19,7 +22,6 @@ async function unzipIpaAsync(options) {
19
22
  (0, node_assert_1.default)(await (0, utils_1.directoryExistsAsync)(appWorkingDirectory));
20
23
  return appWorkingDirectory;
21
24
  }
22
- exports.unzipIpaAsync = unzipIpaAsync;
23
25
  /**
24
26
  * Update some binary files.
25
27
  */
@@ -32,7 +34,6 @@ async function updateFilesAsync(config, appWorkingDirectory) {
32
34
  await promises_1.default.rename(node_path_1.default.join(newAppWorkingDirectory, 'HelloWorld'), node_path_1.default.join(newAppWorkingDirectory, config.name));
33
35
  return newAppWorkingDirectory;
34
36
  }
35
- exports.updateFilesAsync = updateFilesAsync;
36
37
  /**
37
38
  * From the given working .app directory, create a new .ipa file.
38
39
  */
@@ -40,10 +41,43 @@ async function createIpaAsync(options, appWorkingDirectory) {
40
41
  const { workingDirectory } = options;
41
42
  await promises_1.default.mkdir(node_path_1.default.join(workingDirectory, 'Payload'), { recursive: true });
42
43
  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
+ const outputIpaPath = node_path_1.default.resolve(workingDirectory, 'repacked.ipa');
44
45
  await (0, steps_1.spawnAsync)('zip', ['-r', outputIpaPath, 'Payload'], options.verbose
45
46
  ? { logger: options.logger, stdio: 'pipe', cwd: workingDirectory }
46
47
  : { cwd: workingDirectory });
47
48
  return outputIpaPath;
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
+ }
@@ -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, options: Options): Promise<void>;
9
+ export declare function resignIpaAsync(ipaPath: string, signingOptions: IosSigningOptions, _options: Options): Promise<void>;