@expo-harmony/cli 55.0.26-harmony.8 → 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 (115) 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/run.d.ts +4 -10
  68. package/build/run/run.js +45 -90
  69. package/build/start/index.d.ts +2 -0
  70. package/build/start/index.js +33 -0
  71. package/package.json +14 -4
  72. package/src/bin/expo-harmony.ts +2 -2
  73. package/src/buildHap/build.ts +13 -105
  74. package/src/buildHap/index.ts +19 -0
  75. package/src/cli.ts +24 -227
  76. package/src/command.ts +24 -0
  77. package/src/doctor/doctor.ts +63 -61
  78. package/src/doctor/index.ts +16 -0
  79. package/src/exportEmbed/export.ts +64 -55
  80. package/src/exportEmbed/index.ts +17 -0
  81. package/src/file.ts +20 -11
  82. package/src/help.ts +33 -0
  83. package/src/index.ts +5 -3
  84. package/src/internal/prebuild.ts +3 -0
  85. package/src/log.ts +5 -0
  86. package/src/modules/index.ts +16 -0
  87. package/src/native/bare.ts +145 -0
  88. package/src/native/build.ts +81 -0
  89. package/src/{run → native}/cache.ts +10 -9
  90. package/src/native/hvigor.ts +81 -0
  91. package/src/native/install.ts +17 -0
  92. package/src/native/prepareProject.ts +99 -0
  93. package/src/native/project.ts +88 -0
  94. package/src/{tools.ts → native/toolchain.ts} +11 -118
  95. package/src/native/types.ts +44 -0
  96. package/src/prebuild/check.ts +30 -19
  97. package/src/prebuild/clean.ts +3 -3
  98. package/src/prebuild/index.ts +56 -0
  99. package/src/prebuild/prebuild.ts +11 -1
  100. package/src/prebuild/template.ts +2 -2
  101. package/src/process.ts +38 -1
  102. package/src/profile.ts +13 -0
  103. package/src/projectLock.ts +9 -1
  104. package/src/run/devices.ts +16 -11
  105. package/src/run/emulators.ts +4 -6
  106. package/src/run/index.ts +25 -0
  107. package/src/run/run.ts +56 -129
  108. package/src/start/index.ts +31 -0
  109. package/build/buildHap/common.d.ts +0 -24
  110. package/build/buildHap/common.js +0 -92
  111. package/build/run/install.d.ts +0 -7
  112. package/build/run/install.js +0 -26
  113. package/build/tools.d.ts +0 -39
  114. package/src/buildHap/common.ts +0 -145
  115. package/src/run/install.ts +0 -41
package/src/run/run.ts CHANGED
@@ -7,33 +7,18 @@ import {
7
7
  selectDeviceAsync,
8
8
  } from './devices';
9
9
  import { HarmonyCliError } from '../errors';
10
- import { exportEmbedAsync } from '../exportEmbed/export';
11
- import type { HarmonyExportManifest } from '../exportEmbed/manifest';
12
- import {
13
- resolveHarmonyBuildPlanAsync,
14
- resolveHarmonyEmulator,
15
- resolveHarmonyToolchain,
16
- type HarmonyBuildPlan,
17
- } from '../tools';
18
- import { installHarmonyDependenciesAsync } from './install';
10
+ import { resolveHarmonyEmulator, resolveHarmonyToolchain } from '../native/toolchain';
11
+ import { type HarmonyBuildPlan } from '../native/types';
19
12
  import {
20
13
  requireExistingMetroAsync,
21
14
  startExpoMetroAsync,
22
15
  type MetroSession,
23
16
  } from './metro';
24
- import {
25
- commitHarmonyNativeBuildCacheAsync,
26
- prepareHarmonyNativeBuildCacheAsync,
27
- } from './cache';
28
- import { toPosixPath } from '../path';
29
17
  import { withHarmonyProjectLockAsync } from '../projectLock';
