hotcodepush 0.0.0 → 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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/commands/app/create.js +49 -0
- package/dist/commands/app/delete.js +34 -0
- package/dist/commands/app/get.js +33 -0
- package/dist/commands/app/list.js +38 -0
- package/dist/commands/app/transfer.js +58 -0
- package/dist/commands/app/update.js +34 -0
- package/dist/commands/audience/get.js +69 -0
- package/dist/commands/audit-log/list.js +82 -0
- package/dist/commands/binary/create.js +189 -0
- package/dist/commands/binary/get.js +56 -0
- package/dist/commands/binary/list.js +58 -0
- package/dist/commands/bundle/delete.js +35 -0
- package/dist/commands/bundle/get.js +63 -0
- package/dist/commands/bundle/list.js +66 -0
- package/dist/commands/bundle/upload.js +176 -0
- package/dist/commands/channel/create.js +59 -0
- package/dist/commands/channel/delete.js +68 -0
- package/dist/commands/channel/get.js +45 -0
- package/dist/commands/channel/list.js +39 -0
- package/dist/commands/channel/pause.js +32 -0
- package/dist/commands/channel/resume.js +32 -0
- package/dist/commands/channel/update.js +40 -0
- package/dist/commands/checkout/create.js +25 -0
- package/dist/commands/completion.js +36 -0
- package/dist/commands/customer-portal-session/create.js +25 -0
- package/dist/commands/device/delete.js +29 -0
- package/dist/commands/device/get.js +60 -0
- package/dist/commands/device/list.js +97 -0
- package/dist/commands/device/probe.js +253 -0
- package/dist/commands/doctor.js +363 -0
- package/dist/commands/fingerprint/diff.js +133 -0
- package/dist/commands/fingerprint.js +41 -0
- package/dist/commands/init.js +492 -0
- package/dist/commands/invitation/accept.js +53 -0
- package/dist/commands/invitation/create.js +52 -0
- package/dist/commands/invitation/delete.js +60 -0
- package/dist/commands/invitation/list.js +40 -0
- package/dist/commands/limit/list.js +34 -0
- package/dist/commands/login.js +185 -0
- package/dist/commands/logout.js +79 -0
- package/dist/commands/member/delete.js +35 -0
- package/dist/commands/member/get.js +37 -0
- package/dist/commands/member/list.js +41 -0
- package/dist/commands/member/update.js +53 -0
- package/dist/commands/open.js +37 -0
- package/dist/commands/organization/create.js +30 -0
- package/dist/commands/organization/delete.js +39 -0
- package/dist/commands/organization/get.js +38 -0
- package/dist/commands/organization/list.js +41 -0
- package/dist/commands/organization/update.js +64 -0
- package/dist/commands/release/create.js +237 -0
- package/dist/commands/release/get.js +81 -0
- package/dist/commands/release/list.js +69 -0
- package/dist/commands/release/pause.js +36 -0
- package/dist/commands/release/resume.js +36 -0
- package/dist/commands/release/revoke.js +253 -0
- package/dist/commands/release/rollback.js +73 -0
- package/dist/commands/release/rollout.js +63 -0
- package/dist/commands/release/update.js +82 -0
- package/dist/commands/resource-file/write.js +76 -0
- package/dist/commands/signing-key/add.js +45 -0
- package/dist/commands/signing-key/create.js +84 -0
- package/dist/commands/signing-key/delete.js +67 -0
- package/dist/commands/signing-key/list.js +38 -0
- package/dist/commands/sso-provider/delete.js +32 -0
- package/dist/commands/sso-provider/get.js +63 -0
- package/dist/commands/sso-provider/set.js +161 -0
- package/dist/commands/sso-provider/verify.js +31 -0
- package/dist/commands/statistics/get.js +189 -0
- package/dist/commands/usage/get.js +31 -0
- package/dist/commands/usage/list.js +41 -0
- package/dist/commands/webhook/create.js +72 -0
- package/dist/commands/webhook/delete.js +32 -0
- package/dist/commands/webhook/get.js +34 -0
- package/dist/commands/webhook/list.js +40 -0
- package/dist/commands/webhook/test.js +32 -0
- package/dist/commands/webhook/update.js +56 -0
- package/dist/commands/webhook-delivery/list.js +63 -0
- package/dist/commands/webhook-delivery/redeliver.js +48 -0
- package/dist/commands/whoami.js +24 -0
- package/dist/completion/hotcodepush.bash +536 -0
- package/dist/completion/hotcodepush.fish +979 -0
- package/dist/completion/hotcodepush.zsh +1372 -0
- package/dist/config/consts.js +32 -0
- package/dist/index.js +5 -0
- package/dist/registry.js +83 -0
- package/dist/utils/api-client.js +17 -0
- package/dist/utils/audience.js +9 -0
- package/dist/utils/auth-client.js +35 -0
- package/dist/utils/boolean-flag.js +3 -0
- package/dist/utils/browser.js +6 -0
- package/dist/utils/build-step.js +104 -0
- package/dist/utils/bundle-files.js +80 -0
- package/dist/utils/bundle-resolution.js +46 -0
- package/dist/utils/channel-fields.js +130 -0
- package/dist/utils/cli.js +75 -0
- package/dist/utils/command-registry.js +79 -0
- package/dist/utils/completion/bash.js +48 -0
- package/dist/utils/completion/command-tree.js +72 -0
- package/dist/utils/completion/fish.js +44 -0
- package/dist/utils/completion/posix-case-arms.js +9 -0
- package/dist/utils/completion/posix-quote.js +4 -0
- package/dist/utils/completion/script-files.js +26 -0
- package/dist/utils/completion/zsh.js +72 -0
- package/dist/utils/compressed-files.js +31 -0
- package/dist/utils/credential.js +15 -0
- package/dist/utils/device-resolution.js +17 -0
- package/dist/utils/duration.js +16 -0
- package/dist/utils/environment.js +22 -0
- package/dist/utils/error-mapping.js +79 -0
- package/dist/utils/errors.js +266 -0
- package/dist/utils/fingerprint.js +76 -0
- package/dist/utils/flag-name.js +4 -0
- package/dist/utils/fleet.js +9 -0
- package/dist/utils/framework.js +74 -0
- package/dist/utils/frameworks/binary-create-step.js +70 -0
- package/dist/utils/frameworks/capacitor.js +150 -0
- package/dist/utils/frameworks/cordova.js +110 -0
- package/dist/utils/frameworks/expo.js +152 -0
- package/dist/utils/frameworks/index.js +13 -0
- package/dist/utils/frameworks/native-glue.js +10 -0
- package/dist/utils/frameworks/native-projects.js +27 -0
- package/dist/utils/frameworks/react-native-build.js +182 -0
- package/dist/utils/frameworks/react-native.js +127 -0
- package/dist/utils/frameworks/sdk-package.js +63 -0
- package/dist/utils/git-provenance.js +71 -0
- package/dist/utils/global-options.js +34 -0
- package/dist/utils/hosts.js +53 -0
- package/dist/utils/init-steps.js +51 -0
- package/dist/utils/json-file.js +28 -0
- package/dist/utils/member-resolution.js +49 -0
- package/dist/utils/native-project-edit.js +41 -0
- package/dist/utils/nullable-field.js +10 -0
- package/dist/utils/outcome.js +24 -0
- package/dist/utils/output.js +37 -0
- package/dist/utils/pack.js +20 -0
- package/dist/utils/package-json.js +5 -0
- package/dist/utils/package-manager.js +50 -0
- package/dist/utils/pagination.js +29 -0
- package/dist/utils/progress.js +23 -0
- package/dist/utils/progression.js +119 -0
- package/dist/utils/project-config.js +61 -0
- package/dist/utils/prompts.js +53 -0
- package/dist/utils/react-native-project.js +119 -0
- package/dist/utils/release-conditions.js +80 -0
- package/dist/utils/release-output.js +13 -0
- package/dist/utils/release-resolution.js +83 -0
- package/dist/utils/resource-file.js +81 -0
- package/dist/utils/resource-resolution.js +107 -0
- package/dist/utils/signing-private-key.js +105 -0
- package/dist/utils/spending-cap.js +16 -0
- package/dist/utils/telemetry.js +171 -0
- package/dist/utils/token-store.js +63 -0
- package/dist/utils/update-notice.js +67 -0
- package/dist/utils/upload.js +148 -0
- package/dist/utils/usage-month.js +14 -0
- package/dist/utils/user-config.js +46 -0
- package/dist/utils/webhook-resolution.js +47 -0
- package/dist/utils/xcode-project.js +181 -0
- package/package.json +68 -7
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
4
|
+
import { CORDOVA_PACKAGE_NAME, CORDOVA_PACKAGE_SPEC, } from '../../config/consts.js';
|
|
5
|
+
import { resolveCommandLineText, runCommandLineVisibly, } from '../package-manager.js';
|
|
6
|
+
import { NATIVE_GLUE_PATHS } from './native-glue.js';
|
|
7
|
+
import { checkSdkPackage, isSdkPackageDeclared } from './sdk-package.js';
|
|
8
|
+
const CONFIG_FILE_NAME = 'config.xml';
|
|
9
|
+
const INSECURE_FILE_MODE_PREFERENCE_NAME = 'AndroidInsecureFileModeEnabled';
|
|
10
|
+
const LIST_TAG_NAMES = new Set(['platform', 'preference']);
|
|
11
|
+
const PLUGIN_ADD_COMMAND_LINE = {
|
|
12
|
+
args: ['cordova', 'plugin', 'add', CORDOVA_PACKAGE_SPEC],
|
|
13
|
+
command: 'npx',
|
|
14
|
+
};
|
|
15
|
+
const WEB_DIRECTORY = 'www';
|
|
16
|
+
export const cordovaFramework = {
|
|
17
|
+
nativeGluePaths: NATIVE_GLUE_PATHS,
|
|
18
|
+
packageName: CORDOVA_PACKAGE_NAME,
|
|
19
|
+
versionedPackageNames: [
|
|
20
|
+
'cordova',
|
|
21
|
+
'cordova-android',
|
|
22
|
+
'cordova-ios',
|
|
23
|
+
CORDOVA_PACKAGE_NAME,
|
|
24
|
+
],
|
|
25
|
+
checkWiring: project => [
|
|
26
|
+
checkSdkPackage(project, CORDOVA_PACKAGE_NAME),
|
|
27
|
+
checkHook(project),
|
|
28
|
+
checkInsecureFileMode(project),
|
|
29
|
+
],
|
|
30
|
+
// The start page in `config.xml` is a page inside `www`, which the app serves as its root.
|
|
31
|
+
readBuildDirectory: () => ({ path: WEB_DIRECTORY }),
|
|
32
|
+
resolveWiring: project => Promise.resolve(resolveWiring(project)),
|
|
33
|
+
};
|
|
34
|
+
// The plugin wires the build steps itself into each platform once `package.json` lists it among Cordova's plugins.
|
|
35
|
+
function checkHook({ packageJson }) {
|
|
36
|
+
return isPluginListed(packageJson)
|
|
37
|
+
? {
|
|
38
|
+
check: 'hook',
|
|
39
|
+
message: "The plugin's Xcode phase and Gradle task run the build step.",
|
|
40
|
+
status: 'ok',
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
check: 'hook',
|
|
44
|
+
code: 'wiring_missing',
|
|
45
|
+
manualStep: `Run "${resolveCommandLineText(PLUGIN_ADD_COMMAND_LINE)}".`,
|
|
46
|
+
message: `The plugin ${CORDOVA_PACKAGE_NAME} is not among package.json's cordova plugins.`,
|
|
47
|
+
status: 'failed',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function checkInsecureFileMode({ directoryPath, }) {
|
|
51
|
+
return isInsecureFileModeEnabled(readWidget(directoryPath))
|
|
52
|
+
? {
|
|
53
|
+
check: 'android_file_mode',
|
|
54
|
+
code: 'insecure_file_mode',
|
|
55
|
+
manualStep: 'The plugin serves no update from file:// and stays off. Remove the preference.',
|
|
56
|
+
message: `The file ${CONFIG_FILE_NAME} sets ${INSECURE_FILE_MODE_PREFERENCE_NAME}, which loads the Android app from file://.`,
|
|
57
|
+
status: 'failed',
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
check: 'android_file_mode',
|
|
61
|
+
message: `The file ${CONFIG_FILE_NAME} leaves ${INSECURE_FILE_MODE_PREFERENCE_NAME} off.`,
|
|
62
|
+
status: 'ok',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Matches how Cordova reads Android preferences: names and values ignore case, and the last one wins.
|
|
66
|
+
// The Android section comes after the widget's, so it wins too.
|
|
67
|
+
function isInsecureFileModeEnabled(widget) {
|
|
68
|
+
const preferences = [
|
|
69
|
+
...(widget?.preference ?? []),
|
|
70
|
+
...(widget?.platform ?? [])
|
|
71
|
+
.filter(({ name }) => name === 'android')
|
|
72
|
+
.flatMap(({ preference }) => preference ?? []),
|
|
73
|
+
];
|
|
74
|
+
const value = preferences.findLast(({ name }) => name?.toLowerCase() === INSECURE_FILE_MODE_PREFERENCE_NAME.toLowerCase())?.value;
|
|
75
|
+
return value?.toLowerCase() === 'true';
|
|
76
|
+
}
|
|
77
|
+
function isPluginListed(packageJson) {
|
|
78
|
+
return packageJson?.cordova?.plugins?.[CORDOVA_PACKAGE_NAME] !== undefined;
|
|
79
|
+
}
|
|
80
|
+
function readWidget(projectDirectoryPath) {
|
|
81
|
+
const configFilePath = join(projectDirectoryPath, CONFIG_FILE_NAME);
|
|
82
|
+
if (!existsSync(configFilePath)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const document = new XMLParser({
|
|
86
|
+
attributeNamePrefix: '',
|
|
87
|
+
ignoreAttributes: false,
|
|
88
|
+
isArray: (tagName, _jPath, _isLeafNode, isAttribute) => !isAttribute && LIST_TAG_NAMES.has(tagName),
|
|
89
|
+
parseAttributeValue: false,
|
|
90
|
+
}).parse(readFileSync(configFilePath, 'utf8'));
|
|
91
|
+
return document.widget;
|
|
92
|
+
}
|
|
93
|
+
function resolveWiring({ directoryPath, packageJson, }) {
|
|
94
|
+
const isPackageInstalled = isSdkPackageDeclared(packageJson, CORDOVA_PACKAGE_NAME) &&
|
|
95
|
+
isPluginListed(packageJson);
|
|
96
|
+
return {
|
|
97
|
+
isPackageInstalled,
|
|
98
|
+
nativeFilePaths: [],
|
|
99
|
+
packageFilePaths: isPackageInstalled ? [] : ['package.json'],
|
|
100
|
+
installPackage: () => {
|
|
101
|
+
runCommandLineVisibly(PLUGIN_ADD_COMMAND_LINE, directoryPath);
|
|
102
|
+
return `Installed ${CORDOVA_PACKAGE_NAME} from ${CORDOVA_PACKAGE_SPEC} through "cordova plugin add".`;
|
|
103
|
+
},
|
|
104
|
+
wireBinaryCreateStep: () => Promise.resolve({
|
|
105
|
+
message: 'The plugin wires its Xcode phase and Gradle task itself, so there is nothing to wire.',
|
|
106
|
+
status: 'skipped',
|
|
107
|
+
value: undefined,
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { EXPO_PACKAGE_NAME, EXPO_PACKAGE_SPEC, INIT_MANUAL_STEP, REACT_NATIVE_PACKAGE_NAME, } from '../../config/consts.js';
|
|
4
|
+
import { NativeProjectError } from '../errors.js';
|
|
5
|
+
import { stringifyLikeSource } from '../json-file.js';
|
|
6
|
+
import { collectEmbeddedFiles, packageReactNativeBundles, readBuildDirectory, } from './react-native-build.js';
|
|
7
|
+
import { checkSdkPackage, installSdkPackage, isSdkPackageDeclared, } from './sdk-package.js';
|
|
8
|
+
const APP_JSON_FILE_NAME = 'app.json';
|
|
9
|
+
// Expo's native release builds run this bundler, and it resolves the entry file itself.
|
|
10
|
+
const BUNDLER_ARGS = ['expo', 'export:embed'];
|
|
11
|
+
// Both lists are in the order Expo prefers.
|
|
12
|
+
const CODE_APP_CONFIG_FILE_NAMES = [
|
|
13
|
+
'app.config.ts',
|
|
14
|
+
'app.config.mts',
|
|
15
|
+
'app.config.cts',
|
|
16
|
+
'app.config.mjs',
|
|
17
|
+
'app.config.cjs',
|
|
18
|
+
'app.config.js',
|
|
19
|
+
];
|
|
20
|
+
const JSON_APP_CONFIG_FILE_NAMES = ['app.config.json', APP_JSON_FILE_NAME];
|
|
21
|
+
// The config plugin wires the native projects at prebuild, so `init` only adds one entry to the app config.
|
|
22
|
+
export const expoFramework = {
|
|
23
|
+
packageName: EXPO_PACKAGE_NAME,
|
|
24
|
+
versionedPackageNames: ['expo', 'react-native', EXPO_PACKAGE_NAME],
|
|
25
|
+
versionedSdkDependencyNames: [REACT_NATIVE_PACKAGE_NAME],
|
|
26
|
+
checkWiring: project => [
|
|
27
|
+
checkSdkPackage(project, EXPO_PACKAGE_NAME),
|
|
28
|
+
checkPluginEntry(project),
|
|
29
|
+
],
|
|
30
|
+
collectEmbeddedFiles,
|
|
31
|
+
packageBundles: request => packageReactNativeBundles(request, () => BUNDLER_ARGS),
|
|
32
|
+
readBuildDirectory,
|
|
33
|
+
resolveWiring: project => Promise.resolve(resolveWiring(project)),
|
|
34
|
+
};
|
|
35
|
+
function addPluginEntry(filePath) {
|
|
36
|
+
// A project without an app config gets one, as Expo's own install writes it.
|
|
37
|
+
const appJsonText = existsSync(filePath)
|
|
38
|
+
? readFileSync(filePath, 'utf8')
|
|
39
|
+
: '{}';
|
|
40
|
+
const appJson = JSON.parse(appJsonText);
|
|
41
|
+
const appConfig = appJson.expo ?? appJson;
|
|
42
|
+
appConfig.plugins = [...(appConfig.plugins ?? []), EXPO_PACKAGE_NAME];
|
|
43
|
+
writeFileSync(filePath, stringifyLikeSource(appJson, appJsonText));
|
|
44
|
+
}
|
|
45
|
+
function checkPluginEntry({ directoryPath }) {
|
|
46
|
+
const appConfigFileNames = findAppConfigFileNames(directoryPath);
|
|
47
|
+
const pluginEntryFileName = findPluginEntryFileName(directoryPath, appConfigFileNames);
|
|
48
|
+
if (pluginEntryFileName !== undefined) {
|
|
49
|
+
return {
|
|
50
|
+
check: 'hook',
|
|
51
|
+
message: `The app config ${pluginEntryFileName} lists the config plugin, which wires the build step at prebuild.`,
|
|
52
|
+
status: 'ok',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const { codeFileName, jsonFileName } = appConfigFileNames;
|
|
56
|
+
const appConfigFileName = codeFileName ?? jsonFileName;
|
|
57
|
+
return {
|
|
58
|
+
check: 'hook',
|
|
59
|
+
code: 'wiring_missing',
|
|
60
|
+
manualStep: isPluginEntryEditable(directoryPath, appConfigFileNames)
|
|
61
|
+
? INIT_MANUAL_STEP
|
|
62
|
+
: resolvePluginEntryStep(appConfigFileName),
|
|
63
|
+
message: `The app config ${appConfigFileName} does not list the config plugin ${EXPO_PACKAGE_NAME}.`,
|
|
64
|
+
status: 'failed',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function findAppConfigFileNames(projectDirectoryPath) {
|
|
68
|
+
const isPresent = (fileName) => existsSync(join(projectDirectoryPath, fileName));
|
|
69
|
+
return {
|
|
70
|
+
codeFileName: CODE_APP_CONFIG_FILE_NAMES.find(isPresent),
|
|
71
|
+
jsonFileName: JSON_APP_CONFIG_FILE_NAMES.find(isPresent) ?? APP_JSON_FILE_NAME,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// An entry in the JSON config counts beside a code config too, since Expo hands its config to the code.
|
|
75
|
+
function findPluginEntryFileName(projectDirectoryPath, { codeFileName, jsonFileName }) {
|
|
76
|
+
return [codeFileName, jsonFileName].find(fileName => fileName !== undefined && hasPluginEntry(projectDirectoryPath, fileName));
|
|
77
|
+
}
|
|
78
|
+
// A code or JSON5 config is never evaluated, so the package name is matched in its text.
|
|
79
|
+
function hasPluginEntry(projectDirectoryPath, appConfigFileName) {
|
|
80
|
+
const filePath = join(projectDirectoryPath, appConfigFileName);
|
|
81
|
+
if (!existsSync(filePath)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const appConfigText = readFileSync(filePath, 'utf8');
|
|
85
|
+
const appJson = JSON_APP_CONFIG_FILE_NAMES.includes(appConfigFileName)
|
|
86
|
+
? parseAppJson(appConfigText)
|
|
87
|
+
: undefined;
|
|
88
|
+
if (appJson === undefined) {
|
|
89
|
+
return appConfigText.includes(EXPO_PACKAGE_NAME);
|
|
90
|
+
}
|
|
91
|
+
return ((appJson.expo ?? appJson).plugins ?? []).some(plugin => (Array.isArray(plugin) ? plugin[0] : plugin) === EXPO_PACKAGE_NAME);
|
|
92
|
+
}
|
|
93
|
+
// Expo also reads JSON5, which does not parse as JSON. The person adds the entry there and beside a code config.
|
|
94
|
+
function isPluginEntryEditable(projectDirectoryPath, { codeFileName, jsonFileName }) {
|
|
95
|
+
const jsonFilePath = join(projectDirectoryPath, jsonFileName);
|
|
96
|
+
return (codeFileName === undefined &&
|
|
97
|
+
(!existsSync(jsonFilePath) ||
|
|
98
|
+
parseAppJson(readFileSync(jsonFilePath, 'utf8')) !== undefined));
|
|
99
|
+
}
|
|
100
|
+
function parseAppJson(appJsonText) {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(appJsonText);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function resolvePluginEntryStep(appConfigFileName) {
|
|
109
|
+
return `Add "${EXPO_PACKAGE_NAME}" to the plugins in ${appConfigFileName}.`;
|
|
110
|
+
}
|
|
111
|
+
function resolveWiring({ directoryPath, packageJson, }) {
|
|
112
|
+
const isPackageInstalled = isSdkPackageDeclared(packageJson, EXPO_PACKAGE_NAME);
|
|
113
|
+
const appConfigFileNames = findAppConfigFileNames(directoryPath);
|
|
114
|
+
const isAppJsonToEdit = isPluginEntryEditable(directoryPath, appConfigFileNames) &&
|
|
115
|
+
findPluginEntryFileName(directoryPath, appConfigFileNames) === undefined;
|
|
116
|
+
return {
|
|
117
|
+
isPackageInstalled,
|
|
118
|
+
nativeFilePaths: [],
|
|
119
|
+
packageFilePaths: [
|
|
120
|
+
...(isPackageInstalled ? [] : ['package.json']),
|
|
121
|
+
...(isAppJsonToEdit ? [appConfigFileNames.jsonFileName] : []),
|
|
122
|
+
],
|
|
123
|
+
installPackage: () => installSdkPackage(directoryPath, EXPO_PACKAGE_NAME, EXPO_PACKAGE_SPEC),
|
|
124
|
+
wireBinaryCreateStep: editBlocker => wirePluginEntry(directoryPath, appConfigFileNames, editBlocker),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function wirePluginEntry(projectDirectoryPath, appConfigFileNames, editBlocker) {
|
|
128
|
+
const pluginEntryFileName = findPluginEntryFileName(projectDirectoryPath, appConfigFileNames);
|
|
129
|
+
if (pluginEntryFileName !== undefined) {
|
|
130
|
+
return {
|
|
131
|
+
message: `The app config ${pluginEntryFileName} already lists the config plugin.`,
|
|
132
|
+
status: 'skipped',
|
|
133
|
+
value: undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const { codeFileName, jsonFileName } = appConfigFileNames;
|
|
137
|
+
if (codeFileName !== undefined) {
|
|
138
|
+
throw new NativeProjectError(`The app config ${codeFileName} is code, which the CLI does not edit.`, resolvePluginEntryStep(codeFileName));
|
|
139
|
+
}
|
|
140
|
+
if (!isPluginEntryEditable(projectDirectoryPath, appConfigFileNames)) {
|
|
141
|
+
throw new NativeProjectError(`The app config ${jsonFileName} is not JSON the CLI can parse.`, resolvePluginEntryStep(jsonFileName));
|
|
142
|
+
}
|
|
143
|
+
if (editBlocker !== undefined) {
|
|
144
|
+
throw editBlocker;
|
|
145
|
+
}
|
|
146
|
+
addPluginEntry(join(projectDirectoryPath, jsonFileName));
|
|
147
|
+
return {
|
|
148
|
+
message: `Added the config plugin ${EXPO_PACKAGE_NAME} to ${jsonFileName}.`,
|
|
149
|
+
status: 'done',
|
|
150
|
+
value: undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { capacitorFramework } from './capacitor.js';
|
|
2
|
+
import { cordovaFramework } from './cordova.js';
|
|
3
|
+
import { expoFramework } from './expo.js';
|
|
4
|
+
import { reactNativeFramework } from './react-native.js';
|
|
5
|
+
const FRAMEWORK_MODULES = {
|
|
6
|
+
capacitor: capacitorFramework,
|
|
7
|
+
cordova: cordovaFramework,
|
|
8
|
+
expo: expoFramework,
|
|
9
|
+
react_native: reactNativeFramework,
|
|
10
|
+
};
|
|
11
|
+
export function resolveFrameworkModule(framework) {
|
|
12
|
+
return FRAMEWORK_MODULES[framework];
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Cordova's bridge and plugin scripts, which Capacitor copies too. The binary serves them under every bundle.
|
|
2
|
+
// A path ending in `/` names everything under it.
|
|
3
|
+
export const NATIVE_GLUE_PATHS = [
|
|
4
|
+
'cordova.js',
|
|
5
|
+
'cordova_plugins.js',
|
|
6
|
+
'plugins/',
|
|
7
|
+
];
|
|
8
|
+
export function omitNativeGlue(files, nativeGluePaths) {
|
|
9
|
+
return files.filter(({ path }) => !nativeGluePaths.some(gluePath => gluePath.endsWith('/') ? path.startsWith(gluePath) : path === gluePath));
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
|
+
import { isInteractive } from '../environment.js';
|
|
4
|
+
import { MissingParameterError } from '../errors.js';
|
|
5
|
+
import { promptText } from '../prompts.js';
|
|
6
|
+
// Non-interactively, missing projects stop the hook step later instead of failing here.
|
|
7
|
+
export async function resolveNativeProjects(projectDirectoryPath, defaultPaths, options, missingProjectsStep) {
|
|
8
|
+
const android = options.androidPath === undefined
|
|
9
|
+
? defaultPaths.android
|
|
10
|
+
: resolve(options.androidPath);
|
|
11
|
+
const ios = options.iosPath === undefined ? defaultPaths.ios : resolve(options.iosPath);
|
|
12
|
+
if (existsSync(android) || existsSync(ios)) {
|
|
13
|
+
return { android, ios, missingError: undefined };
|
|
14
|
+
}
|
|
15
|
+
if (!isInteractive(options)) {
|
|
16
|
+
return {
|
|
17
|
+
android,
|
|
18
|
+
ios,
|
|
19
|
+
missingError: new MissingParameterError('--ios-path', `Neither ${relative(projectDirectoryPath, ios)} nor ${relative(projectDirectoryPath, android)} exists. ${missingProjectsStep}`),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
android: resolve(await promptText('--android-path', 'Where is the Android project?', options)),
|
|
24
|
+
ios: resolve(await promptText('--ios-path', 'Where is the iOS project?', options)),
|
|
25
|
+
missingError: undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { collectBundleFiles, computeFileSha256 } from '../bundle-files.js';
|
|
5
|
+
import { InvalidParameterError } from '../errors.js';
|
|
6
|
+
import { runCommandLineVisibly } from '../package-manager.js';
|
|
7
|
+
// The names React Native's own builds give the JavaScript, in the app and in an uploaded bundle alike.
|
|
8
|
+
const BUNDLE_FILE_NAMES = {
|
|
9
|
+
android: 'index.android.bundle',
|
|
10
|
+
ios: 'main.jsbundle',
|
|
11
|
+
};
|
|
12
|
+
// React Native's release build defaults: the Gradle plugin's `hermesFlags`, and react-native-xcode.sh without `SOURCEMAP_FILE`.
|
|
13
|
+
// On Android the source map takes the debug information out of the bytecode, on iOS it stays inline.
|
|
14
|
+
const HERMES_FLAGS = {
|
|
15
|
+
android: ['-O', '-output-source-map'],
|
|
16
|
+
ios: ['-O'],
|
|
17
|
+
};
|
|
18
|
+
const HERMESC_DIRECTORY_NAMES = {
|
|
19
|
+
darwin: 'osx-bin',
|
|
20
|
+
linux: 'linux64-bin',
|
|
21
|
+
win32: 'win64-bin',
|
|
22
|
+
};
|
|
23
|
+
const PLATFORMS = ['android', 'ios'];
|
|
24
|
+
// The iOS app holds far more than the bundle, so only the JavaScript and `assets/` are taken.
|
|
25
|
+
// A debug build that Metro serves has no bundle and gives the build step nothing to hash.
|
|
26
|
+
export async function collectEmbeddedFiles(platform, inputDirectoryPath) {
|
|
27
|
+
const bundleFileName = BUNDLE_FILE_NAMES[platform];
|
|
28
|
+
const bundleFilePath = join(inputDirectoryPath, bundleFileName);
|
|
29
|
+
if (!existsSync(bundleFilePath)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (platform === 'android') {
|
|
33
|
+
return collectBundleFiles(inputDirectoryPath);
|
|
34
|
+
}
|
|
35
|
+
const assetsDirectoryPath = join(inputDirectoryPath, 'assets');
|
|
36
|
+
const assetFiles = existsSync(assetsDirectoryPath)
|
|
37
|
+
? await collectBundleFiles(assetsDirectoryPath)
|
|
38
|
+
: [];
|
|
39
|
+
return [
|
|
40
|
+
...assetFiles.map(file => ({ ...file, path: `assets/${file.path}` })),
|
|
41
|
+
{
|
|
42
|
+
filePath: bundleFilePath,
|
|
43
|
+
path: bundleFileName,
|
|
44
|
+
sha256: await computeFileSha256(bundleFilePath),
|
|
45
|
+
sizeBytes: statSync(bundleFilePath).size,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
export function packageReactNativeBundles({ packagingDirectoryPath, path, platforms = PLATFORMS, projectDirectoryPath, }, resolveBundlerArgs) {
|
|
50
|
+
if (path !== undefined) {
|
|
51
|
+
const [platform] = platforms;
|
|
52
|
+
if (platform === undefined || platforms.length > 1) {
|
|
53
|
+
throw new InvalidParameterError('A prepared bundle from --path serves one platform.', 'Name it with --platform ios or --platform android.');
|
|
54
|
+
}
|
|
55
|
+
const directoryPath = resolve(path);
|
|
56
|
+
const bundleFileName = BUNDLE_FILE_NAMES[platform];
|
|
57
|
+
if (!existsSync(join(directoryPath, bundleFileName))) {
|
|
58
|
+
throw new InvalidParameterError(`The directory ${directoryPath} from --path holds no ${bundleFileName}, the JavaScript the ${platform} app loads.`, `Pass the ${platform} bundle's directory, with ${bundleFileName} in it, or leave out --path to bundle the project.`);
|
|
59
|
+
}
|
|
60
|
+
return Promise.resolve([{ directoryPath, platforms }]);
|
|
61
|
+
}
|
|
62
|
+
return Promise.resolve(platforms.map(platform => {
|
|
63
|
+
const directoryPath = join(packagingDirectoryPath, platform);
|
|
64
|
+
packageBundle(projectDirectoryPath, platform, directoryPath, resolveBundlerArgs(projectDirectoryPath, platform));
|
|
65
|
+
return { directoryPath, platforms: [platform] };
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
export function readBuildDirectory() {
|
|
69
|
+
return {
|
|
70
|
+
missingReason: 'the embedded bundle lies in the app the native build makes',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function resolveNativeProjectPaths(projectDirectoryPath) {
|
|
74
|
+
return {
|
|
75
|
+
android: join(projectDirectoryPath, 'android'),
|
|
76
|
+
ios: join(projectDirectoryPath, 'ios'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isHermesEnabled(platform, nativeProjectPaths) {
|
|
80
|
+
const [filePath, disabledPattern] = platform === 'android'
|
|
81
|
+
? [
|
|
82
|
+
join(nativeProjectPaths.android, 'gradle.properties'),
|
|
83
|
+
/^\s*hermesEnabled\s*=\s*false\s*$/m,
|
|
84
|
+
]
|
|
85
|
+
: [
|
|
86
|
+
join(nativeProjectPaths.ios, 'Podfile'),
|
|
87
|
+
/:hermes_enabled\s*=>\s*false/,
|
|
88
|
+
];
|
|
89
|
+
return (!existsSync(filePath) ||
|
|
90
|
+
!disabledPattern.test(readFileSync(filePath, 'utf8')));
|
|
91
|
+
}
|
|
92
|
+
// Mirrors the release build. Intermediate files go beside the bundle's directory, never into it.
|
|
93
|
+
function packageBundle(projectDirectoryPath, platform, outputDirectoryPath, bundlerArgs) {
|
|
94
|
+
const intermediateDirectoryPath = `${outputDirectoryPath}-intermediate`;
|
|
95
|
+
mkdirSync(outputDirectoryPath, { recursive: true });
|
|
96
|
+
mkdirSync(intermediateDirectoryPath, { recursive: true });
|
|
97
|
+
const bundleFileName = BUNDLE_FILE_NAMES[platform];
|
|
98
|
+
const bundleFilePath = join(outputDirectoryPath, bundleFileName);
|
|
99
|
+
const hermescFilePath = resolveHermescFilePath(projectDirectoryPath, platform);
|
|
100
|
+
const javaScriptFilePath = hermescFilePath === undefined
|
|
101
|
+
? bundleFilePath
|
|
102
|
+
: join(intermediateDirectoryPath, bundleFileName);
|
|
103
|
+
const sourceMapFileName = resolvePackagerSourceMapFileName(platform, hermescFilePath !== undefined);
|
|
104
|
+
runCommandLineVisibly({
|
|
105
|
+
args: [
|
|
106
|
+
...bundlerArgs,
|
|
107
|
+
'--platform',
|
|
108
|
+
platform,
|
|
109
|
+
'--dev',
|
|
110
|
+
'false',
|
|
111
|
+
'--bundle-output',
|
|
112
|
+
javaScriptFilePath,
|
|
113
|
+
'--assets-dest',
|
|
114
|
+
outputDirectoryPath,
|
|
115
|
+
'--reset-cache',
|
|
116
|
+
...(sourceMapFileName === undefined
|
|
117
|
+
? []
|
|
118
|
+
: [
|
|
119
|
+
'--sourcemap-output',
|
|
120
|
+
join(intermediateDirectoryPath, sourceMapFileName),
|
|
121
|
+
]),
|
|
122
|
+
// Hermes compiles the JavaScript itself and needs no minification before it.
|
|
123
|
+
...(hermescFilePath === undefined ? [] : ['--minify', 'false']),
|
|
124
|
+
],
|
|
125
|
+
command: 'npx',
|
|
126
|
+
}, projectDirectoryPath);
|
|
127
|
+
if (hermescFilePath !== undefined) {
|
|
128
|
+
const bytecodeFilePath = `${javaScriptFilePath}.hbc`;
|
|
129
|
+
runCommandLineVisibly({
|
|
130
|
+
args: [
|
|
131
|
+
'-emit-binary',
|
|
132
|
+
'-max-diagnostic-width=80',
|
|
133
|
+
...HERMES_FLAGS[platform],
|
|
134
|
+
'-out',
|
|
135
|
+
bytecodeFilePath,
|
|
136
|
+
javaScriptFilePath,
|
|
137
|
+
],
|
|
138
|
+
command: hermescFilePath,
|
|
139
|
+
}, projectDirectoryPath);
|
|
140
|
+
renameSync(bytecodeFilePath, bundleFilePath);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function resolveHermescFilePath(projectDirectoryPath, platform) {
|
|
144
|
+
if (!isHermesEnabled(platform, resolveNativeProjectPaths(projectDirectoryPath))) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const directoryName = HERMESC_DIRECTORY_NAMES[process.platform];
|
|
148
|
+
const fileName = process.platform === 'win32' ? 'hermesc.exe' : 'hermesc';
|
|
149
|
+
const hermescFilePath = [
|
|
150
|
+
['react-native', 'sdks', 'hermesc'],
|
|
151
|
+
['hermes-compiler', 'hermesc'],
|
|
152
|
+
]
|
|
153
|
+
.map(([packageName = '', ...segments]) => resolvePackageDirectoryPath(projectDirectoryPath, packageName, segments))
|
|
154
|
+
.map(directoryPath => directoryPath === undefined || directoryName === undefined
|
|
155
|
+
? undefined
|
|
156
|
+
: join(directoryPath, directoryName, fileName))
|
|
157
|
+
.find(filePath => filePath !== undefined && existsSync(filePath));
|
|
158
|
+
if (hermescFilePath === undefined) {
|
|
159
|
+
throw new InvalidParameterError("Hermes' compiler was not found in the project's react-native.", 'Install the dependencies, or pass --path with a bundle directory prepared for one --platform.');
|
|
160
|
+
}
|
|
161
|
+
return hermescFilePath;
|
|
162
|
+
}
|
|
163
|
+
function resolvePackageDirectoryPath(projectDirectoryPath, packageName, segments) {
|
|
164
|
+
try {
|
|
165
|
+
const packageJsonPath = createRequire(join(projectDirectoryPath, 'package.json')).resolve(`${packageName}/package.json`);
|
|
166
|
+
return join(dirname(packageJsonPath), ...segments);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// The JavaScript ends with a comment naming its source map, so the name must match React Native's release build.
|
|
173
|
+
// Gradle always writes one, and Xcode none by default.
|
|
174
|
+
function resolvePackagerSourceMapFileName(platform, isHermesEnabledForPlatform) {
|
|
175
|
+
if (platform === 'ios') {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const bundleFileName = BUNDLE_FILE_NAMES[platform];
|
|
179
|
+
return isHermesEnabledForPlatform
|
|
180
|
+
? `${bundleFileName}.packager.map`
|
|
181
|
+
: `${bundleFileName}.map`;
|
|
182
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { REACT_NATIVE_PACKAGE_NAME, REACT_NATIVE_PACKAGE_SPEC, } from '../../config/consts.js';
|
|
4
|
+
import { resolveGradleEdit } from '../native-project-edit.js';
|
|
5
|
+
import { runCommandLineVisibly } from '../package-manager.js';
|
|
6
|
+
import { resolveBundleUrlEdit, resolveReactHostEdit, } from '../react-native-project.js';
|
|
7
|
+
import { buildPbxprojShellScript, hasBinaryCreatePhase, resolveXcodeProjectFilePath, } from '../xcode-project.js';
|
|
8
|
+
import { applyNativeProjectEdits, checkBinaryCreateStep, checkEdits, } from './binary-create-step.js';
|
|
9
|
+
import { resolveNativeProjects } from './native-projects.js';
|
|
10
|
+
import { collectEmbeddedFiles, packageReactNativeBundles, readBuildDirectory, resolveNativeProjectPaths, } from './react-native-build.js';
|
|
11
|
+
import { checkSdkPackage, installSdkPackage, isSdkPackageDeclared, } from './sdk-package.js';
|
|
12
|
+
// React Native's `with-environment.sh` finds Node for the script.
|
|
13
|
+
const BINARY_CREATE_PHASE = {
|
|
14
|
+
anchorPhaseName: 'Bundle React Native code and images',
|
|
15
|
+
fix: `Add a Run Script phase after "Bundle React Native code and images" that runs node_modules/${REACT_NATIVE_PACKAGE_NAME}/scripts/binary-create-xcode.sh through React Native's with-environment.sh.`,
|
|
16
|
+
shellScript: buildPbxprojShellScript([
|
|
17
|
+
'set -e',
|
|
18
|
+
'',
|
|
19
|
+
'# hotcodepush: writes hotcodepush.json into the app and, in a store build, creates the binary',
|
|
20
|
+
'WITH_ENVIRONMENT="$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"',
|
|
21
|
+
`HOTCODEPUSH_BINARY_CREATE="$REACT_NATIVE_PATH/../${REACT_NATIVE_PACKAGE_NAME}/scripts/binary-create-xcode.sh"`,
|
|
22
|
+
'',
|
|
23
|
+
'/bin/sh -c "$WITH_ENVIRONMENT $HOTCODEPUSH_BINARY_CREATE"',
|
|
24
|
+
'',
|
|
25
|
+
]),
|
|
26
|
+
};
|
|
27
|
+
const POD_NAME = 'HotcodepushReactNativeCodePush';
|
|
28
|
+
// The project has no build output: the native build and the upload each bundle the JavaScript when needed.
|
|
29
|
+
export const reactNativeFramework = {
|
|
30
|
+
packageName: REACT_NATIVE_PACKAGE_NAME,
|
|
31
|
+
versionedPackageNames: ['react-native', REACT_NATIVE_PACKAGE_NAME],
|
|
32
|
+
checkWiring: project => [
|
|
33
|
+
checkSdkPackage(project, REACT_NATIVE_PACKAGE_NAME),
|
|
34
|
+
checkBinaryCreateStep(project.directoryPath, resolveNativeProjectPaths(project.directoryPath), REACT_NATIVE_PACKAGE_NAME),
|
|
35
|
+
checkBundleWiring(project),
|
|
36
|
+
],
|
|
37
|
+
collectEmbeddedFiles,
|
|
38
|
+
packageBundles: request => packageReactNativeBundles(request, resolveBundlerArgs),
|
|
39
|
+
readBuildDirectory,
|
|
40
|
+
resolveWiring,
|
|
41
|
+
};
|
|
42
|
+
function checkBundleWiring({ directoryPath, }) {
|
|
43
|
+
return checkEdits('host', 'React Native runs the bundle the SDK serves.', resolveBundleWiringEdits(resolveNativeProjectPaths(directoryPath)), directoryPath);
|
|
44
|
+
}
|
|
45
|
+
function isPodInstalled(iosProjectPath) {
|
|
46
|
+
if (!existsSync(join(iosProjectPath, 'Podfile'))) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const lockFilePath = join(iosProjectPath, 'Podfile.lock');
|
|
50
|
+
return (existsSync(lockFilePath) &&
|
|
51
|
+
readFileSync(lockFilePath, 'utf8').includes(POD_NAME));
|
|
52
|
+
}
|
|
53
|
+
function resolveBundleWiringEdits(nativeProjectPaths) {
|
|
54
|
+
return [
|
|
55
|
+
resolveBundleUrlEdit(nativeProjectPaths.ios),
|
|
56
|
+
resolveReactHostEdit(nativeProjectPaths.android),
|
|
57
|
+
].filter(edit => edit !== undefined);
|
|
58
|
+
}
|
|
59
|
+
// React Native's own builds prefer `index.<platform>.js` over `index.js`.
|
|
60
|
+
function resolveBundlerArgs(projectDirectoryPath, platform) {
|
|
61
|
+
const platformEntryFileName = `index.${platform}.js`;
|
|
62
|
+
return [
|
|
63
|
+
'react-native',
|
|
64
|
+
'bundle',
|
|
65
|
+
'--entry-file',
|
|
66
|
+
existsSync(join(projectDirectoryPath, platformEntryFileName))
|
|
67
|
+
? platformEntryFileName
|
|
68
|
+
: 'index.js',
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
function resolveEdits(nativeProjectPaths) {
|
|
72
|
+
return [
|
|
73
|
+
resolveGradleEdit(nativeProjectPaths.android, REACT_NATIVE_PACKAGE_NAME),
|
|
74
|
+
...resolveBundleWiringEdits(nativeProjectPaths),
|
|
75
|
+
].filter(edit => edit !== undefined);
|
|
76
|
+
}
|
|
77
|
+
async function resolveWiring({ directoryPath, packageJson }, options) {
|
|
78
|
+
const nativeProjects = await resolveNativeProjects(directoryPath, resolveNativeProjectPaths(directoryPath), options, 'Pass --ios-path and --android-path.');
|
|
79
|
+
const isPackageInstalled = isSdkPackageDeclared(packageJson, REACT_NATIVE_PACKAGE_NAME);
|
|
80
|
+
const xcodeProjectFilePath = resolveXcodeProjectFilePath(nativeProjects.ios);
|
|
81
|
+
return {
|
|
82
|
+
isPackageInstalled,
|
|
83
|
+
nativeFilePaths: [
|
|
84
|
+
...(xcodeProjectFilePath === undefined ||
|
|
85
|
+
hasBinaryCreatePhase(xcodeProjectFilePath)
|
|
86
|
+
? []
|
|
87
|
+
: [xcodeProjectFilePath]),
|
|
88
|
+
...resolveEdits(nativeProjects)
|
|
89
|
+
.filter(edit => !edit.isRefused && !edit.isApplied())
|
|
90
|
+
.map(({ filePath }) => filePath),
|
|
91
|
+
].map(filePath => relative(directoryPath, filePath)),
|
|
92
|
+
packageFilePaths: isPackageInstalled ? [] : ['package.json'],
|
|
93
|
+
installPackage: () => installSdkPackage(directoryPath, REACT_NATIVE_PACKAGE_NAME, REACT_NATIVE_PACKAGE_SPEC),
|
|
94
|
+
wireBinaryCreateStep: editBlocker => wireBinaryCreateStep(nativeProjects, xcodeProjectFilePath, editBlocker, options),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function wireBinaryCreateStep(nativeProjects, xcodeProjectFilePath, editBlocker, options) {
|
|
98
|
+
if (nativeProjects.missingError !== undefined) {
|
|
99
|
+
throw nativeProjects.missingError;
|
|
100
|
+
}
|
|
101
|
+
const isPhaseWired = xcodeProjectFilePath === undefined ||
|
|
102
|
+
hasBinaryCreatePhase(xcodeProjectFilePath);
|
|
103
|
+
const pendingEdits = resolveEdits(nativeProjects).filter(edit => !edit.isApplied());
|
|
104
|
+
const arePodsInstalled = isPodInstalled(nativeProjects.ios);
|
|
105
|
+
if (isPhaseWired && pendingEdits.length === 0 && arePodsInstalled) {
|
|
106
|
+
return {
|
|
107
|
+
message: 'The build step and the bundle wiring are already wired.',
|
|
108
|
+
status: 'skipped',
|
|
109
|
+
value: undefined,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (editBlocker !== undefined && (!isPhaseWired || pendingEdits.length > 0)) {
|
|
113
|
+
throw editBlocker;
|
|
114
|
+
}
|
|
115
|
+
const wired = await applyNativeProjectEdits(xcodeProjectFilePath === undefined
|
|
116
|
+
? undefined
|
|
117
|
+
: { phase: BINARY_CREATE_PHASE, projectFilePath: xcodeProjectFilePath }, pendingEdits, options);
|
|
118
|
+
if (!arePodsInstalled) {
|
|
119
|
+
runCommandLineVisibly({ args: ['install'], command: 'pod' }, nativeProjects.ios);
|
|
120
|
+
wired.push('the pods through "pod install"');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
message: `Wired ${wired.join(', ')}.`,
|
|
124
|
+
status: 'done',
|
|
125
|
+
value: undefined,
|
|
126
|
+
};
|
|
127
|
+
}
|