@expo-harmony/cli 55.0.26-harmony.7 → 55.0.26-harmony.9
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/README.md +74 -46
- package/build/bin/expo-harmony.js +1 -1
- package/build/buildHap/build.d.ts +3 -10
- package/build/buildHap/build.js +11 -69
- package/build/buildHap/index.d.ts +2 -0
- package/build/buildHap/index.js +19 -0
- package/build/cli.d.ts +3 -3
- package/build/cli.js +21 -187
- package/build/command.d.ts +9 -0
- package/build/command.js +20 -0
- package/build/doctor/doctor.d.ts +1 -1
- package/build/doctor/doctor.js +64 -56
- package/build/doctor/index.d.ts +2 -0
- package/build/doctor/index.js +16 -0
- package/build/exportEmbed/export.d.ts +2 -2
- package/build/exportEmbed/export.js +53 -49
- package/build/exportEmbed/index.d.ts +2 -0
- package/build/exportEmbed/index.js +19 -0
- package/build/file.d.ts +2 -1
- package/build/file.js +18 -8
- package/build/help.d.ts +1 -0
- package/build/help.js +36 -0
- package/build/index.d.ts +5 -3
- package/build/index.js +6 -5
- package/build/internal/prebuild.d.ts +3 -0
- package/build/internal/prebuild.js +9 -0
- package/build/log.d.ts +4 -0
- package/build/log.js +6 -0
- package/build/modules/index.d.ts +2 -0
- package/build/modules/index.js +16 -0
- package/build/native/bare.d.ts +4 -0
- package/build/native/bare.js +130 -0
- package/build/native/build.d.ts +15 -0
- package/build/native/build.js +67 -0
- package/build/{run → native}/cache.d.ts +2 -2
- package/build/{run → native}/cache.js +8 -8
- package/build/native/hvigor.d.ts +2 -0
- package/build/native/hvigor.js +77 -0
- package/build/native/install.d.ts +5 -0
- package/build/native/install.js +13 -0
- package/build/native/prepareProject.d.ts +11 -0
- package/build/native/prepareProject.js +68 -0
- package/build/native/project.d.ts +8 -0
- package/build/native/project.js +58 -0
- package/build/native/toolchain.d.ts +16 -0
- package/build/{tools.js → native/toolchain.js} +1 -57
- package/build/native/types.d.ts +47 -0
- package/build/native/types.js +2 -0
- package/build/prebuild/check.d.ts +7 -3
- package/build/prebuild/check.js +26 -20
- package/build/prebuild/clean.js +5 -5
- package/build/prebuild/index.d.ts +2 -0
- package/build/prebuild/index.js +51 -0
- package/build/prebuild/prebuild.js +11 -3
- package/build/prebuild/template.js +2 -2
- package/build/process.d.ts +11 -1
- package/build/process.js +16 -0
- package/build/profile.d.ts +1 -0
- package/build/profile.js +12 -0
- package/build/projectLock.js +7 -2
- package/build/run/devices.d.ts +1 -1
- package/build/run/devices.js +15 -10
- package/build/run/emulators.d.ts +1 -1
- package/build/run/emulators.js +2 -5
- package/build/run/index.d.ts +2 -0
- package/build/run/index.js +25 -0
- package/build/run/metro.js +1 -1
- package/build/run/run.d.ts +4 -10
- package/build/run/run.js +45 -90
- package/build/start/index.d.ts +2 -0
- package/build/start/index.js +33 -0
- package/package.json +14 -4
- package/src/bin/expo-harmony.ts +2 -2
- package/src/buildHap/build.ts +13 -105
- package/src/buildHap/index.ts +19 -0
- package/src/cli.ts +24 -227
- package/src/command.ts +24 -0
- package/src/doctor/doctor.ts +63 -61
- package/src/doctor/index.ts +16 -0
- package/src/exportEmbed/export.ts +64 -55
- package/src/exportEmbed/index.ts +17 -0
- package/src/file.ts +20 -11
- package/src/help.ts +33 -0
- package/src/index.ts +5 -3
- package/src/internal/prebuild.ts +3 -0
- package/src/log.ts +5 -0
- package/src/modules/index.ts +16 -0
- package/src/native/bare.ts +145 -0
- package/src/native/build.ts +81 -0
- package/src/{run → native}/cache.ts +10 -9
- package/src/native/hvigor.ts +81 -0
- package/src/native/install.ts +17 -0
- package/src/native/prepareProject.ts +99 -0
- package/src/native/project.ts +88 -0
- package/src/{tools.ts → native/toolchain.ts} +11 -118
- package/src/native/types.ts +44 -0
- package/src/prebuild/check.ts +30 -19
- package/src/prebuild/clean.ts +3 -3
- package/src/prebuild/index.ts +56 -0
- package/src/prebuild/prebuild.ts +11 -1
- package/src/prebuild/template.ts +2 -2
- package/src/process.ts +38 -1
- package/src/profile.ts +13 -0
- package/src/projectLock.ts +9 -1
- package/src/run/devices.ts +16 -11
- package/src/run/emulators.ts +4 -6
- package/src/run/index.ts +25 -0
- package/src/run/metro.ts +1 -1
- package/src/run/run.ts +56 -129
- package/src/start/index.ts +31 -0
- package/build/buildHap/common.d.ts +0 -24
- package/build/buildHap/common.js +0 -92
- package/build/run/install.d.ts +0 -7
- package/build/run/install.js +0 -26
- package/build/tools.d.ts +0 -39
- package/src/buildHap/common.ts +0 -145
- package/src/run/install.ts +0 -41
package/build/command.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseCommandArgs = parseCommandArgs;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const help_1 = require("./help");
|
|
9
|
+
const project_1 = require("./project");
|
|
10
|
+
function parseCommandArgs(parse, argv, io) {
|
|
11
|
+
const options = parse(argv);
|
|
12
|
+
if (options.help) {
|
|
13
|
+
io.log(help_1.Help);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
options,
|
|
18
|
+
projectRoot: (0, project_1.resolveProject)(options.project ? node_path_1.default.resolve(options.project) : process.cwd()),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/build/doctor/doctor.d.ts
CHANGED
|
@@ -15,6 +15,6 @@ interface DoctorOptions {
|
|
|
15
15
|
validateGeneratedProject?: boolean;
|
|
16
16
|
validateModules?: boolean;
|
|
17
17
|
}
|
|
18
|
-
declare function doctorAsync(
|
|
18
|
+
declare function doctorAsync(root: string, options?: DoctorOptions): Promise<DoctorResult>;
|
|
19
19
|
declare function formatDoctor(result: DoctorResult): string;
|
|
20
20
|
export { doctorAsync, formatDoctor };
|
package/build/doctor/doctor.js
CHANGED
|
@@ -11,27 +11,28 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
11
11
|
const config_1 = require("@expo/config");
|
|
12
12
|
const config_plugins_1 = require("@expo-harmony/config-plugins");
|
|
13
13
|
const expo_modules_autolinking_1 = require("@expo-harmony/expo-modules-autolinking");
|
|
14
|
-
const
|
|
15
|
-
const signing_1 = require("@expo-harmony/prebuild-config/signing");
|
|
14
|
+
const internal_1 = require("@expo-harmony/prebuild-config/internal");
|
|
16
15
|
const process_1 = require("../process");
|
|
17
16
|
const projectLock_1 = require("../projectLock");
|
|
18
|
-
const
|
|
17
|
+
const project_1 = require("../native/project");
|
|
18
|
+
const toolchain_1 = require("../native/toolchain");
|
|
19
19
|
const upstream_1 = require("../upstream");
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const bare_1 = require("../native/bare");
|
|
21
|
+
function hasPlugin(plugins, name) {
|
|
22
|
+
return (plugins || []).some(plugin => (Array.isArray(plugin) ? plugin[0] : plugin) === name);
|
|
22
23
|
}
|
|
23
24
|
function check(id, status, message, details) {
|
|
24
25
|
return { id, status, message, ...(details ? { details } : {}) };
|
|
25
26
|
}
|
|
26
|
-
function canResolvePackage(
|
|
27
|
-
const
|
|
27
|
+
function canResolvePackage(root, name) {
|
|
28
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(root, 'package.json'));
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
+
require.resolve(`${name}/package.json`);
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
catch {
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
+
require.resolve(name);
|
|
35
36
|
return true;
|
|
36
37
|
}
|
|
37
38
|
catch {
|
|
@@ -39,19 +40,19 @@ function canResolvePackage(projectRoot, packageName) {
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
async function validateMetroConfigAsync(
|
|
43
|
-
const
|
|
44
|
-
let
|
|
43
|
+
async function validateMetroConfigAsync(root) {
|
|
44
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(root, 'package.json'));
|
|
45
|
+
let metro;
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
+
metro = require('metro-config');
|
|
47
48
|
}
|
|
48
49
|
catch (cause) {
|
|
49
50
|
throw new Error('Cannot load the project-local metro-config package.', { cause });
|
|
50
51
|
}
|
|
51
|
-
if (typeof
|
|
52
|
+
if (typeof metro.resolveConfig !== 'function') {
|
|
52
53
|
const version = (() => {
|
|
53
54
|
try {
|
|
54
|
-
return
|
|
55
|
+
return require('metro-config/package.json').version;
|
|
55
56
|
}
|
|
56
57
|
catch {
|
|
57
58
|
return 'unknown';
|
|
@@ -59,16 +60,16 @@ async function validateMetroConfigAsync(projectRoot) {
|
|
|
59
60
|
})();
|
|
60
61
|
throw new Error(`metro-config ${version} does not expose resolveConfig().`);
|
|
61
62
|
}
|
|
62
|
-
const
|
|
63
|
-
const
|
|
63
|
+
const present = Object.hasOwn(process.env, 'EXPO_METRO_TARGET');
|
|
64
|
+
const previous = process.env.EXPO_METRO_TARGET;
|
|
64
65
|
let resolved;
|
|
65
66
|
try {
|
|
66
67
|
process.env.EXPO_METRO_TARGET = 'harmony';
|
|
67
|
-
resolved = await
|
|
68
|
+
resolved = await metro.resolveConfig(undefined, root);
|
|
68
69
|
}
|
|
69
70
|
finally {
|
|
70
|
-
if (
|
|
71
|
-
process.env.EXPO_METRO_TARGET =
|
|
71
|
+
if (present)
|
|
72
|
+
process.env.EXPO_METRO_TARGET = previous;
|
|
72
73
|
else
|
|
73
74
|
delete process.env.EXPO_METRO_TARGET;
|
|
74
75
|
}
|
|
@@ -81,20 +82,27 @@ async function validateMetroConfigAsync(projectRoot) {
|
|
|
81
82
|
throw new Error('The resolved Metro config must register the harmony platform, Harmony conditions, and a resolver.');
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
async function doctorUnlockedAsync(
|
|
85
|
+
async function doctorUnlockedAsync(root, options = {}) {
|
|
85
86
|
const checks = [];
|
|
86
|
-
const
|
|
87
|
+
const unavailable = options.requireBuildTools ? 'error' : 'warn';
|
|
87
88
|
let config;
|
|
89
|
+
const bare = (0, bare_1.isBareHarmonyProject)(root);
|
|
88
90
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
if (bare) {
|
|
92
|
+
const plan = await (0, project_1.resolveHarmonyBuildPlanIfPresentAsync)(root);
|
|
93
|
+
checks.push(check('native-config', 'pass', `Bare Harmony project: ${plan.bundleName} / ${plan.moduleName}. Native files own the configuration.`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
config = (0, config_1.getConfig)(root, {
|
|
97
|
+
isModdedConfig: true,
|
|
98
|
+
skipSDKVersionRequirement: true,
|
|
99
|
+
}).exp;
|
|
100
|
+
(0, config_plugins_1.normalizeHarmonyConfig)(config);
|
|
101
|
+
checks.push(check('app-config', 'pass', 'Harmony app config is valid.'));
|
|
102
|
+
}
|
|
95
103
|
}
|
|
96
104
|
catch (error) {
|
|
97
|
-
checks.push(check('app-config', 'error', error.message, { code: error.code || 'ERR_HARMONY_CONFIG_INVALID' }));
|
|
105
|
+
checks.push(check(bare ? 'native-config' : 'app-config', 'error', error.message, { code: error.code || 'ERR_HARMONY_CONFIG_INVALID' }));
|
|
98
106
|
}
|
|
99
107
|
if (config) {
|
|
100
108
|
checks.push(hasPlugin(config.plugins, '@expo-harmony/prebuild-config')
|
|
@@ -103,7 +111,7 @@ async function doctorUnlockedAsync(projectRoot, options = {}) {
|
|
|
103
111
|
const harmony = config.harmony;
|
|
104
112
|
if (harmony?.signingConfigFile) {
|
|
105
113
|
try {
|
|
106
|
-
const signing = await (0,
|
|
114
|
+
const signing = await (0, internal_1.validateHarmonySigningConfigFile)(root, harmony.signingConfigFile);
|
|
107
115
|
checks.push(check('signing', 'pass', `Harmony signing config ${signing.name} is valid.`));
|
|
108
116
|
}
|
|
109
117
|
catch (error) {
|
|
@@ -115,23 +123,23 @@ async function doctorUnlockedAsync(projectRoot, options = {}) {
|
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
try {
|
|
118
|
-
await validateMetroConfigAsync(
|
|
126
|
+
await validateMetroConfigAsync(root);
|
|
119
127
|
checks.push(check('metro', 'pass', 'The resolved Metro config enables Harmony.'));
|
|
120
128
|
}
|
|
121
129
|
catch (error) {
|
|
122
130
|
checks.push(check('metro', 'error', `Cannot load a Harmony-enabled Metro config: ${error.message}`, { code: error.code || 'ERR_HARMONY_METRO_CONFIG' }));
|
|
123
131
|
}
|
|
124
|
-
for (const
|
|
125
|
-
if (canResolvePackage(
|
|
126
|
-
checks.push(check(`package:${
|
|
132
|
+
for (const name of upstream_1.RequiredProjectPackages) {
|
|
133
|
+
if (canResolvePackage(root, name)) {
|
|
134
|
+
checks.push(check(`package:${name}`, 'pass', `${name} is resolvable.`));
|
|
127
135
|
}
|
|
128
136
|
else {
|
|
129
|
-
checks.push(check(`package:${
|
|
137
|
+
checks.push(check(`package:${name}`, 'error', `${name} is not resolvable from the app.`));
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
if (options.validateModules !== false) {
|
|
133
141
|
try {
|
|
134
|
-
const result = await (0, expo_modules_autolinking_1.verifyModulesAsync)({ platform: 'harmony', projectRoot });
|
|
142
|
+
const result = await (0, expo_modules_autolinking_1.verifyModulesAsync)({ platform: 'harmony', projectRoot: root });
|
|
135
143
|
const errors = result.diagnostics.filter(item => item.severity === 'error');
|
|
136
144
|
const warnings = result.diagnostics.filter(item => item.severity === 'warning');
|
|
137
145
|
if (errors.length > 0) {
|
|
@@ -148,52 +156,52 @@ async function doctorUnlockedAsync(projectRoot, options = {}) {
|
|
|
148
156
|
checks.push(check('expo-modules', 'error', `Harmony Expo Modules verification failed: ${error.message}`, { code: error.code || 'ERR_HARMONY_AUTOLINKING_FAILED' }));
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
|
-
const toolchain = (0,
|
|
152
|
-
let
|
|
159
|
+
const toolchain = (0, toolchain_1.resolveHarmonyToolchain)();
|
|
160
|
+
let sdk;
|
|
153
161
|
if (toolchain.sdkHome) {
|
|
154
|
-
|
|
162
|
+
sdk = check('harmony-sdk', 'pass', `Complete Harmony SDK root was resolved at ${toolchain.sdkHome}.`);
|
|
155
163
|
}
|
|
156
164
|
else {
|
|
157
|
-
|
|
165
|
+
sdk = check('harmony-sdk', unavailable, 'No complete Harmony SDK root with HMS and OpenHarmony components was found; generation works but HAP build cannot be verified.');
|
|
158
166
|
}
|
|
159
|
-
checks.push(
|
|
167
|
+
checks.push(sdk);
|
|
160
168
|
const tools = [
|
|
161
|
-
['ohpm', toolchain.ohpm, ['--version'],
|
|
162
|
-
['hvigor-command', toolchain.hvigor, ['--version'],
|
|
169
|
+
['ohpm', toolchain.ohpm, ['--version'], unavailable],
|
|
170
|
+
['hvigor-command', toolchain.hvigor, ['--version'], unavailable],
|
|
163
171
|
];
|
|
164
172
|
if (options.requireDeviceTools !== false) {
|
|
165
|
-
tools.unshift(['hdc', toolchain.hdc, ['-v'],
|
|
173
|
+
tools.unshift(['hdc', toolchain.hdc, ['-v'], unavailable]);
|
|
166
174
|
}
|
|
167
|
-
for (const [id, tool,
|
|
175
|
+
for (const [id, tool, args, status] of tools) {
|
|
168
176
|
const command = [tool.command, ...tool.args].map(value => JSON.stringify(value)).join(' ');
|
|
169
177
|
try {
|
|
170
|
-
const result = await (0, process_1.spawnAsync)(tool.command, [...tool.args, ...
|
|
178
|
+
const result = await (0, process_1.spawnAsync)(tool.command, [...tool.args, ...args], {
|
|
171
179
|
capture: true,
|
|
172
|
-
cwd:
|
|
180
|
+
cwd: root,
|
|
173
181
|
operation: `doctor-${id}`,
|
|
174
182
|
timeoutMs: 10_000,
|
|
175
183
|
});
|
|
176
184
|
checks.push(result.code === 0 && !result.timedOut
|
|
177
185
|
? check(id, 'pass', `${command} is available through ${tool.source}.`)
|
|
178
|
-
: check(id,
|
|
186
|
+
: check(id, status, `${command} is unavailable or unhealthy; HAP build cannot be verified.`));
|
|
179
187
|
}
|
|
180
188
|
catch {
|
|
181
|
-
checks.push(check(id,
|
|
189
|
+
checks.push(check(id, status, `${command} is unavailable; generation remains available.`));
|
|
182
190
|
}
|
|
183
191
|
}
|
|
184
192
|
if (options.validateGeneratedProject !== false) {
|
|
185
193
|
try {
|
|
186
|
-
const plan = await (0,
|
|
194
|
+
const plan = await (0, project_1.resolveHarmonyBuildPlanIfPresentAsync)(root);
|
|
187
195
|
if (plan && node_fs_1.default.existsSync(plan.harmonyRoot)) {
|
|
188
196
|
if (!node_fs_1.default.existsSync(plan.projectFiles.rootHvigor)
|
|
189
197
|
|| !node_fs_1.default.existsSync(plan.projectFiles.moduleHvigor)) {
|
|
190
|
-
checks.push(check('hvigor', 'error', '
|
|
198
|
+
checks.push(check('hvigor', 'error', 'Root and module Hvigor files are required.'));
|
|
191
199
|
}
|
|
192
200
|
else {
|
|
193
201
|
const content = await node_fs_1.default.promises.readFile(plan.projectFiles.moduleHvigor, 'utf8');
|
|
194
|
-
checks.push((0,
|
|
202
|
+
checks.push((0, internal_1.isRnohAutolinkingDisabled)(content)
|
|
195
203
|
? check('hvigor-autolinking', 'pass', 'RNOH duplicate autolinking is disabled.')
|
|
196
|
-
: check('hvigor-autolinking', 'error', 'The
|
|
204
|
+
: check('hvigor-autolinking', 'error', 'The module Hvigor file must disable duplicate RNOH autolinking.'));
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
}
|
|
@@ -204,11 +212,11 @@ async function doctorUnlockedAsync(projectRoot, options = {}) {
|
|
|
204
212
|
return {
|
|
205
213
|
checks,
|
|
206
214
|
ok: checks.every(item => item.status !== 'error'),
|
|
207
|
-
projectRoot,
|
|
215
|
+
projectRoot: root,
|
|
208
216
|
};
|
|
209
217
|
}
|
|
210
|
-
async function doctorAsync(
|
|
211
|
-
return (0, projectLock_1.withHarmonyProjectLockAsync)(
|
|
218
|
+
async function doctorAsync(root, options = {}) {
|
|
219
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(root, 'doctor', () => doctorUnlockedAsync(root, options));
|
|
212
220
|
}
|
|
213
221
|
function formatDoctor(result) {
|
|
214
222
|
return result.checks.map((item) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const options_1 = require("./options");
|
|
6
|
+
const command = async (argv, io) => {
|
|
7
|
+
const args = (0, command_1.parseCommandArgs)(options_1.parseDoctorArgs, argv, io);
|
|
8
|
+
if (!args)
|
|
9
|
+
return 0;
|
|
10
|
+
const { projectRoot: root } = args;
|
|
11
|
+
const { doctorAsync, formatDoctor } = await import('./doctor.js');
|
|
12
|
+
const result = await doctorAsync(root, { requireBuildTools: true });
|
|
13
|
+
io.log(formatDoctor(result));
|
|
14
|
+
return result.ok ? 0 : 1;
|
|
15
|
+
};
|
|
16
|
+
exports.command = command;
|
|
@@ -12,5 +12,5 @@ export interface ExportTemporary {
|
|
|
12
12
|
metroSourceMap: string;
|
|
13
13
|
sourceMap: string;
|
|
14
14
|
}
|
|
15
|
-
declare function exportEmbedAsync(
|
|
16
|
-
export { exportEmbedAsync
|
|
15
|
+
declare function exportEmbedAsync(root: string, options?: ExportOptions): Promise<HarmonyExportManifest>;
|
|
16
|
+
export { exportEmbedAsync };
|
|
@@ -12,57 +12,56 @@ const doctor_1 = require("../doctor/doctor");
|
|
|
12
12
|
const entry_1 = require("../entry");
|
|
13
13
|
const errors_1 = require("../errors");
|
|
14
14
|
const projectLock_1 = require("../projectLock");
|
|
15
|
-
const
|
|
15
|
+
const project_1 = require("../native/project");
|
|
16
16
|
const process_1 = require("../process");
|
|
17
17
|
const expo_1 = require("../expo");
|
|
18
18
|
const manifest_1 = require("./manifest");
|
|
19
|
-
function createExpoExportEmbedArgs(
|
|
19
|
+
function createExpoExportEmbedArgs(root, entry, temp, options = {}) {
|
|
20
20
|
return [
|
|
21
21
|
'export:embed',
|
|
22
22
|
'--platform', 'harmony',
|
|
23
|
-
'--entry-file',
|
|
24
|
-
'--bundle-output',
|
|
25
|
-
'--assets-dest',
|
|
23
|
+
'--entry-file', entry,
|
|
24
|
+
'--bundle-output', temp.javascript,
|
|
25
|
+
'--assets-dest', temp.assets,
|
|
26
26
|
'--dev', 'false',
|
|
27
27
|
'--minify', 'false',
|
|
28
|
-
'--sourcemap-output',
|
|
28
|
+
'--sourcemap-output', temp.metroSourceMap,
|
|
29
29
|
'--sourcemap-sources-root', '.',
|
|
30
30
|
'--unstable-transform-profile', 'hermes-stable',
|
|
31
31
|
...(options.resetCache ? ['--reset-cache=true'] : []),
|
|
32
|
-
|
|
32
|
+
root,
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
|
-
function
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const plan = await (0, tools_1.resolveHarmonyBuildPlanAsync)(projectRoot, { buildMode: 'release' });
|
|
35
|
+
async function exportEmbedUnlockedAsync(root, options = {}) {
|
|
36
|
+
if (!options.skipDoctor) {
|
|
37
|
+
const doctor = await (0, doctor_1.doctorAsync)(root);
|
|
38
|
+
if (!doctor.ok) {
|
|
39
|
+
const checks = doctor.checks.filter(check => check.status === 'error').map(check => check.id);
|
|
40
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_DOCTOR', `Harmony doctor found blocking checks: ${checks.join(', ') || 'unknown'}.`, { operation: 'doctor' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const plan = await (0, project_1.resolveHarmonyBuildPlanAsync)(root, { buildMode: 'release' });
|
|
45
44
|
const paths = (0, manifest_1.exportPaths)(plan);
|
|
46
45
|
if (options.check)
|
|
47
46
|
return await (0, manifest_1.validatePublishedExportAsync)(paths);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
assets: node_path_1.default.join(
|
|
52
|
-
bundle: node_path_1.default.join(
|
|
53
|
-
javascript: node_path_1.default.join(
|
|
54
|
-
metroSourceMap: node_path_1.default.join(
|
|
55
|
-
sourceMap: node_path_1.default.join(
|
|
47
|
+
const entry = (0, entry_1.resolveHarmonyEntryPoint)(root);
|
|
48
|
+
const directory = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'expo-harmony-export-'));
|
|
49
|
+
const temp = {
|
|
50
|
+
assets: node_path_1.default.join(directory, 'assets'),
|
|
51
|
+
bundle: node_path_1.default.join(directory, 'hermes_bundle.hbc'),
|
|
52
|
+
javascript: node_path_1.default.join(directory, 'index.js'),
|
|
53
|
+
metroSourceMap: node_path_1.default.join(directory, 'index.js.map'),
|
|
54
|
+
sourceMap: node_path_1.default.join(directory, 'hermes_bundle.hbc.map'),
|
|
56
55
|
};
|
|
57
56
|
try {
|
|
58
|
-
await node_fs_1.default.promises.mkdir(
|
|
59
|
-
const expo = (0, expo_1.resolveExpoCli)(
|
|
57
|
+
await node_fs_1.default.promises.mkdir(temp.assets, { recursive: true });
|
|
58
|
+
const expo = (0, expo_1.resolveExpoCli)(root);
|
|
60
59
|
const result = await (0, process_1.spawnAsync)(process.execPath, [
|
|
61
60
|
expo.cliPath,
|
|
62
|
-
...createExpoExportEmbedArgs(
|
|
61
|
+
...createExpoExportEmbedArgs(root, entry, temp, options),
|
|
63
62
|
], {
|
|
64
63
|
capture: true,
|
|
65
|
-
cwd:
|
|
64
|
+
cwd: root,
|
|
66
65
|
env: {
|
|
67
66
|
...process.env,
|
|
68
67
|
EXPO_METRO_TARGET: 'harmony',
|
|
@@ -81,45 +80,50 @@ async function exportEmbedUnlockedAsync(projectRoot, options = {}) {
|
|
|
81
80
|
// then explicitly hand its JS and source map to Expo's own Hermes exporter.
|
|
82
81
|
try {
|
|
83
82
|
const [code, map] = await Promise.all([
|
|
84
|
-
node_fs_1.default.promises.readFile(
|
|
85
|
-
node_fs_1.default.promises.readFile(
|
|
83
|
+
node_fs_1.default.promises.readFile(temp.javascript, 'utf8'),
|
|
84
|
+
node_fs_1.default.promises.readFile(temp.metroSourceMap, 'utf8'),
|
|
86
85
|
]);
|
|
87
|
-
const buildHermesBundleAsync = (0, expo_1.resolveExpoHermesBuilder)(
|
|
86
|
+
const buildHermesBundleAsync = (0, expo_1.resolveExpoHermesBuilder)(root);
|
|
88
87
|
// Expo resolves hermes-compiler from react-native. For Harmony, resolve it
|
|
89
88
|
// from RNOH instead of the app's Android/iOS React Native installation.
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
await node_fs_1.default.promises.mkdir(
|
|
94
|
-
await node_fs_1.default.promises.symlink(
|
|
89
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(root, 'package.json'));
|
|
90
|
+
const runtime = node_path_1.default.dirname(require.resolve('@react-native-oh/react-native-harmony/package.json'));
|
|
91
|
+
const modules = node_path_1.default.join(directory, 'node_modules');
|
|
92
|
+
await node_fs_1.default.promises.mkdir(modules);
|
|
93
|
+
await node_fs_1.default.promises.symlink(runtime, node_path_1.default.join(modules, 'react-native'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
95
94
|
const output = await buildHermesBundleAsync({
|
|
96
95
|
code,
|
|
97
|
-
filename:
|
|
96
|
+
filename: entry,
|
|
98
97
|
map,
|
|
99
98
|
minify: true,
|
|
100
|
-
projectRoot:
|
|
99
|
+
projectRoot: directory,
|
|
101
100
|
});
|
|
102
101
|
if (!(output?.hbc instanceof Uint8Array) || typeof output.sourcemap !== 'string') {
|
|
103
102
|
throw new Error('Expo did not return Hermes bytecode and a composed source map.');
|
|
104
103
|
}
|
|
105
104
|
await Promise.all([
|
|
106
|
-
node_fs_1.default.promises.writeFile(
|
|
107
|
-
node_fs_1.default.promises.writeFile(
|
|
105
|
+
node_fs_1.default.promises.writeFile(temp.bundle, output.hbc),
|
|
106
|
+
node_fs_1.default.promises.writeFile(temp.sourceMap, output.sourcemap),
|
|
108
107
|
]);
|
|
109
108
|
}
|
|
110
109
|
catch (cause) {
|
|
111
|
-
if (cause instanceof errors_1.HarmonyCliError)
|
|
112
|
-
throw cause
|
|
110
|
+
if (cause instanceof errors_1.HarmonyCliError) {
|
|
111
|
+
throw new errors_1.HarmonyCliError(cause.code, cause.message, {
|
|
112
|
+
cause,
|
|
113
|
+
exitCode: cause.exitCode,
|
|
114
|
+
operation: cause.operation,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
113
117
|
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_HERMES', 'Expo failed to compile the Harmony bundle to Hermes bytecode.', { cause, operation: 'expo-hermes-export' });
|
|
114
118
|
}
|
|
115
|
-
const bytecode = await (0, manifest_1.assertHermesBundle)(
|
|
116
|
-
await (0, manifest_1.assertSourceMap)(
|
|
117
|
-
return await (0, manifest_1.publishExportAsync)(
|
|
119
|
+
const bytecode = await (0, manifest_1.assertHermesBundle)(temp.bundle);
|
|
120
|
+
await (0, manifest_1.assertSourceMap)(temp.sourceMap);
|
|
121
|
+
return await (0, manifest_1.publishExportAsync)(root, paths, temp, entry, bytecode);
|
|
118
122
|
}
|
|
119
123
|
finally {
|
|
120
|
-
await node_fs_1.default.promises.rm(
|
|
124
|
+
await node_fs_1.default.promises.rm(directory, { force: true, recursive: true });
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
|
-
async function exportEmbedAsync(
|
|
124
|
-
return (0, projectLock_1.withHarmonyProjectLockAsync)(
|
|
127
|
+
async function exportEmbedAsync(root, options = {}) {
|
|
128
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(root, 'export:embed', () => exportEmbedUnlockedAsync(root, options));
|
|
125
129
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const options_1 = require("./options");
|
|
6
|
+
const command = async (argv, io) => {
|
|
7
|
+
const args = (0, command_1.parseCommandArgs)(options_1.parseExportEmbedArgs, argv, io);
|
|
8
|
+
if (!args)
|
|
9
|
+
return 0;
|
|
10
|
+
const { projectRoot: root, options } = args;
|
|
11
|
+
const { exportEmbedAsync } = await import('./export.js');
|
|
12
|
+
const result = await exportEmbedAsync(root, options);
|
|
13
|
+
if (options.check)
|
|
14
|
+
io.log('Harmony export bundle, assets, and source map are valid.');
|
|
15
|
+
else
|
|
16
|
+
io.log(`Exported Hermes bytecode ${result.bundle.path} with ${result.assets.length} asset file(s).`);
|
|
17
|
+
return 0;
|
|
18
|
+
};
|
|
19
|
+
exports.command = command;
|
package/build/file.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ declare function listFiles(root: string): Promise<string[]>;
|
|
|
6
6
|
declare function atomicCopy(source: string, destination: string, root: string): Promise<void>;
|
|
7
7
|
declare function atomicWriteJson(value: unknown, destination: string, root: string): Promise<void>;
|
|
8
8
|
declare function removeEmptyParents(file: string, stop: string): Promise<void>;
|
|
9
|
-
|
|
9
|
+
declare function isNonEmptyRegularFile(file: string): boolean;
|
|
10
|
+
export { atomicCopy, atomicWriteJson, describeFile, isNonEmptyRegularFile, listFiles, removeEmptyParents };
|
package/build/file.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.atomicCopy = atomicCopy;
|
|
7
7
|
exports.atomicWriteJson = atomicWriteJson;
|
|
8
8
|
exports.describeFile = describeFile;
|
|
9
|
+
exports.isNonEmptyRegularFile = isNonEmptyRegularFile;
|
|
9
10
|
exports.listFiles = listFiles;
|
|
10
11
|
exports.removeEmptyParents = removeEmptyParents;
|
|
11
12
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
@@ -66,24 +67,24 @@ async function ensureSafeParent(root, destination) {
|
|
|
66
67
|
}
|
|
67
68
|
async function atomicCopy(source, destination, root) {
|
|
68
69
|
await ensureSafeParent(root, destination);
|
|
69
|
-
const
|
|
70
|
+
const temp = node_path_1.default.join(node_path_1.default.dirname(destination), `.${node_path_1.default.basename(destination)}.expo-${node_crypto_1.default.randomUUID()}.tmp`);
|
|
70
71
|
try {
|
|
71
|
-
await node_fs_1.default.promises.copyFile(source,
|
|
72
|
-
await node_fs_1.default.promises.rename(
|
|
72
|
+
await node_fs_1.default.promises.copyFile(source, temp, node_fs_1.default.constants.COPYFILE_EXCL);
|
|
73
|
+
await node_fs_1.default.promises.rename(temp, destination);
|
|
73
74
|
}
|
|
74
75
|
finally {
|
|
75
|
-
await node_fs_1.default.promises.rm(
|
|
76
|
+
await node_fs_1.default.promises.rm(temp, { force: true });
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
async function atomicWriteJson(value, destination, root) {
|
|
79
80
|
await ensureSafeParent(root, destination);
|
|
80
|
-
const
|
|
81
|
+
const temp = node_path_1.default.join(node_path_1.default.dirname(destination), `.${node_path_1.default.basename(destination)}.expo-${node_crypto_1.default.randomUUID()}.tmp`);
|
|
81
82
|
try {
|
|
82
|
-
await node_fs_1.default.promises.writeFile(
|
|
83
|
-
await node_fs_1.default.promises.rename(
|
|
83
|
+
await node_fs_1.default.promises.writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' });
|
|
84
|
+
await node_fs_1.default.promises.rename(temp, destination);
|
|
84
85
|
}
|
|
85
86
|
finally {
|
|
86
|
-
await node_fs_1.default.promises.rm(
|
|
87
|
+
await node_fs_1.default.promises.rm(temp, { force: true });
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
async function removeEmptyParents(file, stop) {
|
|
@@ -98,3 +99,12 @@ async function removeEmptyParents(file, stop) {
|
|
|
98
99
|
directory = node_path_1.default.dirname(directory);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
function isNonEmptyRegularFile(file) {
|
|
103
|
+
try {
|
|
104
|
+
const stat = node_fs_1.default.lstatSync(file);
|
|
105
|
+
return !stat.isSymbolicLink() && stat.isFile() && stat.size > 0;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/build/help.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const Help = "Usage: expo-harmony <command> [project] [options]\n\nCommands:\n start Start Expo Metro for Harmony development\n prebuild Generate the Harmony native project with Expo CNG\n prebuild --clean Safely recreate the managed Harmony directory\n prebuild --check Compare generated desired state without project writes\n build Build a HAP without selecting or contacting a device\n doctor Validate config, versions, Metro, RNOH, SDK, and signing\n modules list List native module candidates and their discovery source\n modules inspect Resolve Harmony metadata (--package narrows the result)\n modules verify Validate module config, registration conflicts and safe paths\n export:embed Export validated Hermes bytecode, assets, and a source map\n run Build, install, and launch the Harmony app\n\nOptions:\n --no-install Skip dependency install (prebuild) or HAP install (run)\n --npm|--yarn|--pnpm|--bun\n Select the package manager used by prebuild dependency install\n --skip-dependency-update <packages>\n Preserve comma-separated dependency versions\n --device <id-or-name> Select an HDC target or start a local emulator by name\n --variant <mode> Build debug or release (default: debug)\n --no-bundler Use an already-running Expo Metro server\n --app-id <bundleName>\n Launch another installed app (requires --no-install when different)\n --port <number> Metro and device reverse port (default: 8081)\n --sync Re-run prebuild before building\n --check Validate an existing export without writing\n --reset-cache Reset Metro while exporting or starting\n -c, --clear Alias for --reset-cache (start)\n -h, --help Show this help\n";
|
package/build/help.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Help = void 0;
|
|
4
|
+
exports.Help = `Usage: expo-harmony <command> [project] [options]
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
start Start Expo Metro for Harmony development
|
|
8
|
+
prebuild Generate the Harmony native project with Expo CNG
|
|
9
|
+
prebuild --clean Safely recreate the managed Harmony directory
|
|
10
|
+
prebuild --check Compare generated desired state without project writes
|
|
11
|
+
build Build a HAP without selecting or contacting a device
|
|
12
|
+
doctor Validate config, versions, Metro, RNOH, SDK, and signing
|
|
13
|
+
modules list List native module candidates and their discovery source
|
|
14
|
+
modules inspect Resolve Harmony metadata (--package narrows the result)
|
|
15
|
+
modules verify Validate module config, registration conflicts and safe paths
|
|
16
|
+
export:embed Export validated Hermes bytecode, assets, and a source map
|
|
17
|
+
run Build, install, and launch the Harmony app
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--no-install Skip dependency install (prebuild) or HAP install (run)
|
|
21
|
+
--npm|--yarn|--pnpm|--bun
|
|
22
|
+
Select the package manager used by prebuild dependency install
|
|
23
|
+
--skip-dependency-update <packages>
|
|
24
|
+
Preserve comma-separated dependency versions
|
|
25
|
+
--device <id-or-name> Select an HDC target or start a local emulator by name
|
|
26
|
+
--variant <mode> Build debug or release (default: debug)
|
|
27
|
+
--no-bundler Use an already-running Expo Metro server
|
|
28
|
+
--app-id <bundleName>
|
|
29
|
+
Launch another installed app (requires --no-install when different)
|
|
30
|
+
--port <number> Metro and device reverse port (default: 8081)
|
|
31
|
+
--sync Re-run prebuild before building
|
|
32
|
+
--check Validate an existing export without writing
|
|
33
|
+
--reset-cache Reset Metro while exporting or starting
|
|
34
|
+
-c, --clear Alias for --reset-cache (start)
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
`;
|
package/build/index.d.ts
CHANGED
|
@@ -2,11 +2,13 @@ export { runAsync } from './cli';
|
|
|
2
2
|
export { buildHarmonyAsync } from './buildHap/build';
|
|
3
3
|
export type { HarmonyBuildOptions, HarmonyBuildResult } from './buildHap/build';
|
|
4
4
|
export { exportEmbedAsync } from './exportEmbed/export';
|
|
5
|
-
export { resolveHarmonyBuildPlanAsync
|
|
6
|
-
export {
|
|
5
|
+
export { resolveHarmonyBuildPlanAsync } from './native/project';
|
|
6
|
+
export type { HarmonyBuildPlan, HarmonyBuildMode } from './native/types';
|
|
7
|
+
export { resolveHarmonyToolchain } from './native/toolchain';
|
|
8
|
+
export { installHarmonyDependenciesAsync } from './native/install';
|
|
7
9
|
export { formatModulesResult, runModulesCommandAsync } from './modules/modules';
|
|
8
10
|
export type { ModulesCommandResult } from './modules/modules';
|
|
9
|
-
export { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './
|
|
11
|
+
export { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './native/cache';
|
|
10
12
|
export { runHarmonyAsync } from './run/run';
|
|
11
13
|
export type { HarmonyExportManifest } from './exportEmbed/manifest';
|
|
12
14
|
export type { HarmonyRunOptions, HarmonyRunResult } from './run/run';
|