@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.
Files changed (117) hide show
  1. package/README.md +74 -46
  2. package/build/bin/expo-harmony.js +1 -1
  3. package/build/buildHap/build.d.ts +3 -10
  4. package/build/buildHap/build.js +11 -69
  5. package/build/buildHap/index.d.ts +2 -0
  6. package/build/buildHap/index.js +19 -0
  7. package/build/cli.d.ts +3 -3
  8. package/build/cli.js +21 -187
  9. package/build/command.d.ts +9 -0
  10. package/build/command.js +20 -0
  11. package/build/doctor/doctor.d.ts +1 -1
  12. package/build/doctor/doctor.js +64 -56
  13. package/build/doctor/index.d.ts +2 -0
  14. package/build/doctor/index.js +16 -0
  15. package/build/exportEmbed/export.d.ts +2 -2
  16. package/build/exportEmbed/export.js +53 -49
  17. package/build/exportEmbed/index.d.ts +2 -0
  18. package/build/exportEmbed/index.js +19 -0
  19. package/build/file.d.ts +2 -1
  20. package/build/file.js +18 -8
  21. package/build/help.d.ts +1 -0
  22. package/build/help.js +36 -0
  23. package/build/index.d.ts +5 -3
  24. package/build/index.js +6 -5
  25. package/build/internal/prebuild.d.ts +3 -0
  26. package/build/internal/prebuild.js +9 -0
  27. package/build/log.d.ts +4 -0
  28. package/build/log.js +6 -0
  29. package/build/modules/index.d.ts +2 -0
  30. package/build/modules/index.js +16 -0
  31. package/build/native/bare.d.ts +4 -0
  32. package/build/native/bare.js +130 -0
  33. package/build/native/build.d.ts +15 -0
  34. package/build/native/build.js +67 -0
  35. package/build/{run → native}/cache.d.ts +2 -2
  36. package/build/{run → native}/cache.js +8 -8
  37. package/build/native/hvigor.d.ts +2 -0
  38. package/build/native/hvigor.js +77 -0
  39. package/build/native/install.d.ts +5 -0
  40. package/build/native/install.js +13 -0
  41. package/build/native/prepareProject.d.ts +11 -0
  42. package/build/native/prepareProject.js +68 -0
  43. package/build/native/project.d.ts +8 -0
  44. package/build/native/project.js +58 -0
  45. package/build/native/toolchain.d.ts +16 -0
  46. package/build/{tools.js → native/toolchain.js} +1 -57
  47. package/build/native/types.d.ts +47 -0
  48. package/build/native/types.js +2 -0
  49. package/build/prebuild/check.d.ts +7 -3
  50. package/build/prebuild/check.js +26 -20
  51. package/build/prebuild/clean.js +5 -5
  52. package/build/prebuild/index.d.ts +2 -0
  53. package/build/prebuild/index.js +51 -0
  54. package/build/prebuild/prebuild.js +11 -3
  55. package/build/prebuild/template.js +2 -2
  56. package/build/process.d.ts +11 -1
  57. package/build/process.js +16 -0
  58. package/build/profile.d.ts +1 -0
  59. package/build/profile.js +12 -0
  60. package/build/projectLock.js +7 -2
  61. package/build/run/devices.d.ts +1 -1
  62. package/build/run/devices.js +15 -10
  63. package/build/run/emulators.d.ts +1 -1
  64. package/build/run/emulators.js +2 -5
  65. package/build/run/index.d.ts +2 -0
  66. package/build/run/index.js +25 -0
  67. package/build/run/metro.js +1 -1
  68. package/build/run/run.d.ts +4 -10
  69. package/build/run/run.js +45 -90
  70. package/build/start/index.d.ts +2 -0
  71. package/build/start/index.js +33 -0
  72. package/package.json +14 -4
  73. package/src/bin/expo-harmony.ts +2 -2
  74. package/src/buildHap/build.ts +13 -105
  75. package/src/buildHap/index.ts +19 -0
  76. package/src/cli.ts +24 -227
  77. package/src/command.ts +24 -0
  78. package/src/doctor/doctor.ts +63 -61
  79. package/src/doctor/index.ts +16 -0
  80. package/src/exportEmbed/export.ts +64 -55
  81. package/src/exportEmbed/index.ts +17 -0
  82. package/src/file.ts +20 -11
  83. package/src/help.ts +33 -0
  84. package/src/index.ts +5 -3
  85. package/src/internal/prebuild.ts +3 -0
  86. package/src/log.ts +5 -0
  87. package/src/modules/index.ts +16 -0
  88. package/src/native/bare.ts +145 -0
  89. package/src/native/build.ts +81 -0
  90. package/src/{run → native}/cache.ts +10 -9
  91. package/src/native/hvigor.ts +81 -0
  92. package/src/native/install.ts +17 -0
  93. package/src/native/prepareProject.ts +99 -0
  94. package/src/native/project.ts +88 -0
  95. package/src/{tools.ts → native/toolchain.ts} +11 -118
  96. package/src/native/types.ts +44 -0
  97. package/src/prebuild/check.ts +30 -19
  98. package/src/prebuild/clean.ts +3 -3
  99. package/src/prebuild/index.ts +56 -0
  100. package/src/prebuild/prebuild.ts +11 -1
  101. package/src/prebuild/template.ts +2 -2
  102. package/src/process.ts +38 -1
  103. package/src/profile.ts +13 -0
  104. package/src/projectLock.ts +9 -1
  105. package/src/run/devices.ts +16 -11
  106. package/src/run/emulators.ts +4 -6
  107. package/src/run/index.ts +25 -0
  108. package/src/run/metro.ts +1 -1
  109. package/src/run/run.ts +56 -129
  110. package/src/start/index.ts +31 -0
  111. package/build/buildHap/common.d.ts +0 -24
  112. package/build/buildHap/common.js +0 -92
  113. package/build/run/install.d.ts +0 -7
  114. package/build/run/install.js +0 -26
  115. package/build/tools.d.ts +0 -39
  116. package/src/buildHap/common.ts +0 -145
  117. package/src/run/install.ts +0 -41
