@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
package/build/args.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type Option = {
|
|
2
|
+
short?: string;
|
|
3
|
+
type: 'boolean' | 'string';
|
|
4
|
+
};
|
|
5
|
+
type Options = Record<string, Option>;
|
|
6
|
+
type Values<T extends Options> = {
|
|
7
|
+
[K in keyof T]?: T[K]['type'] extends 'boolean' ? boolean : string;
|
|
8
|
+
};
|
|
9
|
+
declare const CommonOptions: {
|
|
10
|
+
readonly help: {
|
|
11
|
+
readonly short: "h";
|
|
12
|
+
readonly type: "boolean";
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
declare function parseArgs<T extends Options>(options: T, argv: string[]): {
|
|
16
|
+
positionals: string[];
|
|
17
|
+
values: Values<T>;
|
|
18
|
+
};
|
|
19
|
+
export { CommonOptions, parseArgs };
|
package/build/args.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommonOptions = void 0;
|
|
4
|
+
exports.parseArgs = parseArgs;
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const CommonOptions = {
|
|
8
|
+
help: { short: 'h', type: 'boolean' },
|
|
9
|
+
};
|
|
10
|
+
exports.CommonOptions = CommonOptions;
|
|
11
|
+
function parseArgs(options, argv) {
|
|
12
|
+
try {
|
|
13
|
+
return (0, node_util_1.parseArgs)({
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
args: argv,
|
|
16
|
+
options,
|
|
17
|
+
strict: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (cause) {
|
|
21
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
22
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', message, {
|
|
23
|
+
cause,
|
|
24
|
+
operation: 'parse-arguments',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface HarmonyBuildOptions {
|
|
2
|
+
io?: Pick<Console, 'error' | 'log' | 'warn'>;
|
|
3
|
+
/** @internal For release verification after a successful isolated prebuild check. */
|
|
4
|
+
skipGeneratedProjectCheck?: boolean;
|
|
5
|
+
sync?: boolean;
|
|
6
|
+
variant?: 'debug' | 'release';
|
|
7
|
+
}
|
|
8
|
+
export interface HarmonyBuildResult {
|
|
9
|
+
bundleName: string;
|
|
10
|
+
export: null | {
|
|
11
|
+
assetCount: number;
|
|
12
|
+
bundleSha256: string;
|
|
13
|
+
sourceMapSha256: string;
|
|
14
|
+
};
|
|
15
|
+
hapPath: string;
|
|
16
|
+
headless: true;
|
|
17
|
+
installed: false;
|
|
18
|
+
launched: false;
|
|
19
|
+
ok: true;
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
steps: Record<string, number>;
|
|
22
|
+
variant: 'debug' | 'release';
|
|
23
|
+
}
|
|
24
|
+
declare function buildHarmonyAsync(projectRoot: string, options?: HarmonyBuildOptions): Promise<HarmonyBuildResult>;
|
|
25
|
+
export { buildHarmonyAsync };
|
|
@@ -0,0 +1,96 @@
|
|
|
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.buildHarmonyAsync = buildHarmonyAsync;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const errors_1 = require("../errors");
|
|
9
|
+
const export_1 = require("../exportEmbed/export");
|
|
10
|
+
const path_1 = require("../path");
|
|
11
|
+
const projectLock_1 = require("../projectLock");
|
|
12
|
+
const cache_1 = require("../run/cache");
|
|
13
|
+
const install_1 = require("../run/install");
|
|
14
|
+
const tools_1 = require("../tools");
|
|
15
|
+
const common_1 = require("./common");
|
|
16
|
+
async function buildHarmonyUnlockedAsync(projectRoot, options = {}) {
|
|
17
|
+
const normalizedOptions = {
|
|
18
|
+
io: options.io || console,
|
|
19
|
+
requireDeviceTools: false,
|
|
20
|
+
skipGeneratedProjectCheck: Boolean(options.skipGeneratedProjectCheck),
|
|
21
|
+
sync: Boolean(options.sync),
|
|
22
|
+
variant: options.variant || 'debug',
|
|
23
|
+
};
|
|
24
|
+
const steps = {
|
|
25
|
+
device: 0,
|
|
26
|
+
install: 0,
|
|
27
|
+
launch: 0,
|
|
28
|
+
metro: 0,
|
|
29
|
+
metroPort: 0,
|
|
30
|
+
};
|
|
31
|
+
await (0, common_1.ensureGeneratedProjectAsync)(projectRoot, normalizedOptions, steps);
|
|
32
|
+
const plan = await (0, common_1.timed)(steps, 'buildPlan', () => (0, tools_1.resolveHarmonyBuildPlanAsync)(projectRoot, { buildMode: normalizedOptions.variant }));
|
|
33
|
+
const toolchain = (0, tools_1.resolveHarmonyToolchain)();
|
|
34
|
+
let exportManifest = null;
|
|
35
|
+
if (normalizedOptions.variant === 'release') {
|
|
36
|
+
(0, common_1.progress)(normalizedOptions, 'Exporting the release Hermes bundle');
|
|
37
|
+
exportManifest = await (0, common_1.timed)(steps, 'export', () => (0, export_1.exportEmbedAsync)(projectRoot, { skipDoctor: true }));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
steps.export = 0;
|
|
41
|
+
}
|
|
42
|
+
(0, common_1.progress)(normalizedOptions, 'Installing Harmony project dependencies');
|
|
43
|
+
await (0, common_1.timed)(steps, 'ohpm', () => (0, install_1.installHarmonyDependenciesAsync)(plan, toolchain));
|
|
44
|
+
(0, common_1.progress)(normalizedOptions, 'Checking Harmony native dependency cache');
|
|
45
|
+
const nativeBuildCache = await (0, common_1.timed)(steps, 'nativeCache', () => ((0, cache_1.prepareHarmonyNativeBuildCacheAsync)(projectRoot, plan)));
|
|
46
|
+
if (nativeBuildCache.changed) {
|
|
47
|
+
(0, common_1.progress)(normalizedOptions, 'Invalidated stale Harmony native build objects');
|
|
48
|
+
}
|
|
49
|
+
(0, common_1.progress)(normalizedOptions, `Building the ${normalizedOptions.variant} HAP without a device`);
|
|
50
|
+
const buildEnv = {
|
|
51
|
+
...process.env,
|
|
52
|
+
EXPO_HARMONY_NODE: process.env.EXPO_HARMONY_NODE || process.execPath,
|
|
53
|
+
EXPO_METRO_TARGET: 'harmony',
|
|
54
|
+
HERMES_V1_ENABLED: 'true',
|
|
55
|
+
...(normalizedOptions.variant === 'release' ? { EXPO_HARMONY_BUNDLE_PREBUILT: '1' } : {}),
|
|
56
|
+
...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
|
|
57
|
+
? { DEVECO_SDK_HOME: toolchain.sdkHome }
|
|
58
|
+
: {}),
|
|
59
|
+
};
|
|
60
|
+
await (0, common_1.timed)(steps, 'build', () => (0, common_1.runCheckedAsync)(toolchain.hvigor.command, [
|
|
61
|
+
...toolchain.hvigor.args,
|
|
62
|
+
...plan.hvigorArgs,
|
|
63
|
+
], {
|
|
64
|
+
code: 'ERR_HARMONY_BUILD_FAILED',
|
|
65
|
+
cwd: plan.harmonyRoot,
|
|
66
|
+
env: buildEnv,
|
|
67
|
+
message: 'Hvigor build',
|
|
68
|
+
operation: 'hvigor-build',
|
|
69
|
+
timeoutMs: 15 * 60_000,
|
|
70
|
+
}));
|
|
71
|
+
if (!(0, common_1.isNonEmptyRegularFile)(plan.expectedHap)) {
|
|
72
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_HAP_MISSING', 'Hvigor completed without producing the expected non-empty regular HAP.', { operation: 'verify-hap' });
|
|
73
|
+
}
|
|
74
|
+
await (0, common_1.timed)(steps, 'nativeCacheCommit', () => (0, cache_1.commitHarmonyNativeBuildCacheAsync)(nativeBuildCache));
|
|
75
|
+
return {
|
|
76
|
+
bundleName: plan.bundleName,
|
|
77
|
+
export: exportManifest
|
|
78
|
+
? {
|
|
79
|
+
assetCount: exportManifest.assets.length,
|
|
80
|
+
bundleSha256: exportManifest.bundle.sha256,
|
|
81
|
+
sourceMapSha256: exportManifest.sourceMap.sha256,
|
|
82
|
+
}
|
|
83
|
+
: null,
|
|
84
|
+
hapPath: (0, path_1.toPosixPath)(node_path_1.default.relative(projectRoot, plan.expectedHap)),
|
|
85
|
+
headless: true,
|
|
86
|
+
installed: false,
|
|
87
|
+
launched: false,
|
|
88
|
+
ok: true,
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
steps,
|
|
91
|
+
variant: normalizedOptions.variant,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function buildHarmonyAsync(projectRoot, options = {}) {
|
|
95
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'build', () => buildHarmonyUnlockedAsync(projectRoot, options));
|
|
96
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface HarmonyBuildPipelineOptions {
|
|
2
|
+
io?: Pick<Console, 'error' | 'log' | 'warn'>;
|
|
3
|
+
requireDeviceTools?: boolean;
|
|
4
|
+
/** @internal The caller already completed an isolated prebuild check. */
|
|
5
|
+
skipGeneratedProjectCheck?: boolean;
|
|
6
|
+
sync: boolean;
|
|
7
|
+
variant: 'debug' | 'release';
|
|
8
|
+
}
|
|
9
|
+
interface CheckedProcessOptions {
|
|
10
|
+
code: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
message: string;
|
|
14
|
+
operation: string;
|
|
15
|
+
outputLimit?: number;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
declare function progress(options: HarmonyBuildPipelineOptions, message: string): void;
|
|
19
|
+
declare function timed<T>(steps: Record<string, number>, name: string, operation: () => Promise<T> | T): Promise<T>;
|
|
20
|
+
declare function isNonEmptyRegularFile(file: string): boolean;
|
|
21
|
+
declare function runCheckedAsync(command: string, args: string[], options: CheckedProcessOptions): Promise<import("../process").ProcessResult>;
|
|
22
|
+
declare function ensureGeneratedProjectAsync(projectRoot: string, options: HarmonyBuildPipelineOptions, steps: Record<string, number>): Promise<void>;
|
|
23
|
+
export { ensureGeneratedProjectAsync, isNonEmptyRegularFile, progress, runCheckedAsync, timed, };
|
|
24
|
+
export type { CheckedProcessOptions, HarmonyBuildPipelineOptions };
|
|
@@ -0,0 +1,92 @@
|
|
|
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.ensureGeneratedProjectAsync = ensureGeneratedProjectAsync;
|
|
7
|
+
exports.isNonEmptyRegularFile = isNonEmptyRegularFile;
|
|
8
|
+
exports.progress = progress;
|
|
9
|
+
exports.runCheckedAsync = runCheckedAsync;
|
|
10
|
+
exports.timed = timed;
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const doctor_1 = require("../doctor/doctor");
|
|
13
|
+
const errors_1 = require("../errors");
|
|
14
|
+
const check_1 = require("../prebuild/check");
|
|
15
|
+
const prebuild_1 = require("../prebuild/prebuild");
|
|
16
|
+
const process_1 = require("../process");
|
|
17
|
+
const tools_1 = require("../tools");
|
|
18
|
+
function progress(options, message) {
|
|
19
|
+
options.io?.log?.(`› ${message}`);
|
|
20
|
+
}
|
|
21
|
+
async function timed(steps, name, operation) {
|
|
22
|
+
const started = performance.now();
|
|
23
|
+
try {
|
|
24
|
+
return await operation();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
steps[name] = Math.max(0, Math.round(performance.now() - started));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertDoctor(result) {
|
|
31
|
+
if (result.ok)
|
|
32
|
+
return;
|
|
33
|
+
const failing = result.checks.filter(check => check.status === 'error').map(check => check.id);
|
|
34
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', `Harmony doctor found blocking checks: ${failing.join(', ') || 'unknown'}.`, { operation: 'doctor' });
|
|
35
|
+
}
|
|
36
|
+
function isNonEmptyRegularFile(file) {
|
|
37
|
+
try {
|
|
38
|
+
const stat = node_fs_1.default.lstatSync(file);
|
|
39
|
+
return !stat.isSymbolicLink() && stat.isFile() && stat.size > 0;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function runCheckedAsync(command, args, options) {
|
|
46
|
+
const result = await (0, process_1.spawnAsync)(command, args, {
|
|
47
|
+
capture: true,
|
|
48
|
+
cwd: options.cwd,
|
|
49
|
+
env: options.env,
|
|
50
|
+
operation: options.operation,
|
|
51
|
+
outputLimit: options.outputLimit || 2 * 1024 * 1024,
|
|
52
|
+
timeoutMs: options.timeoutMs,
|
|
53
|
+
});
|
|
54
|
+
if (result.code !== 0 || result.timedOut) {
|
|
55
|
+
const diagnostics = (0, process_1.formatDiagnostics)(result);
|
|
56
|
+
throw new errors_1.HarmonyCliError(options.code, `${options.message} exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.${diagnostics ? `\n${diagnostics}` : ''}`, { exitCode: result.code || 1, operation: options.operation });
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
async function ensureGeneratedProjectAsync(projectRoot, options, steps) {
|
|
61
|
+
const plan = await (0, tools_1.resolveHarmonyBuildPlanIfPresentAsync)(projectRoot, {
|
|
62
|
+
buildMode: options.variant,
|
|
63
|
+
});
|
|
64
|
+
const exists = Boolean(plan && node_fs_1.default.existsSync(plan.harmonyRoot));
|
|
65
|
+
progress(options, 'Checking the Harmony project');
|
|
66
|
+
const doctor = await timed(steps, 'doctor', () => (0, doctor_1.doctorAsync)(projectRoot, {
|
|
67
|
+
requireBuildTools: true,
|
|
68
|
+
requireDeviceTools: options.requireDeviceTools,
|
|
69
|
+
validateGeneratedProject: exists,
|
|
70
|
+
validateModules: false,
|
|
71
|
+
}));
|
|
72
|
+
assertDoctor(doctor);
|
|
73
|
+
if (!exists || options.sync) {
|
|
74
|
+
progress(options, exists
|
|
75
|
+
? 'Synchronizing the generated Harmony project'
|
|
76
|
+
: 'Generating the missing Harmony project');
|
|
77
|
+
await timed(steps, 'prebuild', () => (0, prebuild_1.prebuildParsedAsync)(projectRoot, [], { buildType: options.variant }));
|
|
78
|
+
steps.cngCheck = 0;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
steps.prebuild = 0;
|
|
82
|
+
if (options.skipGeneratedProjectCheck) {
|
|
83
|
+
steps.cngCheck = 0;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
progress(options, 'Checking CNG ownership and drift');
|
|
87
|
+
const check = await timed(steps, 'cngCheck', () => (0, check_1.checkAsync)(projectRoot, { buildType: options.variant }));
|
|
88
|
+
if (!check.clean) {
|
|
89
|
+
const summary = check.changes.slice(0, 8).map(change => `${change.type}:${change.path}`).join(', ');
|
|
90
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_MANIFEST_DRIFT', `Generated Harmony files differ from CNG desired state${summary ? ` (${summary})` : ''}. Run expo-harmony prebuild or retry with --sync.`, { operation: 'check' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBuildArgs = parseBuildArgs;
|
|
4
|
+
const args_1 = require("../args");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const BuildOptions = {
|
|
7
|
+
...args_1.CommonOptions,
|
|
8
|
+
sync: { type: 'boolean' },
|
|
9
|
+
variant: { type: 'string' },
|
|
10
|
+
};
|
|
11
|
+
function parseBuildArgs(argv) {
|
|
12
|
+
const { positionals, values } = (0, args_1.parseArgs)(BuildOptions, argv);
|
|
13
|
+
if (positionals.length > 1) {
|
|
14
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unexpected build positional argument: ${positionals[1]}`, {
|
|
15
|
+
operation: 'parse-arguments',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const variant = values.variant || 'debug';
|
|
19
|
+
if (variant !== 'debug' && variant !== 'release') {
|
|
20
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--variant must be debug or release.', {
|
|
21
|
+
operation: 'parse-arguments',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
help: Boolean(values.help),
|
|
26
|
+
project: positionals[0],
|
|
27
|
+
sync: Boolean(values.sync),
|
|
28
|
+
variant: variant,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/build/cli.d.ts
ADDED
package/build/cli.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
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.main = main;
|
|
7
|
+
exports.runAsync = runAsync;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const options_1 = require("./buildHap/options");
|
|
10
|
+
const options_2 = require("./doctor/options");
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const options_3 = require("./exportEmbed/options");
|
|
13
|
+
const options_4 = require("./modules/options");
|
|
14
|
+
const options_5 = require("./prebuild/options");
|
|
15
|
+
const project_1 = require("./project");
|
|
16
|
+
const projectLock_1 = require("./projectLock");
|
|
17
|
+
const options_6 = require("./run/options");
|
|
18
|
+
const options_7 = require("./start/options");
|
|
19
|
+
const Help = `Usage: expo-harmony <command> [project] [options]
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
start Start Expo Metro for Harmony development
|
|
23
|
+
prebuild Generate the Harmony native project with Expo CNG
|
|
24
|
+
prebuild --clean Safely recreate the managed Harmony directory
|
|
25
|
+
prebuild --check Compare generated desired state without project writes
|
|
26
|
+
build Build a HAP without selecting or contacting a device
|
|
27
|
+
doctor Validate config, versions, Metro, RNOH, SDK, and signing
|
|
28
|
+
modules list List native module candidates and their discovery source
|
|
29
|
+
modules inspect Resolve Harmony metadata (--package narrows the result)
|
|
30
|
+
modules verify Validate module config, registration conflicts and safe paths
|
|
31
|
+
export:embed Export validated Hermes bytecode, assets, and a source map
|
|
32
|
+
run Build, install, and launch the Harmony app
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--no-install Skip dependency install (prebuild) or HAP install (run)
|
|
36
|
+
--npm|--yarn|--pnpm|--bun
|
|
37
|
+
Select the package manager used by prebuild dependency install
|
|
38
|
+
--skip-dependency-update <packages>
|
|
39
|
+
Preserve comma-separated dependency versions
|
|
40
|
+
--device <id-or-name> Select an HDC target or start a local emulator by name
|
|
41
|
+
--variant <mode> Build debug or release (default: debug)
|
|
42
|
+
--no-bundler Use an already-running Expo Metro server
|
|
43
|
+
--app-id <bundleName>
|
|
44
|
+
Launch another installed app (requires --no-install when different)
|
|
45
|
+
--port <number> Metro and device reverse port (default: 8081)
|
|
46
|
+
--sync Re-run prebuild before building
|
|
47
|
+
--check Validate an existing export without writing
|
|
48
|
+
--reset-cache Reset Metro while exporting or starting
|
|
49
|
+
-c, --clear Alias for --reset-cache (start)
|
|
50
|
+
-h, --help Show this help
|
|
51
|
+
`;
|
|
52
|
+
function parseInvocation(argv) {
|
|
53
|
+
if (argv.length === 0 || (argv.length === 1 && ['--help', '-h'].includes(argv[0]))) {
|
|
54
|
+
return { command: 'help' };
|
|
55
|
+
}
|
|
56
|
+
const command = argv[0];
|
|
57
|
+
if (!['build', 'prebuild', 'doctor', 'export:embed', 'modules', 'run', 'start'].includes(command)) {
|
|
58
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unknown command: ${command}`, {
|
|
59
|
+
operation: 'parse-arguments',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const parsed = command === 'build'
|
|
63
|
+
? (0, options_1.parseBuildArgs)(argv.slice(1))
|
|
64
|
+
: command === 'prebuild'
|
|
65
|
+
? (0, options_5.parsePrebuildArgs)(argv.slice(1), { allowProject: true })
|
|
66
|
+
: command === 'doctor'
|
|
67
|
+
? (0, options_2.parseDoctorArgs)(argv.slice(1))
|
|
68
|
+
: command === 'export:embed'
|
|
69
|
+
? (0, options_3.parseExportEmbedArgs)(argv.slice(1))
|
|
70
|
+
: command === 'modules'
|
|
71
|
+
? (0, options_4.parseModulesArgs)(argv.slice(1))
|
|
72
|
+
: command === 'start'
|
|
73
|
+
? (0, options_7.parseStartArgs)(argv.slice(1))
|
|
74
|
+
: (0, options_6.parseRunArgs)(argv.slice(1));
|
|
75
|
+
if (parsed.help)
|
|
76
|
+
return { command: 'help' };
|
|
77
|
+
const projectRoot = (0, project_1.resolveProject)(parsed.project ? node_path_1.default.resolve(parsed.project) : process.cwd());
|
|
78
|
+
return { command, parsed, projectRoot };
|
|
79
|
+
}
|
|
80
|
+
async function runAsync(argv = process.argv.slice(2), io = console) {
|
|
81
|
+
const invocation = parseInvocation(argv);
|
|
82
|
+
if (invocation.command === 'help') {
|
|
83
|
+
io.log(Help);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
if (invocation.command === 'start') {
|
|
87
|
+
const { startExpoMetroAsync } = await import('./run/metro.js');
|
|
88
|
+
const metro = await startExpoMetroAsync(invocation.projectRoot, {
|
|
89
|
+
...invocation.parsed,
|
|
90
|
+
interactive: true,
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
if (metro.owner === 'existing') {
|
|
94
|
+
io.log(`Expo Metro is already running on port ${metro.port}.`);
|
|
95
|
+
if (invocation.parsed.resetCache) {
|
|
96
|
+
io.warn('Stop the existing Metro server and run this command again to reset its cache.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
|
|
101
|
+
await metro.waitAsync();
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await metro.stop();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (invocation.command === 'doctor') {
|
|
110
|
+
const { doctorAsync, formatDoctor } = await import('./doctor/doctor.js');
|
|
111
|
+
const result = await doctorAsync(invocation.projectRoot, { requireBuildTools: true });
|
|
112
|
+
io.log(formatDoctor(result));
|
|
113
|
+
return result.ok ? 0 : 1;
|
|
114
|
+
}
|
|
115
|
+
if (invocation.command === 'build') {
|
|
116
|
+
const { buildHarmonyAsync } = await import('./buildHap/build.js');
|
|
117
|
+
const result = await buildHarmonyAsync(invocation.projectRoot, {
|
|
118
|
+
...invocation.parsed,
|
|
119
|
+
io,
|
|
120
|
+
});
|
|
121
|
+
io.log(`Built ${result.variant} HAP at ${result.hapPath}; no device actions were performed.`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (invocation.command === 'export:embed') {
|
|
125
|
+
const { exportEmbedAsync } = await import('./exportEmbed/export.js');
|
|
126
|
+
const result = await exportEmbedAsync(invocation.projectRoot, invocation.parsed);
|
|
127
|
+
if (invocation.parsed.check)
|
|
128
|
+
io.log('Harmony export bundle, assets, and source map are valid.');
|
|
129
|
+
else
|
|
130
|
+
io.log(`Exported Hermes bytecode ${result.bundle.path} with ${result.assets.length} asset file(s).`);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (invocation.command === 'modules') {
|
|
134
|
+
const { formatModulesResult, runModulesCommandAsync } = await import('./modules/modules.js');
|
|
135
|
+
const result = await runModulesCommandAsync(invocation.projectRoot, invocation.parsed);
|
|
136
|
+
io.log(formatModulesResult(result));
|
|
137
|
+
return result.ok ? 0 : 1;
|
|
138
|
+
}
|
|
139
|
+
if (invocation.command === 'run') {
|
|
140
|
+
const { runHarmonySessionAsync } = await import('./run/run.js');
|
|
141
|
+
const session = await runHarmonySessionAsync(invocation.projectRoot, {
|
|
142
|
+
...invocation.parsed,
|
|
143
|
+
interactiveBundler: true,
|
|
144
|
+
io,
|
|
145
|
+
});
|
|
146
|
+
const { result } = session;
|
|
147
|
+
io.log(`Launched ${result.bundleName} on ${result.device.id} (${result.variant}).`);
|
|
148
|
+
if (session.metro.owner === 'started') {
|
|
149
|
+
io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
|
|
150
|
+
await session.metro.waitAsync();
|
|
151
|
+
}
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
const { check, clean, passthrough } = invocation.parsed;
|
|
155
|
+
if (check && passthrough.length) {
|
|
156
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--check cannot be combined with mutating prebuild options.');
|
|
157
|
+
}
|
|
158
|
+
return (0, projectLock_1.withHarmonyProjectLockAsync)(invocation.projectRoot, check ? 'prebuild-check' : 'prebuild', async () => {
|
|
159
|
+
if (clean) {
|
|
160
|
+
const { assertSafeCleanTarget } = await import('./prebuild/clean.js');
|
|
161
|
+
await assertSafeCleanTarget(invocation.projectRoot);
|
|
162
|
+
}
|
|
163
|
+
const { doctorAsync, formatDoctor } = await import('./doctor/doctor.js');
|
|
164
|
+
const doctor = await doctorAsync(invocation.projectRoot, {
|
|
165
|
+
requireBuildTools: false,
|
|
166
|
+
validateGeneratedProject: !clean,
|
|
167
|
+
validateModules: false,
|
|
168
|
+
});
|
|
169
|
+
if (!doctor.ok) {
|
|
170
|
+
io.error(formatDoctor(doctor));
|
|
171
|
+
throw new errors_1.HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', 'Harmony doctor found blocking errors.', {
|
|
172
|
+
operation: 'doctor',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
for (const item of doctor.checks.filter(item => item.status === 'warn'))
|
|
176
|
+
io.warn(`! ${item.message}`);
|
|
177
|
+
if (check) {
|
|
178
|
+
const { checkAsync } = await import('./prebuild/check.js');
|
|
179
|
+
const result = await checkAsync(invocation.projectRoot);
|
|
180
|
+
if (result.clean)
|
|
181
|
+
io.log('Harmony CNG output is up to date.');
|
|
182
|
+
else
|
|
183
|
+
for (const change of result.changes)
|
|
184
|
+
io.log(`${change.type}: ${change.path}`);
|
|
185
|
+
return result.clean ? 0 : 2;
|
|
186
|
+
}
|
|
187
|
+
const { prebuildParsedAsync } = await import('./prebuild/prebuild.js');
|
|
188
|
+
await prebuildParsedAsync(invocation.projectRoot, passthrough);
|
|
189
|
+
return 0;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async function main(argv = process.argv.slice(2)) {
|
|
193
|
+
try {
|
|
194
|
+
const code = await runAsync(argv);
|
|
195
|
+
process.exitCode = code;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const code = error.code || 'ERR_HARMONY_UNKNOWN';
|
|
199
|
+
console.error(`[${code}] ${error.message}`);
|
|
200
|
+
process.exitCode = error.exitCode || 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DoctorCheck {
|
|
2
|
+
details?: unknown;
|
|
3
|
+
id: string;
|
|
4
|
+
message: string;
|
|
5
|
+
status: 'error' | 'pass' | 'warn';
|
|
6
|
+
}
|
|
7
|
+
export interface DoctorResult {
|
|
8
|
+
checks: DoctorCheck[];
|
|
9
|
+
ok: boolean;
|
|
10
|
+
projectRoot: string;
|
|
11
|
+
}
|
|
12
|
+
interface DoctorOptions {
|
|
13
|
+
requireBuildTools?: boolean;
|
|
14
|
+
requireDeviceTools?: boolean;
|
|
15
|
+
validateGeneratedProject?: boolean;
|
|
16
|
+
validateModules?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare function doctorAsync(projectRoot: string, options?: DoctorOptions): Promise<DoctorResult>;
|
|
19
|
+
declare function formatDoctor(result: DoctorResult): string;
|
|
20
|
+
export { doctorAsync, formatDoctor };
|