@expo-harmony/cli 55.0.26-harmony.1 → 55.0.26-harmony.3

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.
Files changed (73) hide show
  1. package/build/args.d.ts +19 -0
  2. package/build/args.js +27 -0
  3. package/build/bin/expo-harmony.d.ts +2 -0
  4. package/build/buildHap/build.d.ts +25 -0
  5. package/build/buildHap/build.js +96 -0
  6. package/build/buildHap/common.d.ts +24 -0
  7. package/build/buildHap/common.js +92 -0
  8. package/build/buildHap/options.d.ts +7 -0
  9. package/build/buildHap/options.js +30 -0
  10. package/build/cli.d.ts +3 -0
  11. package/build/cli.js +202 -0
  12. package/build/doctor/doctor.d.ts +20 -0
  13. package/build/doctor/doctor.js +217 -0
  14. package/build/doctor/options.d.ts +5 -0
  15. package/build/doctor/options.js +15 -0
  16. package/build/entry.d.ts +2 -0
  17. package/build/entry.js +47 -0
  18. package/build/errors.d.ts +12 -0
  19. package/build/errors.js +16 -0
  20. package/build/expo.d.ts +19 -0
  21. package/build/expo.js +53 -0
  22. package/build/exportEmbed/export.d.ts +16 -0
  23. package/build/exportEmbed/export.js +125 -0
  24. package/build/exportEmbed/manifest.d.ts +44 -0
  25. package/build/exportEmbed/manifest.js +171 -0
  26. package/build/exportEmbed/options.d.ts +7 -0
  27. package/build/exportEmbed/options.js +22 -0
  28. package/build/file.d.ts +9 -0
  29. package/build/file.js +100 -0
  30. package/build/index.d.ts +12 -0
  31. package/build/modules/modules.d.ts +42 -0
  32. package/build/modules/modules.js +84 -0
  33. package/build/modules/options.d.ts +10 -0
  34. package/build/modules/options.js +45 -0
  35. package/build/path.d.ts +4 -0
  36. package/build/path.js +27 -0
  37. package/build/prebuild/check.d.ts +6 -0
  38. package/build/prebuild/check.js +139 -0
  39. package/build/prebuild/clean.d.ts +2 -0
  40. package/build/prebuild/clean.js +50 -0
  41. package/build/prebuild/options.d.ts +10 -0
  42. package/build/prebuild/options.js +50 -0
  43. package/build/prebuild/prebuild.d.ts +6 -0
  44. package/build/prebuild/prebuild.js +62 -0
  45. package/build/prebuild/template.d.ts +8 -0
  46. package/build/prebuild/template.js +86 -0
  47. package/build/process.d.ts +31 -0
  48. package/build/process.js +236 -0
  49. package/build/project.d.ts +2 -0
  50. package/build/project.js +23 -0
  51. package/build/projectLock.d.ts +2 -0
  52. package/build/projectLock.js +180 -0
  53. package/build/run/cache.d.ts +11 -0
  54. package/build/run/cache.js +73 -0
  55. package/build/run/devices.d.ts +29 -0
  56. package/build/run/devices.js +211 -0
  57. package/build/run/emulators.d.ts +13 -0
  58. package/build/run/emulators.js +85 -0
  59. package/build/run/install.d.ts +7 -0
  60. package/build/run/install.js +26 -0
  61. package/build/run/metro.d.ts +17 -0
  62. package/build/run/metro.js +156 -0
  63. package/build/run/options.d.ts +13 -0
  64. package/build/run/options.js +60 -0
  65. package/build/run/run.d.ts +51 -0
  66. package/build/run/run.js +174 -0
  67. package/build/start/options.d.ts +7 -0
  68. package/build/start/options.js +31 -0
  69. package/build/tools.d.ts +39 -0
  70. package/build/tools.js +226 -0
  71. package/build/upstream.d.ts +9 -0
  72. package/build/upstream.js +17 -0
  73. package/package.json +4 -4
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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,7 @@
1
+ declare function parseBuildArgs(argv: string[]): {
2
+ help: boolean;
3
+ project: string;
4
+ sync: boolean;
5
+ variant: "debug" | "release";
6
+ };
7
+ export { parseBuildArgs };
@@ -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
@@ -0,0 +1,3 @@
1
+ declare function runAsync(argv?: string[], io?: Pick<Console, 'error' | 'log' | 'warn'>): Promise<number>;
2
+ declare function main(argv?: string[]): Promise<void>;
3
+ export { main, runAsync };
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 };