@@ -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
+ }
@@ -15,6 +15,6 @@ interface DoctorOptions {
15
15
  validateGeneratedProject?: boolean;
16
16
  validateModules?: boolean;
17
17
  }
18
- declare function doctorAsync(projectRoot: string, options?: DoctorOptions): Promise<DoctorResult>;
18
+ declare function doctorAsync(root: string, options?: DoctorOptions): Promise<DoctorResult>;
19
19
  declare function formatDoctor(result: DoctorResult): string;
20
20
  export { doctorAsync, formatDoctor };
@@ -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 native_project_1 = require("@expo-harmony/prebuild-config/native-project");
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 tools_1 = require("../tools");
17
+ const project_1 = require("../native/project");
18
+ const toolchain_1 = require("../native/toolchain");
19
19
  const upstream_1 = require("../upstream");
20
- function hasPlugin(plugins, packageName) {
21
- return (plugins || []).some(plugin => (Array.isArray(plugin) ? plugin[0] : plugin) === packageName);
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(projectRoot, packageName) {
27
- const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
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
- projectRequire.resolve(`${packageName}/package.json`);
30
+ require.resolve(`${name}/package.json`);
30
31
  return true;
31
32
  }
32
33
  catch {
33
34
  try {
34
- projectRequire.resolve(packageName);
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(projectRoot) {
43
- const projectRequire = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
44
- let metroConfig;
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
- metroConfig = projectRequire('metro-config');
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 metroConfig.resolveConfig !== 'function') {
52
+ if (typeof metro.resolveConfig !== 'function') {
52
53
  const version = (() => {
53
54
  try {
54
- return projectRequire('metro-config/package.json').version;
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 hadMetroTarget = Object.hasOwn(process.env, 'EXPO_METRO_TARGET');
63
- const previousMetroTarget = process.env.EXPO_METRO_TARGET;
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 metroConfig.resolveConfig(undefined, projectRoot);
68
+ resolved = await metro.resolveConfig(undefined, root);
68
69
  }
69
70
  finally {
70
- if (hadMetroTarget)
71
- process.env.EXPO_METRO_TARGET = previousMetroTarget;
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(projectRoot, options = {}) {
85
+ async function doctorUnlockedAsync(root, options = {}) {
85
86
  const checks = [];
86
- const unavailableToolStatus = options.requireBuildTools ? 'error' : 'warn';
87
+ const unavailable = options.requireBuildTools ? 'error' : 'warn';
87
88
  let config;
89
+ const bare = (0, bare_1.isBareHarmonyProject)(root);
88
90
  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.'));
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, signing_1.validateHarmonySigningConfigFile)(projectRoot, harmony.signingConfigFile);
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(projectRoot);
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 packageName of upstream_1.RequiredProjectPackages) {
125
- if (canResolvePackage(projectRoot, packageName)) {
126
- checks.push(check(`package:${packageName}`, 'pass', `${packageName} is resolvable.`));
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:${packageName}`, 'error', `${packageName} is not resolvable from the app.`));
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, tools_1.resolveHarmonyToolchain)();
152
- let sdkCheck;
159
+ const toolchain = (0, toolchain_1.resolveHarmonyToolchain)();
160
+ let sdk;
153
161
  if (toolchain.sdkHome) {
154
- sdkCheck = check('harmony-sdk', 'pass', `Complete Harmony SDK root was resolved at ${toolchain.sdkHome}.`);
162
+ sdk = check('harmony-sdk', 'pass', `Complete Harmony SDK root was resolved at ${toolchain.sdkHome}.`);
155
163
  }
156
164
  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.');
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(sdkCheck);
167
+ checks.push(sdk);
160
168
  const tools = [
161
- ['ohpm', toolchain.ohpm, ['--version'], unavailableToolStatus],
162
- ['hvigor-command', toolchain.hvigor, ['--version'], unavailableToolStatus],
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'], unavailableToolStatus]);
173
+ tools.unshift(['hdc', toolchain.hdc, ['-v'], unavailable]);
166
174
  }
167
- for (const [id, tool, versionArgs, unavailableStatus] of tools) {
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, ...versionArgs], {
178
+ const result = await (0, process_1.spawnAsync)(tool.command, [...tool.args, ...args], {
171
179
  capture: true,
172
- cwd: projectRoot,
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, unavailableStatus, `${command} is unavailable or unhealthy; HAP build cannot be verified.`));
186
+ : check(id, status, `${command} is unavailable or unhealthy; HAP build cannot be verified.`));
179
187
  }
180
188
  catch {
181
- checks.push(check(id, unavailableStatus, `${command} is unavailable; generation remains available.`));
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, tools_1.resolveHarmonyBuildPlanIfPresentAsync)(projectRoot);
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', 'Generated root and module Hvigor files are required.'));
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, native_project_1.isRnohAutolinkingDisabled)(content)
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 generated module Hvigor file must disable RNOH autolinking.'));
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(projectRoot, options = {}) {
211
- return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'doctor', () => doctorUnlockedAsync(projectRoot, options));
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,2 @@
1
+ import { type Command } from '../command';
2
+ export declare const command: Command;
@@ -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(projectRoot: string, options?: ExportOptions): Promise<HarmonyExportManifest>;
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 tools_1 = require("../tools");
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(projectRoot, entryFile, temporary, options = {}) {
19
+ function createExpoExportEmbedArgs(root, entry, temp, options = {}) {
20
20
  return [
21
21
  'export:embed',
22
22
  '--platform', 'harmony',
23
- '--entry-file', entryFile,
24
- '--bundle-output', temporary.javascript,
25
- '--assets-dest', temporary.assets,
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', temporary.metroSourceMap,
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
- projectRoot,
32
+ root,
33
33
  ];
34
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' });
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 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'),
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(temporary.assets, { recursive: true });
59
- const expo = (0, expo_1.resolveExpoCli)(projectRoot);
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(projectRoot, entryFile, temporary, options),
61
+ ...createExpoExportEmbedArgs(root, entry, temp, options),
63
62
  ], {
64
63
  capture: true,
65
- cwd: projectRoot,
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(temporary.javascript, 'utf8'),
85
- node_fs_1.default.promises.readFile(temporary.metroSourceMap, 'utf8'),
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)(projectRoot);
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 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');
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: entryFile,
96
+ filename: entry,
98
97
  map,
99
98
  minify: true,
100
- projectRoot: temporaryRoot,
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(temporary.bundle, output.hbc),
107
- node_fs_1.default.promises.writeFile(temporary.sourceMap, output.sourcemap),
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)(temporary.bundle);
116
- await (0, manifest_1.assertSourceMap)(temporary.sourceMap);
117
- return await (0, manifest_1.publishExportAsync)(projectRoot, paths, temporary, entryFile, bytecode);
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(temporaryRoot, { force: true, recursive: true });
124
+ await node_fs_1.default.promises.rm(directory, { force: true, recursive: true });
121
125
  }
122
126
  }
123
- async function exportEmbedAsync(projectRoot, options = {}) {
124
- return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'export:embed', () => exportEmbedUnlockedAsync(projectRoot, options));
127
+ async function exportEmbedAsync(root, options = {}) {
128
+ return (0, projectLock_1.withHarmonyProjectLockAsync)(root, 'export:embed', () => exportEmbedUnlockedAsync(root, options));
125
129
  }
@@ -0,0 +1,2 @@
1
+ import { type Command } from '../command';
2
+ export declare const command: Command;
@@ -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
- export { atomicCopy, atomicWriteJson, describeFile, listFiles, removeEmptyParents };
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 temporary = 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
+ 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, temporary, node_fs_1.default.constants.COPYFILE_EXCL);
72
- await node_fs_1.default.promises.rename(temporary, destination);
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(temporary, { force: true });
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 temporary = 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
+ 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(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' });
83
- await node_fs_1.default.promises.rename(temporary, destination);
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(temporary, { force: true });
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
+ }
@@ -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, resolveHarmonyToolchain } from './tools';
6
- export { installHarmonyDependenciesAsync } from './run/install';
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 './run/cache';
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';