30
- import {
31
- ensureGeneratedProjectAsync,
32
- isNonEmptyRegularFile,
33
- progress,
34
- runCheckedAsync,
35
- timed,
36
- } from '../buildHap/common';
18
+ import { buildNativeAsync, type NativeBuildResult } from '../native/build';
19
+ import { prepareNativeProjectAsync } from '../native/prepareProject';
20
+ import { progress } from '../log';
21
+ import { timedAsync } from '../profile';
37
22
 
38
23
  export interface HarmonyRunOptions {
39
24
  appId?: string;
@@ -47,11 +32,9 @@ export interface HarmonyRunOptions {
47
32
  variant?: 'debug' | 'release';
48
33
  }
49
34
 
50
- export interface HarmonyRunResult {
35
+ export interface HarmonyRunResult extends NativeBuildResult {
51
36
  bundleName: string;
52
37
  device: { id: string; transport: string };
53
- export: null | { assetCount: number; bundleSha256: string; sourceMapSha256: string };
54
- hapPath: string;
55
38
  installed: boolean;
56
39
  launched: true;
57
40
  metro: { owner: 'disabled' | 'existing' | 'started'; port: number };
@@ -62,7 +45,6 @@ export interface HarmonyRunResult {
62
45
  }
63
46
 
64
47
  interface HarmonyRunSessionOptions extends HarmonyRunOptions {
65
- /** Give a CLI-started Metro process ownership of the current terminal. */
66
48
  interactiveBundler?: boolean;
67
49
  }
68
50
 
@@ -101,10 +83,10 @@ function resolveRunIdentity(plan: HarmonyBuildPlan, options: NormalizedRunOption
101
83
  }
102
84
 
103
85
  async function runHarmonyUnlockedAsync(
104
- projectRoot: string,
86
+ root: string,
105
87
  options: HarmonyRunSessionOptions = {}
106
88
  ): Promise<HarmonyRunSession> {
107
- const normalizedOptions: NormalizedRunOptions = {
89
+ const settings: NormalizedRunOptions = {
108
90
  appId: options.appId,
109
91
  device: options.device,
110
92
  interactiveBundler: Boolean(options.interactiveBundler),
@@ -118,105 +100,52 @@ async function runHarmonyUnlockedAsync(
118
100
  };
119
101
  const steps: Record<string, number> = {};
120
102
 
121
- await ensureGeneratedProjectAsync(projectRoot, {
122
- ...normalizedOptions,
123
- skipGeneratedProjectCheck: true,
103
+ const plan = await prepareNativeProjectAsync(root, {
104
+ ...settings,
105
+ check: false,
124
106
  }, steps);
125
107
 
126
- const plan = await timed(steps, 'buildPlan', () => resolveHarmonyBuildPlanAsync(
127
- projectRoot,
128
- { buildMode: normalizedOptions.variant }
129
- ));
130
- const identity = resolveRunIdentity(plan, normalizedOptions);
131
- const toolchain = resolveHarmonyToolchain();
108
+ const identity = resolveRunIdentity(plan, settings);
109
+ const tools = resolveHarmonyToolchain();
132
110
 
133
- progress(normalizedOptions, 'Selecting a Harmony device or emulator');
134
- const device = await timed(steps, 'device', () => selectDeviceAsync(
135
- toolchain.hdc,
136
- normalizedOptions.device,
111
+ progress(settings, 'Selecting a Harmony device or emulator');
112
+ const device = await timedAsync(steps, 'device', () => selectDeviceAsync(
113
+ tools.hdc,
114
+ settings.device,
137
115
  {
138
116
  cwd: plan.harmonyRoot,
139
- emulator: resolveHarmonyEmulator(toolchain),
140
- emulatorLogFile: path.join(projectRoot, '.expo', 'harmony', 'emulator.log'),
141
- onProgress: message => progress(normalizedOptions, message),
117
+ emulator: resolveHarmonyEmulator(tools),
118
+ emulatorLogFile: path.join(root, '.expo', 'harmony', 'emulator.log'),
119
+ onProgress: message => progress(settings, message),
142
120
  }
143
121
  ));
144
122
 
145
- let exportManifest: HarmonyExportManifest | null = null;
146
- if (normalizedOptions.variant === 'release') {
147
- progress(normalizedOptions, 'Exporting the release Hermes bundle');
148
- exportManifest = await timed(steps, 'export', () => exportEmbedAsync(
149
- projectRoot,
150
- { resetCache: normalizedOptions.resetCache, skipDoctor: true }
151
- ));
152
- } else {
153
- steps.export = 0;
154
- }
155
-
156
- progress(normalizedOptions, 'Installing Harmony project dependencies');
157
- await timed(steps, 'ohpm', () => installHarmonyDependenciesAsync(plan, toolchain));
123
+ const built = await buildNativeAsync(root, plan, tools, settings, steps);
158
124
 
159
125
  let metro: HarmonyRunSession['metro'] = {
160
126
  owner: 'disabled',
161
- port: normalizedOptions.port,
127
+ port: settings.port,
162
128
  stop: async () => {},
163
129
  waitAsync: async () => {},
164
130
  };
165
- try {
166
- progress(normalizedOptions, 'Checking Harmony native dependency cache');
167
- const nativeBuildCache = await timed(steps, 'nativeCache', () => (
168
- prepareHarmonyNativeBuildCacheAsync(projectRoot, plan)
169
- ));
170
131
 
171
- if (nativeBuildCache.changed) {
172
- progress(normalizedOptions, 'Invalidated stale Harmony native build objects');
173
- }
174
-
175
- progress(normalizedOptions, `Building the ${normalizedOptions.variant} HAP`);
176
- const buildEnv = {
177
- ...process.env,
178
- EXPO_HARMONY_NODE: process.env.EXPO_HARMONY_NODE || process.execPath,
179
- EXPO_METRO_TARGET: 'harmony',
180
- HERMES_V1_ENABLED: 'true',
181
- ...(normalizedOptions.variant === 'release' ? { EXPO_HARMONY_BUNDLE_PREBUILT: '1' } : {}),
182
- ...(toolchain.sdkHome && !process.env.DEVECO_SDK_HOME
183
- ? { DEVECO_SDK_HOME: toolchain.sdkHome }
184
- : {}),
185
- };
186
- await timed(steps, 'build', () => runCheckedAsync(toolchain.hvigor.command, [
187
- ...toolchain.hvigor.args,
188
- ...plan.hvigorArgs,
189
- ], {
190
- code: 'ERR_HARMONY_BUILD_FAILED',
191
- cwd: plan.harmonyRoot,
192
- env: buildEnv,
193
- message: 'Hvigor build',
194
- operation: 'hvigor-build',
195
- timeoutMs: 15 * 60_000,
196
- }));
197
-
198
- if (!isNonEmptyRegularFile(plan.expectedHap)) {
199
- throw new HarmonyCliError('ERR_HARMONY_HAP_MISSING', 'Hvigor completed without producing the expected non-empty regular HAP.', { operation: 'verify-hap' });
200
- }
201
-
202
- await timed(steps, 'nativeCacheCommit', () => commitHarmonyNativeBuildCacheAsync(nativeBuildCache));
203
-
204
- if (normalizedOptions.variant === 'debug') {
205
- progress(normalizedOptions, normalizedOptions.noBundler
132
+ try {
133
+ if (settings.variant === 'debug') {
134
+ progress(settings, settings.noBundler
206
135
  ? 'Connecting to the existing Expo Metro server'
207
136
  : 'Starting Expo Metro');
208
- metro = await timed(steps, 'metro', () => normalizedOptions.noBundler
209
- ? requireExistingMetroAsync(normalizedOptions.port)
210
- : startExpoMetroAsync(projectRoot, {
211
- interactive: normalizedOptions.interactiveBundler,
212
- port: normalizedOptions.port,
213
- resetCache: normalizedOptions.resetCache,
137
+ metro = await timedAsync(steps, 'metro', () => settings.noBundler
138
+ ? requireExistingMetroAsync(settings.port)
139
+ : startExpoMetroAsync(root, {
140
+ interactive: settings.interactiveBundler,
141
+ port: settings.port,
142
+ resetCache: settings.resetCache,
214
143
  }));
215
144
 
216
- await timed(steps, 'metroPort', () => configureMetroPortAsync(
217
- toolchain.hdc,
145
+ await timedAsync(steps, 'metroPort', () => configureMetroPortAsync(
146
+ tools.hdc,
218
147
  device,
219
- normalizedOptions.port,
148
+ settings.port,
220
149
  { cwd: plan.harmonyRoot }
221
150
  ));
222
151
  } else {
@@ -224,21 +153,21 @@ async function runHarmonyUnlockedAsync(
224
153
  steps.metroPort = 0;
225
154
  }
226
155
 
227
- if (normalizedOptions.noInstall) {
156
+ if (settings.noInstall) {
228
157
  steps.install = 0;
229
158
  } else {
230
- progress(normalizedOptions, `Installing the HAP on ${device.id}`);
231
- await timed(steps, 'install', () => installHapAsync(
232
- toolchain.hdc,
159
+ progress(settings, `Installing the HAP on ${device.id}`);
160
+ await timedAsync(steps, 'install', () => installHapAsync(
161
+ tools.hdc,
233
162
  device,
234
163
  plan.expectedHap,
235
164
  { cwd: plan.harmonyRoot }
236
165
  ));
237
166
  }
238
167
 
239
- progress(normalizedOptions, `Launching ${identity.bundleName}`);
240
- await timed(steps, 'launch', () => launchAppAsync(
241
- toolchain.hdc,
168
+ progress(settings, `Launching ${identity.bundleName}`);
169
+ await timedAsync(steps, 'launch', () => launchAppAsync(
170
+ tools.hdc,
242
171
  device,
243
172
  identity.bundleName,
244
173
  identity.abilityName,
@@ -251,15 +180,8 @@ async function runHarmonyUnlockedAsync(
251
180
  id: device.id,
252
181
  transport: device.transport,
253
182
  },
254
- export: exportManifest
255
- ? {
256
- assetCount: exportManifest.assets.length,
257
- bundleSha256: exportManifest.bundle.sha256,
258
- sourceMapSha256: exportManifest.sourceMap.sha256,
259
- }
260
- : null,
261
- hapPath: toPosixPath(path.relative(projectRoot, plan.expectedHap)),
262
- installed: !normalizedOptions.noInstall,
183
+ ...built,
184
+ installed: !settings.noInstall,
263
185
  launched: true,
264
186
  metro: {
265
187
  owner: metro.owner,
@@ -268,32 +190,37 @@ async function runHarmonyUnlockedAsync(
268
190
  ok: true,
269
191
  schemaVersion: 1,
270
192
  steps,
271
- variant: normalizedOptions.variant,
193
+ variant: settings.variant,
272
194
  };
273
195
 
274
196
  return { metro, result };
275
197
  } catch (error) {
276
198
  await metro.stop();
277
- throw error;
199
+
200
+ throw new HarmonyCliError(error?.code || 'ERR_HARMONY_UNKNOWN', error?.message || 'Harmony run failed.', {
201
+ cause: error,
202
+ exitCode: error?.exitCode,
203
+ operation: error?.operation,
204
+ });
278
205
  }
279
206
  }
280
207
 
281
208
  async function runHarmonySessionAsync(
282
- projectRoot: string,
209
+ root: string,
283
210
  options: HarmonyRunSessionOptions = {}
284
211
  ): Promise<HarmonyRunSession> {
285
212
  return withHarmonyProjectLockAsync(
286
- projectRoot,
213
+ root,
287
214
  'run',
288
- () => runHarmonyUnlockedAsync(projectRoot, options)
215
+ () => runHarmonyUnlockedAsync(root, options)
289
216
  );
290
217
  }
291
218
 
292
219
  async function runHarmonyAsync(
293
- projectRoot: string,
220
+ root: string,
294
221
  options: HarmonyRunOptions = {}
295
222
  ): Promise<HarmonyRunResult> {
296
- const session = await runHarmonySessionAsync(projectRoot, options);
223
+ const session = await runHarmonySessionAsync(root, options);
297
224
 
298
225
  try {
299
226
  return session.result;
@@ -0,0 +1,31 @@
1
+ import { parseCommandArgs, type Command } from '../command';
2
+ import { parseStartArgs } from './options';
3
+
4
+ export const command: Command = async (argv, io) => {
5
+ const args = parseCommandArgs(parseStartArgs, argv, io);
6
+ if (!args) return 0;
7
+
8
+ const { projectRoot: root, options } = args;
9
+ const { startExpoMetroAsync } = await import('../run/metro.js');
10
+
11
+ const metro = await startExpoMetroAsync(root, {
12
+ ...options,
13
+ interactive: true,
14
+ });
15
+
16
+ try {
17
+ if (metro.owner === 'existing') {
18
+ io.log(`Expo Metro is already running on port ${metro.port}.`);
19
+ if (options.resetCache) {
20
+ io.warn('Stop the existing Metro server and run this command again to reset its cache.');
21
+ }
22
+ } else {
23
+ io.log('\n› Logs for your project will appear below. Press Ctrl+C to exit.');
24
+ await metro.waitAsync();
25
+ }
26
+
27
+ return 0;
28
+ } finally {
29
+ await metro.stop();
30
+ }
31
+ };
@@ -1,24 +0,0 @@
1
- interface HarmonyBuildPipelineOptions {
2
- io?: Pick<Console, 'error' | 'log' | 'warn'>;
3
- requireDeviceTools?: boolean;
4
- /** @internal Reuse the native project without an isolated drift 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 };
@@ -1,92 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
1
- import type { HarmonyBuildPlan, HarmonyToolchain } from '../tools';
2
- interface InstallOptions {
3
- timeoutMs?: number;
4
- }
5
- /** Runs OHPM against the generated project without rewriting its manifest. */
6
- declare function installHarmonyDependenciesAsync(plan: HarmonyBuildPlan, toolchain: HarmonyToolchain, options?: InstallOptions): Promise<void>;
7
- export { installHarmonyDependenciesAsync };
@@ -1,26 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installHarmonyDependenciesAsync = installHarmonyDependenciesAsync;
4
- const errors_1 = require("../errors");
5
- const process_1 = require("../process");
6
- function installMessage(result) {
7
- const diagnostics = (0, process_1.formatDiagnostics)(result);
8
- return `OHPM install exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.`
9
- + `${diagnostics ? `\n${diagnostics}` : ''}`;
10
- }
11
- /** Runs OHPM against the generated project without rewriting its manifest. */
12
- async function installHarmonyDependenciesAsync(plan, toolchain, options = {}) {
13
- const result = await (0, process_1.spawnAsync)(toolchain.ohpm.command, [...toolchain.ohpm.args, 'install', '--all'], {
14
- capture: true,
15
- cwd: plan.harmonyRoot,
16
- operation: 'ohpm-install',
17
- outputLimit: 2 * 1024 * 1024,
18
- timeoutMs: options.timeoutMs || 5 * 60_000,
19
- });
20
- if (result.code !== 0 || result.timedOut) {
21
- throw new errors_1.HarmonyCliError('ERR_HARMONY_OHPM_FAILED', installMessage(result), {
22
- exitCode: result.code || 1,
23
- operation: 'ohpm-install',
24
- });
25
- }
26
- }
package/build/tools.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import { type HarmonyBuildDescriptor } from '@expo-harmony/prebuild-config/build-descriptor';
2
- export interface HarmonyTool {
3
- args: string[];
4
- command: string;
5
- source: 'deveco' | 'override' | 'path';
6
- }
7
- export interface HarmonyToolchain {
8
- hdc: HarmonyTool;
9
- hvigor: HarmonyTool;
10
- ohpm: HarmonyTool;
11
- sdkHome: string | null;
12
- toolsRoot: string | null;
13
- }
14
- export interface HarmonyBuildPlan {
15
- abilityName: string;
16
- buildMode: 'debug' | 'release';
17
- bundleName: string;
18
- expectedHap: string;
19
- exportPaths: HarmonyBuildDescriptor['export'];
20
- harmonyRoot: string;
21
- hvigorArgs: string[];
22
- moduleName: string;
23
- moduleRoot: string;
24
- nativeCache: HarmonyBuildDescriptor['nativeCache'];
25
- nativeInputs: HarmonyBuildDescriptor['nativeInputs'];
26
- projectFiles: HarmonyBuildDescriptor['projectFiles'];
27
- productName: string;
28
- targetName: string;
29
- }
30
- declare function resolveHarmonyToolchain(): HarmonyToolchain;
31
- declare function createHarmonyToolchainEnv(toolchain?: HarmonyToolchain): NodeJS.ProcessEnv;
32
- declare function resolveHarmonyEmulator(toolchain: HarmonyToolchain): HarmonyTool;
33
- declare function resolveHarmonyBuildPlanIfPresentAsync(projectRoot: string, options?: {
34
- buildMode?: 'debug' | 'release';
35
- }): Promise<HarmonyBuildPlan | null>;
36
- declare function resolveHarmonyBuildPlanAsync(projectRoot: string, options?: {
37
- buildMode?: 'debug' | 'release';
38
- }): Promise<HarmonyBuildPlan>;
39
- export { createHarmonyToolchainEnv, resolveHarmonyBuildPlanAsync, resolveHarmonyBuildPlanIfPresentAsync, resolveHarmonyEmulator, resolveHarmonyToolchain, };
@@ -1,145 +0,0 @@
1
- import fs from 'node:fs';
2
-
3
- import { doctorAsync, type DoctorResult } from '../doctor/doctor';
4
- import { HarmonyCliError } from '../errors';
5
- import { checkAsync } from '../prebuild/check';
6
- import { prebuildParsedAsync } from '../prebuild/prebuild';
7
- import { formatDiagnostics, spawnAsync } from '../process';
8
- import { resolveHarmonyBuildPlanIfPresentAsync } from '../tools';
9
-
10
- interface HarmonyBuildPipelineOptions {
11
- io?: Pick<Console, 'error' | 'log' | 'warn'>;
12
- requireDeviceTools?: boolean;
13
- /** @internal Reuse the native project without an isolated drift check. */
14
- skipGeneratedProjectCheck?: boolean;
15
- sync: boolean;
16
- variant: 'debug' | 'release';
17
- }
18
-
19
- interface CheckedProcessOptions {
20
- code: string;
21
- cwd: string;
22
- env?: NodeJS.ProcessEnv;
23
- message: string;
24
- operation: string;
25
- outputLimit?: number;
26
- timeoutMs?: number;
27
- }
28
-
29
- function progress(options: HarmonyBuildPipelineOptions, message: string): void {
30
- options.io?.log?.(`› ${message}`);
31
- }
32
-
33
- async function timed<T>(
34
- steps: Record<string, number>,
35
- name: string,
36
- operation: () => Promise<T> | T
37
- ): Promise<T> {
38
- const started = performance.now();
39
- try {
40
- return await operation();
41
- } finally {
42
- steps[name] = Math.max(0, Math.round(performance.now() - started));
43
- }
44
- }
45
-
46
- function assertDoctor(result: DoctorResult): void {
47
- if (result.ok) return;
48
-
49
- const failing = result.checks.filter(check => check.status === 'error').map(check => check.id);
50
-
51
- throw new HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', `Harmony doctor found blocking checks: ${failing.join(', ') || 'unknown'}.`, { operation: 'doctor' });
52
- }
53
-
54
- function isNonEmptyRegularFile(file: string): boolean {
55
- try {
56
- const stat = fs.lstatSync(file);
57
- return !stat.isSymbolicLink() && stat.isFile() && stat.size > 0;
58
- } catch {
59
- return false;
60
- }
61
- }
62
-
63
- async function runCheckedAsync(
64
- command: string,
65
- args: string[],
66
- options: CheckedProcessOptions
67
- ) {
68
- const result = await spawnAsync(command, args, {
69
- capture: true,
70
- cwd: options.cwd,
71
- env: options.env,
72
- operation: options.operation,
73
- outputLimit: options.outputLimit || 2 * 1024 * 1024,
74
- timeoutMs: options.timeoutMs,
75
- });
76
-
77
- if (result.code !== 0 || result.timedOut) {
78
- const diagnostics = formatDiagnostics(result);
79
- throw new HarmonyCliError(
80
- options.code,
81
- `${options.message} exited with code ${result.code}${result.timedOut ? ' after timing out' : ''}.${diagnostics ? `\n${diagnostics}` : ''}`,
82
- { exitCode: result.code || 1, operation: options.operation }
83
- );
84
- }
85
-
86
- return result;
87
- }
88
-
89
- async function ensureGeneratedProjectAsync(
90
- projectRoot: string,
91
- options: HarmonyBuildPipelineOptions,
92
- steps: Record<string, number>
93
- ) {
94
- const plan = await resolveHarmonyBuildPlanIfPresentAsync(projectRoot, {
95
- buildMode: options.variant,
96
- });
97
- const exists = Boolean(plan && fs.existsSync(plan.harmonyRoot));
98
-
99
- progress(options, 'Checking the Harmony project');
100
- const doctor = await timed(steps, 'doctor', () => doctorAsync(projectRoot, {
101
- requireBuildTools: true,
102
- requireDeviceTools: options.requireDeviceTools,
103
- validateGeneratedProject: exists,
104
- validateModules: false,
105
- }));
106
- assertDoctor(doctor);
107
-
108
- if (!exists || options.sync) {
109
- progress(options, exists
110
- ? 'Synchronizing the generated Harmony project'
111
- : 'Generating the missing Harmony project');
112
- await timed(steps, 'prebuild', () => prebuildParsedAsync(
113
- projectRoot,
114
- [],
115
- { buildType: options.variant }
116
- ));
117
- steps.cngCheck = 0;
118
- return;
119
- }
120
-
121
- steps.prebuild = 0;
122
- if (options.skipGeneratedProjectCheck) {
123
- steps.cngCheck = 0;
124
- return;
125
- }
126
- progress(options, 'Checking CNG ownership and drift');
127
- const check = await timed(steps, 'cngCheck', () => checkAsync(projectRoot, { buildType: options.variant }));
128
- if (!check.clean) {
129
- const summary = check.changes.slice(0, 8).map(change => `${change.type}:${change.path}`).join(', ');
130
- throw new HarmonyCliError(
131
- 'ERR_HARMONY_MANIFEST_DRIFT',
132
- `Generated Harmony files differ from CNG desired state${summary ? ` (${summary})` : ''}. Run expo-harmony prebuild or retry with --sync.`,
133
- { operation: 'check' }
134
- );
135
- }
136
- }
137
-
138
- export {
139
- ensureGeneratedProjectAsync,
140
- isNonEmptyRegularFile,
141
- progress,
142
- runCheckedAsync,
143
- timed,
144
- };
145
- export type { CheckedProcessOptions, HarmonyBuildPipelineOptions };