@expo-harmony/cli 55.0.26-harmony.1 → 55.0.26-harmony.2
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/build/args.d.ts +19 -0
- package/build/args.js +27 -0
- package/build/bin/expo-harmony.d.ts +2 -0
- package/build/buildHap/build.d.ts +25 -0
- package/build/buildHap/build.js +96 -0
- package/build/buildHap/common.d.ts +24 -0
- package/build/buildHap/common.js +92 -0
- package/build/buildHap/options.d.ts +7 -0
- package/build/buildHap/options.js +30 -0
- package/build/cli.d.ts +3 -0
- package/build/cli.js +202 -0
- package/build/doctor/doctor.d.ts +20 -0
- package/build/doctor/doctor.js +217 -0
- package/build/doctor/options.d.ts +5 -0
- package/build/doctor/options.js +15 -0
- package/build/entry.d.ts +2 -0
- package/build/entry.js +47 -0
- package/build/errors.d.ts +12 -0
- package/build/errors.js +16 -0
- package/build/expo.d.ts +19 -0
- package/build/expo.js +53 -0
- package/build/exportEmbed/export.d.ts +16 -0
- package/build/exportEmbed/export.js +125 -0
- package/build/exportEmbed/manifest.d.ts +44 -0
- package/build/exportEmbed/manifest.js +171 -0
- package/build/exportEmbed/options.d.ts +7 -0
- package/build/exportEmbed/options.js +22 -0
- package/build/file.d.ts +9 -0
- package/build/file.js +100 -0
- package/build/index.d.ts +12 -0
- package/build/modules/modules.d.ts +42 -0
- package/build/modules/modules.js +84 -0
- package/build/modules/options.d.ts +10 -0
- package/build/modules/options.js +45 -0
- package/build/path.d.ts +4 -0
- package/build/path.js +27 -0
- package/build/prebuild/check.d.ts +6 -0
- package/build/prebuild/check.js +139 -0
- package/build/prebuild/clean.d.ts +2 -0
- package/build/prebuild/clean.js +50 -0
- package/build/prebuild/options.d.ts +10 -0
- package/build/prebuild/options.js +50 -0
- package/build/prebuild/prebuild.d.ts +6 -0
- package/build/prebuild/prebuild.js +62 -0
- package/build/prebuild/template.d.ts +8 -0
- package/build/prebuild/template.js +86 -0
- package/build/process.d.ts +31 -0
- package/build/process.js +236 -0
- package/build/project.d.ts +2 -0
- package/build/project.js +23 -0
- package/build/projectLock.d.ts +2 -0
- package/build/projectLock.js +180 -0
- package/build/run/cache.d.ts +11 -0
- package/build/run/cache.js +73 -0
- package/build/run/devices.d.ts +29 -0
- package/build/run/devices.js +211 -0
- package/build/run/emulators.d.ts +13 -0
- package/build/run/emulators.js +85 -0
- package/build/run/install.d.ts +7 -0
- package/build/run/install.js +26 -0
- package/build/run/metro.d.ts +17 -0
- package/build/run/metro.js +156 -0
- package/build/run/options.d.ts +13 -0
- package/build/run/options.js +60 -0
- package/build/run/run.d.ts +51 -0
- package/build/run/run.js +174 -0
- package/build/start/options.d.ts +7 -0
- package/build/start/options.js +31 -0
- package/build/tools.d.ts +39 -0
- package/build/tools.js +226 -0
- package/build/upstream.d.ts +9 -0
- package/build/upstream.js +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
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.doctorAsync = doctorAsync;
|
|
7
|
+
exports.formatDoctor = formatDoctor;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_module_1 = require("node:module");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const config_1 = require("@expo/config");
|
|
12
|
+
const config_plugins_1 = require("@expo-harmony/config-plugins");
|
|
13
|
+
const expo_modules_autolinking_1 = require("@expo-harmony/expo-modules-autolinking");
|
|
14
|
+
const native_project_1 = require("@expo-harmony/prebuild-config/native-project");
|
|
15
|
+
const signing_1 = require("@expo-harmony/prebuild-config/signing");
|
|
16
|
+
const process_1 = require("../process");
|
|
17
|
+
const projectLock_1 = require("../projectLock");
|
|
18
|
+
const tools_1 = require("../tools");
|
|
19
|
+
const upstream_1 = require("../upstream");
|
|
20
|
+
function hasPlugin(plugins, packageName) {
|
|
21
|
+
return (plugins || []).some(plugin => (Array.isArray(plugin) ? plugin[0] : plugin) === packageName);
|
|
22
|
+
}
|
|
23
|
+
function check(id, status, message, details) {
|
|
24
|
+
return { id, status, message, ...(details ? { details } : {}) };
|
|
25
|
+
}
|
|
26
|
+
function canResolvePackage(projectRoot, packageName) {
|
|
27
|
+
const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
28
|
+
try {
|
|
29
|
+
projectRequire.resolve(`${packageName}/package.json`);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
try {
|
|
34
|
+
projectRequire.resolve(packageName);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function validateMetroConfigAsync(projectRoot) {
|
|
43
|
+
const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
44
|
+
let metroConfig;
|
|
45
|
+
try {
|
|
46
|
+
metroConfig = projectRequire('metro-config');
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
throw new Error('Cannot load the project-local metro-config package.', { cause });
|
|
50
|
+
}
|
|
51
|
+
if (typeof metroConfig.resolveConfig !== 'function') {
|
|
52
|
+
const version = (() => {
|
|
53
|
+
try {
|
|
54
|
+
return projectRequire('metro-config/package.json').version;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return 'unknown';
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
throw new Error(`metro-config ${version} does not expose resolveConfig().`);
|
|
61
|
+
}
|
|
62
|
+
const hadMetroTarget = Object.hasOwn(process.env, 'EXPO_METRO_TARGET');
|
|
63
|
+
const previousMetroTarget = process.env.EXPO_METRO_TARGET;
|
|
64
|
+
let resolved;
|
|
65
|
+
try {
|
|
66
|
+
process.env.EXPO_METRO_TARGET = 'harmony';
|
|
67
|
+
resolved = await metroConfig.resolveConfig(undefined, projectRoot);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
if (hadMetroTarget)
|
|
71
|
+
process.env.EXPO_METRO_TARGET = previousMetroTarget;
|
|
72
|
+
else
|
|
73
|
+
delete process.env.EXPO_METRO_TARGET;
|
|
74
|
+
}
|
|
75
|
+
const config = resolved?.config;
|
|
76
|
+
const platforms = config?.resolver?.platforms;
|
|
77
|
+
const conditions = config?.resolver?.unstable_conditionsByPlatform?.harmony;
|
|
78
|
+
if (!Array.isArray(platforms) || !platforms.includes('harmony')
|
|
79
|
+
|| !Array.isArray(conditions) || !conditions.includes('harmony')
|
|
80
|
+
|| typeof config?.resolver?.resolveRequest !== 'function') {
|
|
81
|
+
throw new Error('The resolved Metro config must register the harmony platform, Harmony conditions, and a resolver.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function doctorUnlockedAsync(projectRoot, options = {}) {
|
|
85
|
+
const checks = [];
|
|
86
|
+
const unavailableToolStatus = options.requireBuildTools ? 'error' : 'warn';
|
|
87
|
+
let config;
|
|
88
|
+
try {
|
|
89
|
+
config = (0, config_1.getConfig)(projectRoot, {
|
|
90
|
+
isModdedConfig: true,
|
|
91
|
+
skipSDKVersionRequirement: true,
|
|
92
|
+
}).exp;
|
|
93
|
+
(0, config_plugins_1.normalizeHarmonyConfig)(config);
|
|
94
|
+
checks.push(check('app-config', 'pass', 'Harmony app config is valid.'));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
checks.push(check('app-config', 'error', error.message, { code: error.code || 'ERR_HARMONY_CONFIG_INVALID' }));
|
|
98
|
+
}
|
|
99
|
+
if (config) {
|
|
100
|
+
checks.push(hasPlugin(config.plugins, '@expo-harmony/prebuild-config')
|
|
101
|
+
? check('config-plugin', 'pass', '@expo-harmony/prebuild-config is registered.')
|
|
102
|
+
: check('config-plugin', 'error', 'Add @expo-harmony/prebuild-config to expo.plugins.'));
|
|
103
|
+
const harmony = config.harmony;
|
|
104
|
+
if (harmony?.signingConfigFile) {
|
|
105
|
+
try {
|
|
106
|
+
const signing = await (0, signing_1.validateHarmonySigningConfigFile)(projectRoot, harmony.signingConfigFile);
|
|
107
|
+
checks.push(check('signing', 'pass', `Harmony signing config ${signing.name} is valid.`));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
checks.push(check('signing', 'error', error.message, { code: error.code || 'ERR_HARMONY_SIGNING_INVALID' }));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
checks.push(check('signing', 'warn', 'No external signing config is set; unsigned generation remains available.'));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
await validateMetroConfigAsync(projectRoot);
|
|
119
|
+
checks.push(check('metro', 'pass', 'The resolved Metro config enables Harmony.'));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
checks.push(check('metro', 'error', `Cannot load a Harmony-enabled Metro config: ${error.message}`, { code: error.code || 'ERR_HARMONY_METRO_CONFIG' }));
|
|
123
|
+
}
|
|
124
|
+
for (const packageName of upstream_1.RequiredProjectPackages) {
|
|
125
|
+
if (canResolvePackage(projectRoot, packageName)) {
|
|
126
|
+
checks.push(check(`package:${packageName}`, 'pass', `${packageName} is resolvable.`));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
checks.push(check(`package:${packageName}`, 'error', `${packageName} is not resolvable from the app.`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (options.validateModules !== false) {
|
|
133
|
+
try {
|
|
134
|
+
const result = await (0, expo_modules_autolinking_1.verifyModulesAsync)({ platform: 'harmony', projectRoot });
|
|
135
|
+
const errors = result.diagnostics.filter(item => item.severity === 'error');
|
|
136
|
+
const warnings = result.diagnostics.filter(item => item.severity === 'warning');
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
checks.push(check('expo-modules', 'error', `Harmony Expo Modules verification found ${errors.length} error(s). Run 'expo-harmony modules verify' for details.`, { diagnostics: result.diagnostics }));
|
|
139
|
+
}
|
|
140
|
+
else if (warnings.length > 0) {
|
|
141
|
+
checks.push(check('expo-modules', 'warn', `Verified ${result.modules.length} Harmony native module(s) with ${warnings.length} warning(s).`, { diagnostics: warnings }));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
checks.push(check('expo-modules', 'pass', `Verified ${result.modules.length} Harmony native module(s).`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
checks.push(check('expo-modules', 'error', `Harmony Expo Modules verification failed: ${error.message}`, { code: error.code || 'ERR_HARMONY_AUTOLINKING_FAILED' }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const toolchain = (0, tools_1.resolveHarmonyToolchain)();
|
|
152
|
+
let sdkCheck;
|
|
153
|
+
if (toolchain.sdkHome) {
|
|
154
|
+
sdkCheck = check('harmony-sdk', 'pass', `Complete Harmony SDK root was resolved at ${toolchain.sdkHome}.`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
sdkCheck = check('harmony-sdk', unavailableToolStatus, 'No complete Harmony SDK root with HMS and OpenHarmony components was found; generation works but HAP build cannot be verified.');
|
|
158
|
+
}
|
|
159
|
+
checks.push(sdkCheck);
|
|
160
|
+
const tools = [
|
|
161
|
+
['ohpm', toolchain.ohpm, ['--version'], unavailableToolStatus],
|
|
162
|
+
['hvigor-command', toolchain.hvigor, ['--version'], unavailableToolStatus],
|
|
163
|
+
];
|
|
164
|
+
if (options.requireDeviceTools !== false) {
|
|
165
|
+
tools.unshift(['hdc', toolchain.hdc, ['-v'], unavailableToolStatus]);
|
|
166
|
+
}
|
|
167
|
+
for (const [id, tool, versionArgs, unavailableStatus] of tools) {
|
|
168
|
+
try {
|
|
169
|
+
const result = await (0, process_1.spawnAsync)(tool.command, [...tool.args, ...versionArgs], {
|
|
170
|
+
capture: true,
|
|
171
|
+
cwd: projectRoot,
|
|
172
|
+
operation: `doctor-${id}`,
|
|
173
|
+
timeoutMs: 10_000,
|
|
174
|
+
});
|
|
175
|
+
checks.push(result.code === 0 && !result.timedOut
|
|
176
|
+
? check(id, 'pass', `${tool.command} is available through ${tool.source}.`)
|
|
177
|
+
: check(id, unavailableStatus, `${tool.command} is unavailable or unhealthy; HAP build cannot be verified.`));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
checks.push(check(id, unavailableStatus, `${tool.command} is unavailable; generation remains available.`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (options.validateGeneratedProject !== false) {
|
|
184
|
+
try {
|
|
185
|
+
const plan = await (0, tools_1.resolveHarmonyBuildPlanIfPresentAsync)(projectRoot);
|
|
186
|
+
if (plan && node_fs_1.default.existsSync(plan.harmonyRoot)) {
|
|
187
|
+
if (!node_fs_1.default.existsSync(plan.projectFiles.rootHvigor)
|
|
188
|
+
|| !node_fs_1.default.existsSync(plan.projectFiles.moduleHvigor)) {
|
|
189
|
+
checks.push(check('hvigor', 'error', 'Generated root and module Hvigor files are required.'));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const content = await node_fs_1.default.promises.readFile(plan.projectFiles.moduleHvigor, 'utf8');
|
|
193
|
+
checks.push((0, native_project_1.isRnohAutolinkingDisabled)(content)
|
|
194
|
+
? check('hvigor-autolinking', 'pass', 'RNOH duplicate autolinking is disabled.')
|
|
195
|
+
: check('hvigor-autolinking', 'error', 'The generated module Hvigor file must disable RNOH autolinking.'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
checks.push(check('harmony-project', 'error', error.message, { code: error.code || 'ERR_HARMONY_TEMPLATE_INVALID' }));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
checks,
|
|
205
|
+
ok: checks.every(item => item.status !== 'error'),
|
|
206
|
+
projectRoot,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function doctorAsync(projectRoot, options = {}) {
|
|
210
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'doctor', () => doctorUnlockedAsync(projectRoot, options));
|
|
211
|
+
}
|
|
212
|
+
function formatDoctor(result) {
|
|
213
|
+
return result.checks.map((item) => {
|
|
214
|
+
const symbol = item.status === 'pass' ? '✓' : item.status === 'warn' ? '!' : '✗';
|
|
215
|
+
return `${symbol} [${item.status}] ${item.message}`;
|
|
216
|
+
}).join('\n');
|
|
217
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDoctorArgs = parseDoctorArgs;
|
|
4
|
+
const args_1 = require("../args");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
function parseDoctorArgs(argv) {
|
|
7
|
+
const { positionals, values } = (0, args_1.parseArgs)(args_1.CommonOptions, argv);
|
|
8
|
+
if (positionals.length > 1) {
|
|
9
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unexpected doctor positional argument: ${positionals[1]}`, { operation: 'parse-arguments' });
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
help: Boolean(values.help),
|
|
13
|
+
project: positionals[0],
|
|
14
|
+
};
|
|
15
|
+
}
|
package/build/entry.d.ts
ADDED
package/build/entry.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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.resolveHarmonyEntryPoint = resolveHarmonyEntryPoint;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_module_1 = require("node:module");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const paths_1 = require("@expo/config/paths");
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
function isFile(file) {
|
|
13
|
+
try {
|
|
14
|
+
return node_fs_1.default.statSync(file).isFile();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveHarmonyEntryPoint(projectRoot) {
|
|
21
|
+
const packageJsonPath = node_path_1.default.join(projectRoot, 'package.json');
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
24
|
+
let entry = (0, paths_1.resolveEntryPoint)(projectRoot, { pkg, platform: 'harmony' });
|
|
25
|
+
if (!isFile(entry) && typeof pkg.main === 'string' && pkg.main) {
|
|
26
|
+
const logicalPackageRoot = entry;
|
|
27
|
+
const resolved = (0, node_module_1.createRequire)(packageJsonPath).resolve(pkg.main);
|
|
28
|
+
try {
|
|
29
|
+
const relative = node_path_1.default.relative(node_fs_1.default.realpathSync(logicalPackageRoot), node_fs_1.default.realpathSync(resolved));
|
|
30
|
+
entry = relative !== '..'
|
|
31
|
+
&& !relative.startsWith(`..${node_path_1.default.sep}`)
|
|
32
|
+
&& !node_path_1.default.isAbsolute(relative)
|
|
33
|
+
? node_path_1.default.join(logicalPackageRoot, relative)
|
|
34
|
+
: resolved;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
entry = resolved;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!isFile(entry))
|
|
41
|
+
throw new Error('Resolved entry is not a file.');
|
|
42
|
+
return entry;
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_ENTRY', 'Cannot resolve the Expo entry point for the Harmony bundle.', { cause, operation: 'resolve-entry-point' });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface HarmonyCliErrorOptions {
|
|
2
|
+
cause?: unknown;
|
|
3
|
+
exitCode?: number;
|
|
4
|
+
operation?: string;
|
|
5
|
+
}
|
|
6
|
+
declare class HarmonyCliError extends Error {
|
|
7
|
+
code: string;
|
|
8
|
+
exitCode: number;
|
|
9
|
+
operation: string;
|
|
10
|
+
constructor(code: string, message: string, options?: HarmonyCliErrorOptions);
|
|
11
|
+
}
|
|
12
|
+
export { HarmonyCliError };
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HarmonyCliError = void 0;
|
|
4
|
+
class HarmonyCliError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
exitCode;
|
|
7
|
+
operation;
|
|
8
|
+
constructor(code, message, options = {}) {
|
|
9
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
10
|
+
this.name = 'HarmonyCliError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.exitCode = options.exitCode || 1;
|
|
13
|
+
this.operation = options.operation || 'cli';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.HarmonyCliError = HarmonyCliError;
|
package/build/expo.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ExpoHermesBundleOptions {
|
|
2
|
+
code: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
map: string | null;
|
|
5
|
+
minify?: boolean;
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
}
|
|
8
|
+
interface ExpoHermesBundleOutput {
|
|
9
|
+
hbc: Uint8Array;
|
|
10
|
+
sourcemap: string | null;
|
|
11
|
+
}
|
|
12
|
+
type ExpoHermesBuilder = (options: ExpoHermesBundleOptions) => Promise<ExpoHermesBundleOutput>;
|
|
13
|
+
declare function resolveExpoCli(projectRoot: any): {
|
|
14
|
+
cliPath: string;
|
|
15
|
+
packageJsonPath: any;
|
|
16
|
+
};
|
|
17
|
+
declare function resolveExpoHermesBuilder(projectRoot: string): ExpoHermesBuilder;
|
|
18
|
+
export { resolveExpoCli, resolveExpoHermesBuilder };
|
|
19
|
+
export type { ExpoHermesBuilder };
|
package/build/expo.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
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.resolveExpoCli = resolveExpoCli;
|
|
7
|
+
exports.resolveExpoHermesBuilder = resolveExpoHermesBuilder;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_module_1 = require("node:module");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
function resolveExpoCli(projectRoot) {
|
|
13
|
+
const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
14
|
+
let packageJsonPath;
|
|
15
|
+
try {
|
|
16
|
+
packageJsonPath = projectRequire.resolve('expo/package.json');
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPO_CLI_NOT_FOUND', 'Cannot resolve the project-local expo package.', { cause, operation: 'resolve-expo-cli' });
|
|
20
|
+
}
|
|
21
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
22
|
+
const bin = typeof packageJson.bin === 'string' ? packageJson.bin : packageJson.bin?.expo;
|
|
23
|
+
if (typeof bin !== 'string' || !bin) {
|
|
24
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPO_CLI_NOT_FOUND', 'The project-local expo package has no expo binary.', { operation: 'resolve-expo-cli' });
|
|
25
|
+
}
|
|
26
|
+
const cliPath = node_path_1.default.resolve(node_path_1.default.dirname(packageJsonPath), bin);
|
|
27
|
+
if (!node_fs_1.default.existsSync(cliPath)) {
|
|
28
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPO_CLI_NOT_FOUND', `Expo CLI binary does not exist: ${cliPath}`, { operation: 'resolve-expo-cli' });
|
|
29
|
+
}
|
|
30
|
+
return { cliPath, packageJsonPath };
|
|
31
|
+
}
|
|
32
|
+
function resolveExpoHermesBuilder(projectRoot) {
|
|
33
|
+
const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
34
|
+
let packageJsonPath;
|
|
35
|
+
try {
|
|
36
|
+
packageJsonPath = projectRequire.resolve('@expo/metro-config/package.json');
|
|
37
|
+
}
|
|
38
|
+
catch (cause) {
|
|
39
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_HERMES', 'Cannot resolve the project-local @expo/metro-config package required for Hermes export.', { cause, operation: 'resolve-expo-hermes' });
|
|
40
|
+
}
|
|
41
|
+
const modulePath = node_path_1.default.join(node_path_1.default.dirname(packageJsonPath), 'build', 'serializer', 'exportHermes.js');
|
|
42
|
+
let moduleExports;
|
|
43
|
+
try {
|
|
44
|
+
moduleExports = projectRequire(modulePath);
|
|
45
|
+
}
|
|
46
|
+
catch (cause) {
|
|
47
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_HERMES', `Cannot load the Expo Hermes exporter: ${modulePath}`, { cause, operation: 'resolve-expo-hermes' });
|
|
48
|
+
}
|
|
49
|
+
if (typeof moduleExports?.buildHermesBundleAsync !== 'function') {
|
|
50
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_HERMES', 'The project-local @expo/metro-config package does not expose buildHermesBundleAsync().', { operation: 'resolve-expo-hermes' });
|
|
51
|
+
}
|
|
52
|
+
return moduleExports.buildHermesBundleAsync;
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type HarmonyExportManifest } from './manifest';
|
|
2
|
+
export interface ExportOptions {
|
|
3
|
+
check?: boolean;
|
|
4
|
+
resetCache?: boolean;
|
|
5
|
+
skipDoctor?: boolean;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ExportTemporary {
|
|
9
|
+
assets: string;
|
|
10
|
+
bundle: string;
|
|
11
|
+
javascript: string;
|
|
12
|
+
metroSourceMap: string;
|
|
13
|
+
sourceMap: string;
|
|
14
|
+
}
|
|
15
|
+
declare function exportEmbedAsync(projectRoot: string, options?: ExportOptions): Promise<HarmonyExportManifest>;
|
|
16
|
+
export { exportEmbedAsync, };
|
|
@@ -0,0 +1,125 @@
|
|
|
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.exportEmbedAsync = exportEmbedAsync;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_module_1 = require("node:module");
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const doctor_1 = require("../doctor/doctor");
|
|
12
|
+
const entry_1 = require("../entry");
|
|
13
|
+
const errors_1 = require("../errors");
|
|
14
|
+
const projectLock_1 = require("../projectLock");
|
|
15
|
+
const tools_1 = require("../tools");
|
|
16
|
+
const process_1 = require("../process");
|
|
17
|
+
const expo_1 = require("../expo");
|
|
18
|
+
const manifest_1 = require("./manifest");
|
|
19
|
+
function createExpoExportEmbedArgs(projectRoot, entryFile, temporary, options = {}) {
|
|
20
|
+
return [
|
|
21
|
+
'export:embed',
|
|
22
|
+
'--platform', 'harmony',
|
|
23
|
+
'--entry-file', entryFile,
|
|
24
|
+
'--bundle-output', temporary.javascript,
|
|
25
|
+
'--assets-dest', temporary.assets,
|
|
26
|
+
'--dev', 'false',
|
|
27
|
+
'--minify', 'false',
|
|
28
|
+
'--sourcemap-output', temporary.metroSourceMap,
|
|
29
|
+
'--sourcemap-sources-root', '.',
|
|
30
|
+
'--unstable-transform-profile', 'hermes-stable',
|
|
31
|
+
...(options.resetCache ? ['--reset-cache=true'] : []),
|
|
32
|
+
projectRoot,
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function assertDoctor(result) {
|
|
36
|
+
if (result.ok)
|
|
37
|
+
return;
|
|
38
|
+
const failing = result.checks.filter(check => check.status === 'error').map(check => check.id);
|
|
39
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_DOCTOR', `Harmony doctor found blocking checks: ${failing.join(', ') || 'unknown'}.`, { operation: 'doctor' });
|
|
40
|
+
}
|
|
41
|
+
async function exportEmbedUnlockedAsync(projectRoot, options = {}) {
|
|
42
|
+
if (!options.skipDoctor)
|
|
43
|
+
assertDoctor(await (0, doctor_1.doctorAsync)(projectRoot));
|
|
44
|
+
const plan = await (0, tools_1.resolveHarmonyBuildPlanAsync)(projectRoot, { buildMode: 'release' });
|
|
45
|
+
const paths = (0, manifest_1.exportPaths)(plan);
|
|
46
|
+
if (options.check)
|
|
47
|
+
return await (0, manifest_1.validatePublishedExportAsync)(paths);
|
|
48
|
+
const entryFile = (0, entry_1.resolveHarmonyEntryPoint)(projectRoot);
|
|
49
|
+
const temporaryRoot = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'expo-harmony-export-'));
|
|
50
|
+
const temporary = {
|
|
51
|
+
assets: node_path_1.default.join(temporaryRoot, 'assets'),
|
|
52
|
+
bundle: node_path_1.default.join(temporaryRoot, 'hermes_bundle.hbc'),
|
|
53
|
+
javascript: node_path_1.default.join(temporaryRoot, 'index.js'),
|
|
54
|
+
metroSourceMap: node_path_1.default.join(temporaryRoot, 'index.js.map'),
|
|
55
|
+
sourceMap: node_path_1.default.join(temporaryRoot, 'hermes_bundle.hbc.map'),
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
await node_fs_1.default.promises.mkdir(temporary.assets, { recursive: true });
|
|
59
|
+
const expo = (0, expo_1.resolveExpoCli)(projectRoot);
|
|
60
|
+
const result = await (0, process_1.spawnAsync)(process.execPath, [
|
|
61
|
+
expo.cliPath,
|
|
62
|
+
...createExpoExportEmbedArgs(projectRoot, entryFile, temporary, options),
|
|
63
|
+
], {
|
|
64
|
+
capture: true,
|
|
65
|
+
cwd: projectRoot,
|
|
66
|
+
env: {
|
|
67
|
+
...process.env,
|
|
68
|
+
EXPO_METRO_TARGET: 'harmony',
|
|
69
|
+
HERMES_V1_ENABLED: 'true',
|
|
70
|
+
NODE_ENV: 'production',
|
|
71
|
+
},
|
|
72
|
+
operation: 'expo-export-embed',
|
|
73
|
+
outputLimit: 4 * 1024 * 1024,
|
|
74
|
+
timeoutMs: options.timeoutMs || 10 * 60_000,
|
|
75
|
+
});
|
|
76
|
+
if (result.code !== 0 || result.timedOut) {
|
|
77
|
+
const diagnostics = (0, process_1.formatDiagnostics)(result);
|
|
78
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_FAILED', `Expo export:embed exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.${diagnostics ? `\n${diagnostics}` : ''}`, { exitCode: result.code || 1, operation: 'expo-export-embed' });
|
|
79
|
+
}
|
|
80
|
+
// Expo 55 only infers Hermes for iOS and Android. Keep Harmony bundling in the Expo CLI,
|
|
81
|
+
// then explicitly hand its JS and source map to Expo's own Hermes exporter.
|
|
82
|
+
try {
|
|
83
|
+
const [code, map] = await Promise.all([
|
|
84
|
+
node_fs_1.default.promises.readFile(temporary.javascript, 'utf8'),
|
|
85
|
+
node_fs_1.default.promises.readFile(temporary.metroSourceMap, 'utf8'),
|
|
86
|
+
]);
|
|
87
|
+
const buildHermesBundleAsync = (0, expo_1.resolveExpoHermesBuilder)(projectRoot);
|
|
88
|
+
// Expo resolves hermes-compiler from react-native. For Harmony, resolve it
|
|
89
|
+
// from RNOH instead of the app's Android/iOS React Native installation.
|
|
90
|
+
const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
91
|
+
const harmonyRuntime = node_path_1.default.dirname(projectRequire.resolve('@react-native-oh/react-native-harmony/package.json'));
|
|
92
|
+
const compilerModules = node_path_1.default.join(temporaryRoot, 'node_modules');
|
|
93
|
+
await node_fs_1.default.promises.mkdir(compilerModules);
|
|
94
|
+
await node_fs_1.default.promises.symlink(harmonyRuntime, node_path_1.default.join(compilerModules, 'react-native'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
95
|
+
const output = await buildHermesBundleAsync({
|
|
96
|
+
code,
|
|
97
|
+
filename: entryFile,
|
|
98
|
+
map,
|
|
99
|
+
minify: true,
|
|
100
|
+
projectRoot: temporaryRoot,
|
|
101
|
+
});
|
|
102
|
+
if (!(output?.hbc instanceof Uint8Array) || typeof output.sourcemap !== 'string') {
|
|
103
|
+
throw new Error('Expo did not return Hermes bytecode and a composed source map.');
|
|
104
|
+
}
|
|
105
|
+
await Promise.all([
|
|
106
|
+
node_fs_1.default.promises.writeFile(temporary.bundle, output.hbc),
|
|
107
|
+
node_fs_1.default.promises.writeFile(temporary.sourceMap, output.sourcemap),
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
catch (cause) {
|
|
111
|
+
if (cause instanceof errors_1.HarmonyCliError)
|
|
112
|
+
throw cause;
|
|
113
|
+
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
|
+
}
|
|
115
|
+
const bytecode = await (0, manifest_1.assertHermesBundle)(temporary.bundle);
|
|
116
|
+
await (0, manifest_1.assertSourceMap)(temporary.sourceMap);
|
|
117
|
+
return await (0, manifest_1.publishExportAsync)(projectRoot, paths, temporary, entryFile, bytecode);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await node_fs_1.default.promises.rm(temporaryRoot, { force: true, recursive: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function exportEmbedAsync(projectRoot, options = {}) {
|
|
124
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'export:embed', () => exportEmbedUnlockedAsync(projectRoot, options));
|
|
125
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ExportTemporary } from './export';
|
|
2
|
+
export interface HarmonyExportFile {
|
|
3
|
+
path: string;
|
|
4
|
+
sha256: string;
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
7
|
+
export interface HarmonyExportManifest {
|
|
8
|
+
assets: HarmonyExportFile[];
|
|
9
|
+
bundle: HarmonyExportFile & {
|
|
10
|
+
bytecodeVersion: number;
|
|
11
|
+
};
|
|
12
|
+
entryFile: string;
|
|
13
|
+
platform: 'harmony';
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
sourceMap: HarmonyExportFile;
|
|
16
|
+
transformProfile: 'hermes-stable';
|
|
17
|
+
}
|
|
18
|
+
declare const HermesMagic: Buffer;
|
|
19
|
+
declare function exportPaths(plan: {
|
|
20
|
+
exportPaths: {
|
|
21
|
+
bundle: string;
|
|
22
|
+
manifest: string;
|
|
23
|
+
metadataRoot: string;
|
|
24
|
+
rawfileRoot: string;
|
|
25
|
+
sourceMap: string;
|
|
26
|
+
};
|
|
27
|
+
}): {
|
|
28
|
+
bundle: string;
|
|
29
|
+
manifest: string;
|
|
30
|
+
metadataRoot: string;
|
|
31
|
+
rawfileRoot: string;
|
|
32
|
+
sourceMap: string;
|
|
33
|
+
};
|
|
34
|
+
declare function assertHermesBundle(file: any): Promise<{
|
|
35
|
+
bytecodeVersion: number;
|
|
36
|
+
size: any;
|
|
37
|
+
}>;
|
|
38
|
+
declare function assertSourceMap(file: any): Promise<{
|
|
39
|
+
sha256: string;
|
|
40
|
+
size: number;
|
|
41
|
+
}>;
|
|
42
|
+
declare function publishExportAsync(projectRoot: string, paths: ReturnType<typeof exportPaths>, temporary: ExportTemporary, entryFile: string, bytecode: Awaited<ReturnType<typeof assertHermesBundle>>): Promise<HarmonyExportManifest>;
|
|
43
|
+
declare function validatePublishedExportAsync(paths: ReturnType<typeof exportPaths>): Promise<HarmonyExportManifest>;
|
|
44
|
+
export { HermesMagic, assertHermesBundle, assertSourceMap, exportPaths, publishExportAsync, validatePublishedExportAsync, };
|