Skip to content

Commit 6f898c3

Browse files
authored
feat: add --app flag to sign:ios to support replacing js bundle in APP files (#429)
* feat: add '--app' flag to sign:ios to support replacing js bundle in APP files * changeset
1 parent 24d6e3a commit 6f898c3

File tree

6 files changed

+149
-29
lines changed

6 files changed

+149
-29
lines changed

.changeset/quick-pianos-draw.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@rnef/platform-apple-helpers': patch
3+
'@rnef/platform-android': patch
4+
'@rnef/platform-ios': patch
5+
'rnef-docs': patch
6+
---
7+
8+
feat: add '--app' flag to sign:ios to support replacing js bundle in APP files

packages/platform-android/src/lib/commands/signAndroid/command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const OPTIONS = [
6363
export const registerSignCommand = (api: PluginApi) => {
6464
api.registerCommand({
6565
name: 'sign:android',
66-
description: 'Sign the Android app',
66+
description: 'Sign the Android app with modified JS bundle.',
6767
args: ARGUMENTS,
6868
options: OPTIONS,
6969
action: async (apkPath, flags: SignFlags) => {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fs from 'node:fs';
2+
import {
3+
color,
4+
intro,
5+
logger,
6+
outro,
7+
relativeToCwd,
8+
RnefError,
9+
spinner,
10+
} from '@rnef/tools';
11+
import { buildJsBundle } from './bundle.js';
12+
import { getAppPaths } from './utils.js';
13+
14+
export type ModifyAppOptions = {
15+
appPath: string;
16+
outputPath?: string;
17+
buildJsBundle?: boolean;
18+
jsBundlePath?: string;
19+
useHermes?: boolean;
20+
};
21+
22+
export const modifyApp = async (options: ModifyAppOptions) => {
23+
validateOptions(options);
24+
25+
intro(`Modifying APP file`);
26+
27+
const loader = spinner();
28+
29+
// 1. Copy APP file to output path if provided
30+
if (options.outputPath) {
31+
try {
32+
fs.cpSync(options.appPath, options.outputPath, { recursive: true });
33+
} catch (error) {
34+
throw new RnefError(
35+
`Failed to copy APP file to ${color.cyan(
36+
relativeToCwd(options.outputPath)
37+
)}.`,
38+
{ cause: error }
39+
);
40+
}
41+
}
42+
43+
// 2. Make APP content changes if needed: build or swap JS bundle
44+
const appPaths = getAppPaths(options.outputPath ?? options.appPath);
45+
if (options.buildJsBundle) {
46+
loader.start('Building JS bundle');
47+
await buildJsBundle({
48+
bundleOutputPath: appPaths.jsBundle,
49+
assetsDestPath: appPaths.assetsDest,
50+
useHermes: options.useHermes ?? true,
51+
});
52+
loader.stop(
53+
`Built JS bundle: ${color.cyan(relativeToCwd(appPaths.jsBundle))}`
54+
);
55+
} else if (options.jsBundlePath) {
56+
loader.start('Replacing JS bundle');
57+
fs.copyFileSync(options.jsBundlePath, appPaths.jsBundle);
58+
loader.stop(
59+
`Replaced JS bundle with ${color.cyan(
60+
relativeToCwd(options.jsBundlePath)
61+
)}`
62+
);
63+
}
64+
65+
logger.log(
66+
`Modified APP file with new JS bundle. Available at: ${color.cyan(
67+
relativeToCwd(options.outputPath ?? options.appPath)
68+
)}`
69+
);
70+
71+
outro('Success 🎉.');
72+
};
73+
74+
function validateOptions(options: ModifyAppOptions) {
75+
if (!fs.existsSync(options.appPath)) {
76+
throw new RnefError(
77+
`APP file (directory) not found at "${options.appPath}". Please provide a correct path.`
78+
);
79+
}
80+
81+
if (options.buildJsBundle && options.jsBundlePath) {
82+
throw new RnefError(
83+
'The "--build-jsbundle" flag is incompatible with "--jsbundle". Pick one.'
84+
);
85+
}
86+
87+
if (options.jsBundlePath && !fs.existsSync(options.jsBundlePath)) {
88+
throw new RnefError(
89+
`JS bundle file not found at "${options.jsBundlePath}". Please provide a correct path.`
90+
);
91+
}
92+
}

packages/platform-apple-helpers/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { createRun } from './commands/run/createRun.js';
33
export { getBuildOptions, BuildFlags } from './commands/build/buildOptions.js';
44
export { getRunOptions, RunFlags } from './commands/run/runOptions.js';
55
export { modifyIpa, type ModifyIpaOptions } from './commands/sign/modifyIpa.js';
6+
export { modifyApp, type ModifyAppOptions } from './commands/sign/modifyApp.js';
67
export { genericDestinations } from './utils/destionation.js';
78
export { getBuildPaths } from './utils/getBuildPaths.js';
89
export { getInfo } from './utils/getInfo.js';
Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { PluginApi } from '@rnef/config';
2-
import { modifyIpa } from '@rnef/platform-apple-helpers';
2+
import { modifyApp, modifyIpa } from '@rnef/platform-apple-helpers';
33

44
export type SignFlags = {
5-
verbose?: boolean;
6-
ipa: string;
5+
app: string;
76
output?: string;
87
identity?: string;
98
buildJsbundle?: boolean;
@@ -13,19 +12,20 @@ export type SignFlags = {
1312

1413
const ARGUMENTS = [
1514
{
16-
name: 'ipa',
17-
description: 'IPA file path',
15+
name: 'binaryPath',
16+
description: 'Path to the IPA or APP file.',
1817
},
1918
];
2019

2120
const OPTIONS = [
2221
{
23-
name: '--verbose',
24-
description: '',
22+
name: '--app',
23+
description: 'Modify APP file (directory) instead of IPA file. No signing is done.',
2524
},
2625
{
2726
name: '--identity <string>',
28-
description: 'Certificate Identity name to use for code signing, e.g. "Apple Distribution: Your Team (HFJASKHDDS)".',
27+
description:
28+
'Certificate Identity name to use for code signing, e.g. "Apple Distribution: Your Team (HFJASKHDDS)".',
2929
},
3030
{
3131
name: '--output <string>',
@@ -48,19 +48,29 @@ const OPTIONS = [
4848
export const registerSignCommand = (api: PluginApi) => {
4949
api.registerCommand({
5050
name: 'sign:ios',
51-
description: 'Sign the iOS app',
51+
description: 'Sign the iOS app (IPA or APP file) with modified JS bundle.',
5252
args: ARGUMENTS,
5353
options: OPTIONS,
54-
action: async (ipaPath, flags: SignFlags) => {
55-
await modifyIpa({
56-
platformName: 'ios',
57-
ipaPath,
58-
identity: flags.identity,
59-
outputPath: flags.output,
60-
buildJsBundle: flags.buildJsbundle,
61-
jsBundlePath: flags.jsbundle,
62-
useHermes: !flags.noHermes,
63-
});
54+
action: async (binaryPath, flags: SignFlags) => {
55+
if (flags.app) {
56+
await modifyApp({
57+
appPath: binaryPath,
58+
outputPath: flags.output,
59+
buildJsBundle: flags.buildJsbundle,
60+
jsBundlePath: flags.jsbundle,
61+
useHermes: !flags.noHermes,
62+
});
63+
} else {
64+
await modifyIpa({
65+
platformName: 'ios',
66+
ipaPath: binaryPath,
67+
identity: flags.identity,
68+
outputPath: flags.output,
69+
buildJsBundle: flags.buildJsbundle,
70+
jsBundlePath: flags.jsbundle,
71+
useHermes: !flags.noHermes,
72+
});
73+
}
6474
},
6575
});
6676
};

website/docs/docs/cli.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,20 @@ The build cache is populated either by a local build or when downloaded frome re
162162

163163
### `rnef sign:ios` Options
164164

165-
The `sign:ios` command signs your iOS app with certificates and provisioning profiles, producing a signed IPA file ready for distribution.
165+
The `sign:ios` command either signs your iOS app with certificates and provisioning profiles, producing a signed IPA file ready for distribution, or modifies APP file without signing. It allows for replacing the JS bundle with a new version.
166166

167-
| Option | Description |
168-
| :-------------------- | :----------------------------------------- |
169-
| `--identity <string>` | Certificate Identity name for code signing |
170-
| `--output <string>` | Path to output IPA file |
171-
| `--build-jsbundle` | Build JS bundle before signing |
172-
| `--jsbundle <string>` | Path to JS bundle to apply before signing |
173-
| `--no-hermes` | Don't use Hermes for JS bundle |
167+
| Argument | Description |
168+
| :----------- | :-------------------------- |
169+
| `binaryPath` | Path to the IPA or APP file |
170+
171+
| Option | Description |
172+
| :-------------------- | :------------------------------------------------------------------ |
173+
| `--app` | Modify APP file (directory) instead of IPA file. No signing is done |
174+
| `--identity <string>` | Certificate Identity name for code signing |
175+
| `--output <string>` | Path to output IPA file |
176+
| `--build-jsbundle` | Build JS bundle before signing |
177+
| `--jsbundle <string>` | Path to JS bundle to apply before signing |
178+
| `--no-hermes` | Don't use Hermes for JS bundle |
174179

175180
### `rnef build:android` Options
176181

@@ -205,7 +210,11 @@ Same as for `build:android` and:
205210

206211
### `rnef sign:android` Options
207212

208-
The `sign:android` command signs your Android app with a keystore, producing a signed APK file ready for distribution.
213+
The `sign:android` command signs your Android app with a keystore, producing a signed APK file ready for distribution. It allows for replacing the JS bundle with a new version.
214+
215+
| Argument | Description |
216+
| :----------- | :------------------- |
217+
| `binaryPath` | Path to the APK file |
209218

210219
| Option | Description |
211220
| :----------------------------- | :---------------------------------------- |

0 commit comments

Comments
 (0)