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

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 (185) hide show
  1. package/README.md +87 -38
  2. package/build/args.d.ts +19 -0
  3. package/build/args.js +27 -0
  4. package/build/bin/expo-harmony.d.ts +2 -0
  5. package/build/bin/expo-harmony.js +1 -1
  6. package/build/buildHap/build.d.ts +18 -0
  7. package/build/buildHap/build.js +38 -0
  8. package/build/buildHap/index.d.ts +2 -0
  9. package/build/buildHap/index.js +19 -0
  10. package/build/buildHap/options.d.ts +7 -0
  11. package/build/buildHap/options.js +30 -0
  12. package/build/cli.d.ts +3 -0
  13. package/build/cli.js +40 -0
  14. package/build/command.d.ts +9 -0
  15. package/build/command.js +20 -0
  16. package/build/development/manifest.d.ts +1 -0
  17. package/build/development/manifest.js +100 -0
  18. package/build/development/protocol.d.ts +2 -0
  19. package/build/development/protocol.js +26 -0
  20. package/build/development/session.d.ts +5 -0
  21. package/build/development/session.js +66 -0
  22. package/build/doctor/doctor.d.ts +20 -0
  23. package/build/doctor/doctor.js +226 -0
  24. package/build/doctor/index.d.ts +2 -0
  25. package/build/doctor/index.js +16 -0
  26. package/build/doctor/options.d.ts +5 -0
  27. package/build/doctor/options.js +15 -0
  28. package/build/entry.d.ts +2 -0
  29. package/build/entry.js +47 -0
  30. package/build/errors.d.ts +12 -0
  31. package/build/errors.js +16 -0
  32. package/build/expo.d.ts +19 -0
  33. package/build/expo.js +53 -0
  34. package/build/exportEmbed/export.d.ts +18 -0
  35. package/build/exportEmbed/export.js +124 -0
  36. package/build/exportEmbed/index.d.ts +2 -0
  37. package/build/exportEmbed/index.js +19 -0
  38. package/build/exportEmbed/manifest.d.ts +44 -0
  39. package/build/exportEmbed/manifest.js +171 -0
  40. package/build/exportEmbed/options.d.ts +7 -0
  41. package/build/exportEmbed/options.js +22 -0
  42. package/build/file.d.ts +10 -0
  43. package/build/file.js +110 -0
  44. package/build/help.d.ts +1 -0
  45. package/build/help.js +44 -0
  46. package/build/index.d.ts +14 -0
  47. package/build/index.js +6 -5
  48. package/build/internal/development.d.ts +1 -0
  49. package/build/internal/development.js +8 -0
  50. package/build/internal/export.d.ts +1 -0
  51. package/build/internal/export.js +80 -0
  52. package/build/internal/prebuild.d.ts +3 -0
  53. package/build/internal/prebuild.js +9 -0
  54. package/build/log.d.ts +4 -0
  55. package/build/log.js +6 -0
  56. package/build/modules/index.d.ts +2 -0
  57. package/build/modules/index.js +16 -0
  58. package/build/modules/modules.d.ts +42 -0
  59. package/build/modules/modules.js +84 -0
  60. package/build/modules/options.d.ts +10 -0
  61. package/build/modules/options.js +45 -0
  62. package/build/native/bare.d.ts +4 -0
  63. package/build/native/bare.js +130 -0
  64. package/build/native/build.d.ts +15 -0
  65. package/build/native/build.js +69 -0
  66. package/build/native/cache.d.ts +11 -0
  67. package/build/native/cache.js +73 -0
  68. package/build/native/hvigor.d.ts +3 -0
  69. package/build/native/hvigor.js +83 -0
  70. package/build/native/install.d.ts +5 -0
  71. package/build/native/install.js +13 -0
  72. package/build/native/prepareProject.d.ts +11 -0
  73. package/build/native/prepareProject.js +68 -0
  74. package/build/native/project.d.ts +8 -0
  75. package/build/native/project.js +58 -0
  76. package/build/native/toolchain.d.ts +16 -0
  77. package/build/native/toolchain.js +172 -0
  78. package/build/native/types.d.ts +47 -0
  79. package/build/native/types.js +2 -0
  80. package/build/path.d.ts +4 -0
  81. package/build/path.js +27 -0
  82. package/build/prebuild/check.d.ts +10 -0
  83. package/build/prebuild/check.js +151 -0
  84. package/build/prebuild/clean.d.ts +2 -0
  85. package/build/prebuild/clean.js +50 -0
  86. package/build/prebuild/index.d.ts +2 -0
  87. package/build/prebuild/index.js +51 -0
  88. package/build/prebuild/options.d.ts +10 -0
  89. package/build/prebuild/options.js +50 -0
  90. package/build/prebuild/prebuild.d.ts +6 -0
  91. package/build/prebuild/prebuild.js +70 -0
  92. package/build/prebuild/template.d.ts +8 -0
  93. package/build/prebuild/template.js +86 -0
  94. package/build/process.d.ts +41 -0
  95. package/build/process.js +324 -0
  96. package/build/profile.d.ts +1 -0
  97. package/build/profile.js +12 -0
  98. package/build/project.d.ts +2 -0
  99. package/build/project.js +23 -0
  100. package/build/projectLock.d.ts +2 -0
  101. package/build/projectLock.js +185 -0
  102. package/build/run/devices.d.ts +29 -0
  103. package/build/run/devices.js +216 -0
  104. package/build/run/emulators.d.ts +13 -0
  105. package/build/run/emulators.js +82 -0
  106. package/build/run/index.d.ts +2 -0
  107. package/build/run/index.js +25 -0
  108. package/build/run/metro.d.ts +21 -0
  109. package/build/run/metro.js +162 -0
  110. package/build/run/options.d.ts +14 -0
  111. package/build/run/options.js +62 -0
  112. package/build/run/run.d.ts +46 -0
  113. package/build/run/run.js +135 -0
  114. package/build/runtime/config.d.ts +35 -0
  115. package/build/runtime/config.js +118 -0
  116. package/build/runtime/contract.d.ts +5 -0
  117. package/build/runtime/contract.js +108 -0
  118. package/build/runtime/fingerprint.d.ts +1 -0
  119. package/build/runtime/fingerprint.js +44 -0
  120. package/build/runtime/index.d.ts +2 -0
  121. package/build/runtime/index.js +50 -0
  122. package/build/start/index.d.ts +2 -0
  123. package/build/start/index.js +38 -0
  124. package/build/start/options.d.ts +9 -0
  125. package/build/start/options.js +38 -0
  126. package/build/updates/export.d.ts +14 -0
  127. package/build/updates/export.js +46 -0
  128. package/build/updates/index.d.ts +2 -0
  129. package/build/updates/index.js +96 -0
  130. package/build/upstream.d.ts +9 -0
  131. package/build/upstream.js +17 -0
  132. package/package.json +21 -5
  133. package/src/bin/expo-harmony.ts +2 -2
  134. package/src/buildHap/build.ts +13 -105
  135. package/src/buildHap/index.ts +19 -0
  136. package/src/cli.ts +27 -225
  137. package/src/command.ts +24 -0
  138. package/src/development/manifest.ts +106 -0
  139. package/src/development/protocol.ts +37 -0
  140. package/src/development/session.ts +83 -0
  141. package/src/doctor/doctor.ts +67 -62
  142. package/src/doctor/index.ts +16 -0
  143. package/src/exportEmbed/export.ts +73 -72
  144. package/src/exportEmbed/index.ts +17 -0
  145. package/src/file.ts +20 -11
  146. package/src/help.ts +41 -0
  147. package/src/index.ts +5 -3
  148. package/src/internal/development.ts +8 -0
  149. package/src/internal/export.ts +82 -0
  150. package/src/internal/prebuild.ts +3 -0
  151. package/src/log.ts +5 -0
  152. package/src/modules/index.ts +16 -0
  153. package/src/native/bare.ts +145 -0
  154. package/src/native/build.ts +83 -0
  155. package/src/{run → native}/cache.ts +10 -9
  156. package/src/native/hvigor.ts +88 -0
  157. package/src/native/install.ts +17 -0
  158. package/src/native/prepareProject.ts +99 -0
  159. package/src/native/project.ts +88 -0
  160. package/src/{tools.ts → native/toolchain.ts} +38 -140
  161. package/src/native/types.ts +44 -0
  162. package/src/prebuild/check.ts +56 -31
  163. package/src/prebuild/clean.ts +3 -3
  164. package/src/prebuild/index.ts +56 -0
  165. package/src/prebuild/prebuild.ts +11 -1
  166. package/src/prebuild/template.ts +2 -2
  167. package/src/process.ts +175 -64
  168. package/src/profile.ts +13 -0
  169. package/src/projectLock.ts +9 -1
  170. package/src/run/devices.ts +16 -11
  171. package/src/run/emulators.ts +4 -6
  172. package/src/run/index.ts +25 -0
  173. package/src/run/metro.ts +10 -1
  174. package/src/run/options.ts +2 -0
  175. package/src/run/run.ts +63 -129
  176. package/src/runtime/config.ts +150 -0
  177. package/src/runtime/contract.ts +165 -0
  178. package/src/runtime/fingerprint.ts +39 -0
  179. package/src/runtime/index.ts +54 -0
  180. package/src/start/index.ts +37 -0
  181. package/src/start/options.ts +12 -0
  182. package/src/updates/export.ts +60 -0
  183. package/src/updates/index.ts +89 -0
  184. package/src/buildHap/common.ts +0 -145
  185. package/src/run/install.ts +0 -41
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const errors_1 = require("../errors");
6
+ const projectLock_1 = require("../projectLock");
7
+ const options_1 = require("./options");
8
+ const command = async (argv, io) => {
9
+ const args = (0, command_1.parseCommandArgs)(args => (0, options_1.parsePrebuildArgs)(args, { allowProject: true }), argv, io);
10
+ if (!args)
11
+ return 0;
12
+ const { projectRoot: root, options } = args;
13
+ const { check, clean, passthrough } = options;
14
+ if (check && passthrough.length) {
15
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--check cannot be combined with mutating prebuild options.');
16
+ }
17
+ return (0, projectLock_1.withHarmonyProjectLockAsync)(root, check ? 'prebuild-check' : 'prebuild', async () => {
18
+ if (clean) {
19
+ const { assertSafeCleanTarget } = await import('./clean.js');
20
+ await assertSafeCleanTarget(root);
21
+ }
22
+ const { doctorAsync, formatDoctor } = await import('../doctor/doctor.js');
23
+ const doctor = await doctorAsync(root, {
24
+ requireBuildTools: false,
25
+ validateGeneratedProject: !clean,
26
+ validateModules: false,
27
+ });
28
+ if (!doctor.ok) {
29
+ io.error(formatDoctor(doctor));
30
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_DOCTOR_FAILED', 'Harmony doctor found blocking errors.', {
31
+ operation: 'doctor',
32
+ });
33
+ }
34
+ for (const item of doctor.checks.filter(item => item.status === 'warn'))
35
+ io.warn(`! ${item.message}`);
36
+ if (check) {
37
+ const { checkAsync } = await import('./check.js');
38
+ const result = await checkAsync(root);
39
+ if (result.clean)
40
+ io.log('Harmony CNG output is up to date.');
41
+ else
42
+ for (const change of result.changes)
43
+ io.log(`${change.type}: ${change.path}`);
44
+ return result.clean ? 0 : 2;
45
+ }
46
+ const { prebuildParsedAsync } = await import('./prebuild.js');
47
+ await prebuildParsedAsync(root, passthrough);
48
+ return 0;
49
+ });
50
+ };
51
+ exports.command = command;
@@ -0,0 +1,10 @@
1
+ declare function parsePrebuildArgs(argv: string[], options?: {
2
+ allowProject?: boolean;
3
+ }): {
4
+ check: boolean;
5
+ clean: boolean;
6
+ help: boolean;
7
+ passthrough: string[];
8
+ project: string;
9
+ };
10
+ export { parsePrebuildArgs };
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePrebuildArgs = parsePrebuildArgs;
4
+ const errors_1 = require("../errors");
5
+ const args_1 = require("../args");
6
+ const PrebuildOptions = {
7
+ ...args_1.CommonOptions,
8
+ 'bun': { type: 'boolean' },
9
+ 'check': { type: 'boolean' },
10
+ 'clean': { type: 'boolean' },
11
+ 'no-install': { type: 'boolean' },
12
+ 'npm': { type: 'boolean' },
13
+ 'platform': { short: 'p', type: 'string' },
14
+ 'pnpm': { type: 'boolean' },
15
+ 'skip-dependency-update': { type: 'string' },
16
+ 'template': { short: 't', type: 'string' },
17
+ 'yarn': { type: 'boolean' },
18
+ };
19
+ function parsePrebuildArgs(argv, options = {}) {
20
+ const { positionals, values } = (0, args_1.parseArgs)(PrebuildOptions, argv);
21
+ if (values.platform !== undefined) {
22
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--platform is fixed to harmony by expo-harmony.', { operation: 'parse-arguments' });
23
+ }
24
+ if (values.template !== undefined) {
25
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', '--template is managed by expo-harmony.', { operation: 'parse-arguments' });
26
+ }
27
+ if (positionals.length > (options.allowProject ? 1 : 0)) {
28
+ const unexpected = positionals[options.allowProject ? 1 : 0];
29
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unexpected prebuild positional argument: ${unexpected}`, { operation: 'parse-arguments' });
30
+ }
31
+ const packageManagerFlags = ['npm', 'yarn', 'pnpm', 'bun'].filter(name => values[name]);
32
+ if (packageManagerFlags.length > 1) {
33
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', 'Choose at most one package manager: --npm, --yarn, --pnpm, or --bun.', { operation: 'parse-arguments' });
34
+ }
35
+ const passthrough = [];
36
+ for (const name of ['clean', 'npm', 'yarn', 'pnpm', 'bun', 'no-install']) {
37
+ if (values[name])
38
+ passthrough.push(`--${name}`);
39
+ }
40
+ if (values['skip-dependency-update'] !== undefined) {
41
+ passthrough.push('--skip-dependency-update', values['skip-dependency-update']);
42
+ }
43
+ return {
44
+ check: Boolean(values.check),
45
+ clean: Boolean(values.clean),
46
+ help: Boolean(values.help),
47
+ passthrough,
48
+ project: positionals[0],
49
+ };
50
+ }
@@ -0,0 +1,6 @@
1
+ interface PrebuildOptions {
2
+ buildType?: 'debug' | 'release';
3
+ capture?: boolean;
4
+ }
5
+ declare function prebuildParsedAsync(projectRoot: string, passthrough: string[], options?: PrebuildOptions): Promise<import("../process").ProcessResult>;
6
+ export { prebuildParsedAsync };
@@ -0,0 +1,70 @@
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.prebuildParsedAsync = prebuildParsedAsync;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const errors_1 = require("../errors");
9
+ const bare_1 = require("../native/bare");
10
+ const process_1 = require("../process");
11
+ const projectLock_1 = require("../projectLock");
12
+ const expo_1 = require("../expo");
13
+ const toolchain_1 = require("../native/toolchain");
14
+ const project_1 = require("../native/project");
15
+ const clean_1 = require("./clean");
16
+ const template_1 = require("./template");
17
+ async function prebuildParsedUnlockedAsync(projectRoot, passthrough, options = {}) {
18
+ if ((0, bare_1.isBareHarmonyProject)(projectRoot)) {
19
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_BARE_PREBUILD', 'This Harmony project is manually maintained. Build it directly; prebuild cannot overwrite a bare project.', { operation: 'prebuild' });
20
+ }
21
+ if (passthrough.includes('--clean'))
22
+ await (0, clean_1.assertSafeCleanTarget)(projectRoot);
23
+ const expo = (0, expo_1.resolveExpoCli)(projectRoot);
24
+ const packed = await (0, template_1.packAsync)(projectRoot);
25
+ try {
26
+ const result = await (0, process_1.spawnAsync)(process.execPath, [
27
+ expo.cliPath,
28
+ 'prebuild', projectRoot,
29
+ '--platform', 'harmony',
30
+ '--template', packed.tarball,
31
+ ...passthrough,
32
+ ], {
33
+ capture: Boolean(options.capture),
34
+ cwd: projectRoot,
35
+ env: {
36
+ ...(0, toolchain_1.createHarmonyToolchainEnv)(),
37
+ ...packed.env,
38
+ ...(options.buildType ? { EXPO_HARMONY_BUILD_TYPE: options.buildType } : {}),
39
+ },
40
+ operation: 'expo-prebuild',
41
+ outputLimit: 4 * 1024 * 1024,
42
+ });
43
+ if (result.code !== 0) {
44
+ const diagnostics = options.capture ? (0, process_1.formatDiagnostics)(result) : '';
45
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_PREBUILD_FAILED', `Expo prebuild exited with code ${result.code}.${diagnostics ? `\n${diagnostics}` : ''}`, {
46
+ exitCode: result.code,
47
+ operation: 'expo-prebuild',
48
+ });
49
+ }
50
+ try {
51
+ const plan = await (0, project_1.resolveHarmonyBuildPlanAsync)(projectRoot, {
52
+ buildMode: options.buildType,
53
+ });
54
+ if (plan.workflow !== 'cng') {
55
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'Prebuild did not produce a CNG project.', { operation: 'prebuild' });
56
+ }
57
+ await node_fs_1.default.promises.access(plan.projectFiles.templateMarker, node_fs_1.default.constants.R_OK);
58
+ }
59
+ catch (cause) {
60
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'Expo prebuild exited successfully but the Harmony marker or CNG manifest is invalid.', { cause, operation: 'verify-prebuild' });
61
+ }
62
+ return result;
63
+ }
64
+ finally {
65
+ await packed.cleanup();
66
+ }
67
+ }
68
+ async function prebuildParsedAsync(projectRoot, passthrough, options = {}) {
69
+ return (0, projectLock_1.withHarmonyProjectLockAsync)(projectRoot, 'prebuild', () => prebuildParsedUnlockedAsync(projectRoot, passthrough, options));
70
+ }
@@ -0,0 +1,8 @@
1
+ declare function packAsync(project: string): Promise<{
2
+ tarball: string;
3
+ env: {
4
+ [x: string]: string;
5
+ };
6
+ cleanup(): Promise<void>;
7
+ }>;
8
+ export { packAsync, };
@@ -0,0 +1,86 @@
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.packAsync = packAsync;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_module_1 = require("node:module");
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const errors_1 = require("../errors");
12
+ const process_1 = require("../process");
13
+ function resolve(project) {
14
+ const load = (0, node_module_1.createRequire)(node_path_1.default.join(project, 'package.json'));
15
+ let api;
16
+ try {
17
+ api = load('@expo-harmony/prebuild-config/internal/template');
18
+ }
19
+ catch (cause) {
20
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'Cannot load @expo-harmony/prebuild-config/internal/template from the project. Update the project-local prebuild config to a compatible version.', { cause, operation: 'resolve-template' });
21
+ }
22
+ if (typeof api?.ROOT_ENV !== 'string' || typeof api.resolveBundled !== 'function') {
23
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'The project-local @expo-harmony/prebuild-config template API is invalid.', { operation: 'resolve-template' });
24
+ }
25
+ let template;
26
+ try {
27
+ template = api.resolveBundled();
28
+ }
29
+ catch (cause) {
30
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'Cannot resolve the Harmony template declared by the project-local prebuild config.', { cause, operation: 'resolve-template' });
31
+ }
32
+ if (!template
33
+ || typeof template.root !== 'string'
34
+ || !node_path_1.default.isAbsolute(template.root)
35
+ || typeof template.version !== 'string'
36
+ || !template.version) {
37
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'The project-local @expo-harmony/prebuild-config returned an invalid template descriptor.', { operation: 'resolve-template' });
38
+ }
39
+ return {
40
+ env: { [api.ROOT_ENV]: template.root },
41
+ root: template.root,
42
+ };
43
+ }
44
+ async function packAsync(project) {
45
+ const template = resolve(project);
46
+ const temp = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'expo-harmony-template-'));
47
+ const result = await (0, process_1.spawnAsync)('npm', [
48
+ 'pack',
49
+ '--json',
50
+ '--ignore-scripts',
51
+ '--cache', node_path_1.default.join(temp, '.npm-cache'),
52
+ '--pack-destination', temp,
53
+ template.root,
54
+ ], { capture: true, cwd: project, operation: 'pack-template' });
55
+ if (result.code !== 0) {
56
+ await node_fs_1.default.promises.rm(temp, { recursive: true, force: true });
57
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', `npm pack failed: ${result.stderr.trim()}`, {
58
+ exitCode: result.code,
59
+ operation: 'pack-template',
60
+ });
61
+ }
62
+ let packs;
63
+ try {
64
+ packs = JSON.parse(result.stdout);
65
+ }
66
+ catch (cause) {
67
+ await node_fs_1.default.promises.rm(temp, { recursive: true, force: true });
68
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'npm pack returned invalid JSON.', {
69
+ cause,
70
+ operation: 'pack-template',
71
+ });
72
+ }
73
+ const filename = packs?.[0]?.filename;
74
+ const tarball = filename && node_path_1.default.join(temp, filename);
75
+ if (!tarball || !node_fs_1.default.existsSync(tarball)) {
76
+ await node_fs_1.default.promises.rm(temp, { recursive: true, force: true });
77
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_TEMPLATE_INVALID', 'npm pack did not create a template tarball.', { operation: 'pack-template' });
78
+ }
79
+ return {
80
+ tarball,
81
+ env: template.env,
82
+ async cleanup() {
83
+ await node_fs_1.default.promises.rm(temp, { recursive: true, force: true });
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,41 @@
1
+ export interface ProcessOptions {
2
+ capture?: boolean;
3
+ cwd?: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ onStderr?: (chunk: Uint8Array) => void;
6
+ onStdout?: (chunk: Uint8Array) => void;
7
+ operation?: string;
8
+ outputLimit?: number;
9
+ signal?: AbortSignal;
10
+ stdio?: 'inherit' | 'pipe';
11
+ stopGraceMs?: number;
12
+ timeoutMs?: number;
13
+ }
14
+ export interface ProcessResult {
15
+ code: number;
16
+ signal: NodeJS.Signals | null;
17
+ stderr: string;
18
+ stdout: string;
19
+ timedOut: boolean;
20
+ }
21
+ interface CheckedProcessOptions {
22
+ code: string;
23
+ cwd: string;
24
+ env?: NodeJS.ProcessEnv;
25
+ message: string;
26
+ operation: string;
27
+ outputLimit?: number;
28
+ timeoutMs?: number;
29
+ }
30
+ declare function formatDiagnostics(result: Pick<ProcessResult, 'stderr' | 'stdout'>, limit?: number): string;
31
+ declare function spawnAsync(command: string, args: string[], options?: ProcessOptions): Promise<ProcessResult>;
32
+ declare function startManagedProcess(command: string, args: string[], options?: ProcessOptions): {
33
+ child: any;
34
+ completion: Promise<ProcessResult>;
35
+ getStderr: () => string;
36
+ getStdout: () => string;
37
+ stop: (signal?: NodeJS.Signals, graceMs?: number) => Promise<ProcessResult>;
38
+ wasStopped: () => boolean;
39
+ };
40
+ declare function runCheckedAsync(command: string, args: string[], options: CheckedProcessOptions): Promise<ProcessResult>;
41
+ export { formatDiagnostics, runCheckedAsync, spawnAsync, startManagedProcess };
@@ -0,0 +1,324 @@
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.formatDiagnostics = formatDiagnostics;
7
+ exports.runCheckedAsync = runCheckedAsync;
8
+ exports.spawnAsync = spawnAsync;
9
+ exports.startManagedProcess = startManagedProcess;
10
+ const cross_spawn_1 = __importDefault(require("cross-spawn"));
11
+ const tool_command_1 = require("@expo-harmony/expo-modules-autolinking/tool-command");
12
+ const errors_1 = require("./errors");
13
+ const DefaultOutputLimit = 1024 * 1024;
14
+ const DefaultStopGraceMs = 3_000;
15
+ class BoundedCapture {
16
+ limit;
17
+ buffers = [];
18
+ head = 0;
19
+ length = 0;
20
+ constructor(limit) {
21
+ this.limit = limit;
22
+ }
23
+ append(value) {
24
+ const buffer = typeof value === 'string' ? new TextEncoder().encode(value) : Uint8Array.from(value);
25
+ if (buffer.length >= this.limit) {
26
+ this.buffers = [buffer.subarray(buffer.length - this.limit)];
27
+ this.head = 0;
28
+ this.length = this.limit;
29
+ return;
30
+ }
31
+ while (this.length + buffer.length > this.limit && this.head < this.buffers.length) {
32
+ const first = this.buffers[this.head];
33
+ const excess = this.length + buffer.length - this.limit;
34
+ if (first.length > excess) {
35
+ this.buffers[this.head] = first.subarray(excess);
36
+ this.length -= excess;
37
+ break;
38
+ }
39
+ this.length -= first.length;
40
+ this.head += 1;
41
+ }
42
+ this.buffers.push(buffer);
43
+ this.length += buffer.length;
44
+ if (this.head > 128 && this.head * 2 > this.buffers.length) {
45
+ this.buffers = this.buffers.slice(this.head);
46
+ this.head = 0;
47
+ }
48
+ }
49
+ toString() {
50
+ return Buffer.concat(this.buffers.slice(this.head), this.length).toString('utf8');
51
+ }
52
+ }
53
+ function formatDiagnostics(result, limit = 4_000) {
54
+ return (result.stderr || result.stdout || '').slice(-limit).trim();
55
+ }
56
+ function spawnAsync(command, args, options = {}) {
57
+ return new Promise((resolve, reject) => {
58
+ if (options.signal?.aborted) {
59
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot run ${command}: operation aborted.`, {
60
+ cause: options.signal.reason,
61
+ operation: options.operation || 'spawn',
62
+ });
63
+ }
64
+ const piped = Boolean(options.capture || options.onStdout || options.onStderr);
65
+ const limit = options.outputLimit || DefaultOutputLimit;
66
+ const child = (0, cross_spawn_1.default)(command, args, {
67
+ cwd: options.cwd,
68
+ env: options.env || process.env,
69
+ shell: false,
70
+ stdio: piped ? ['ignore', 'pipe', 'pipe'] : 'inherit',
71
+ windowsHide: true,
72
+ });
73
+ const stdout = new BoundedCapture(limit);
74
+ const stderr = new BoundedCapture(limit);
75
+ let timedOut = false;
76
+ let settled = false;
77
+ let escalation = null;
78
+ let stopping;
79
+ if (piped) {
80
+ child.stdout.on('data', (chunk) => {
81
+ stdout.append(chunk);
82
+ options.onStdout?.(chunk);
83
+ });
84
+ child.stderr.on('data', (chunk) => {
85
+ stderr.append(chunk);
86
+ options.onStderr?.(chunk);
87
+ });
88
+ }
89
+ const stop = (signal = 'SIGTERM') => {
90
+ if (stopping || settled)
91
+ return;
92
+ stopping = Promise.resolve().then(() => (0, tool_command_1.terminateProcess)(child, signal));
93
+ if (process.platform === 'win32') {
94
+ void finish(null, signal);
95
+ }
96
+ else {
97
+ stopping.catch((cause) => {
98
+ void finish(null, null, cause);
99
+ });
100
+ escalation = setTimeout(() => {
101
+ stopping = (0, tool_command_1.terminateProcess)(child, 'SIGKILL');
102
+ stopping.catch((cause) => {
103
+ void finish(null, null, cause);
104
+ });
105
+ }, options.stopGraceMs || DefaultStopGraceMs);
106
+ escalation.unref?.();
107
+ }
108
+ };
109
+ const interrupt = () => stop('SIGINT');
110
+ const terminate = () => stop('SIGTERM');
111
+ process.once('SIGINT', interrupt);
112
+ process.once('SIGTERM', terminate);
113
+ const timeout = options.timeoutMs
114
+ ? setTimeout(() => {
115
+ timedOut = true;
116
+ stop('SIGTERM');
117
+ }, options.timeoutMs)
118
+ : null;
119
+ timeout?.unref?.();
120
+ const cleanup = () => {
121
+ if (timeout)
122
+ clearTimeout(timeout);
123
+ if (escalation)
124
+ clearTimeout(escalation);
125
+ process.removeListener('SIGINT', interrupt);
126
+ process.removeListener('SIGTERM', terminate);
127
+ options.signal?.removeEventListener('abort', abort);
128
+ };
129
+ const finish = async (code, signal, cause) => {
130
+ try {
131
+ await stopping;
132
+ }
133
+ catch (error) {
134
+ cause = error;
135
+ }
136
+ if (settled)
137
+ return;
138
+ settled = true;
139
+ cleanup();
140
+ if (cause)
141
+ reject(new errors_1.HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot run ${command}: ${cause.message}`, {
142
+ cause,
143
+ operation: options.operation || 'spawn',
144
+ }));
145
+ else
146
+ resolve({
147
+ code: code === null ? 1 : code,
148
+ signal,
149
+ stderr: stderr.toString(),
150
+ stdout: stdout.toString(),
151
+ timedOut,
152
+ });
153
+ };
154
+ child.once('error', (cause) => {
155
+ void finish(null, null, cause);
156
+ });
157
+ child.once('close', (code, signal) => {
158
+ void finish(code, signal);
159
+ });
160
+ const abort = () => stop('SIGTERM');
161
+ options.signal?.addEventListener('abort', abort, { once: true });
162
+ if (options.signal?.aborted)
163
+ abort();
164
+ });
165
+ }
166
+ function startManagedProcess(command, args, options = {}) {
167
+ if (options.signal?.aborted) {
168
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot launch ${command}: operation aborted.`, {
169
+ cause: options.signal.reason,
170
+ operation: options.operation || 'spawn',
171
+ });
172
+ }
173
+ const limit = options.outputLimit || DefaultOutputLimit;
174
+ const piped = options.stdio !== 'inherit';
175
+ const child = (0, cross_spawn_1.default)(command, args, {
176
+ cwd: options.cwd,
177
+ env: options.env || process.env,
178
+ shell: false,
179
+ stdio: piped ? ['ignore', 'pipe', 'pipe'] : 'inherit',
180
+ windowsHide: true,
181
+ });
182
+ const stdout = new BoundedCapture(limit);
183
+ const stderr = new BoundedCapture(limit);
184
+ let failure = null;
185
+ let closed = false;
186
+ let stopped = false;
187
+ let stopping;
188
+ let stopPromise;
189
+ let finish;
190
+ if (piped) {
191
+ child.stdout.on('data', (chunk) => {
192
+ stdout.append(chunk);
193
+ options.onStdout?.(chunk);
194
+ });
195
+ child.stderr.on('data', (chunk) => {
196
+ stderr.append(chunk);
197
+ options.onStderr?.(chunk);
198
+ });
199
+ }
200
+ const interrupt = () => {
201
+ void stop('SIGINT').catch(() => { });
202
+ };
203
+ const terminate = () => {
204
+ void stop('SIGTERM').catch(() => { });
205
+ };
206
+ const abort = () => {
207
+ void stop('SIGTERM').catch(() => { });
208
+ };
209
+ const cleanup = () => {
210
+ process.removeListener('SIGINT', interrupt);
211
+ process.removeListener('SIGTERM', terminate);
212
+ options.signal?.removeEventListener('abort', abort);
213
+ };
214
+ const completion = new Promise((resolve, reject) => {
215
+ child.once('error', (cause) => {
216
+ failure = new errors_1.HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot launch ${command}: ${cause.message}`, {
217
+ cause,
218
+ operation: options.operation || 'spawn',
219
+ });
220
+ });
221
+ finish = async (code, signal) => {
222
+ try {
223
+ await stopping;
224
+ }
225
+ catch (cause) {
226
+ failure = new errors_1.HarmonyCliError('ERR_HARMONY_PROCESS_FAILED', `Cannot stop ${command}: ${cause.message}`, {
227
+ cause, operation: options.operation || 'spawn',
228
+ });
229
+ }
230
+ if (closed)
231
+ return;
232
+ closed = true;
233
+ cleanup();
234
+ if (failure)
235
+ reject(failure);
236
+ else
237
+ resolve({
238
+ code: code === null ? 1 : code,
239
+ signal,
240
+ stderr: stderr.toString(),
241
+ stdout: stdout.toString(),
242
+ timedOut: false,
243
+ });
244
+ };
245
+ child.once('close', (code, signal) => {
246
+ void finish(code, signal);
247
+ });
248
+ });
249
+ // A readiness probe may be the first consumer. Keep early spawn failures from
250
+ // becoming unhandled rejections while the probe is still polling.
251
+ completion.catch(() => { });
252
+ async function stop(signal = 'SIGTERM', graceMs = DefaultStopGraceMs) {
253
+ if (stopPromise)
254
+ return stopPromise;
255
+ if (closed)
256
+ return completion;
257
+ stopped = true;
258
+ stopping = Promise.resolve().then(() => (0, tool_command_1.terminateProcess)(child, signal));
259
+ stopPromise = (async () => {
260
+ if (process.platform === 'win32') {
261
+ void finish(null, signal);
262
+ return completion;
263
+ }
264
+ try {
265
+ await stopping;
266
+ }
267
+ catch {
268
+ await finish(null, signal);
269
+ return completion;
270
+ }
271
+ let timer;
272
+ await Promise.race([
273
+ completion.catch(() => undefined),
274
+ new Promise((resolve) => {
275
+ timer = setTimeout(resolve, graceMs);
276
+ timer.unref?.();
277
+ }),
278
+ ]);
279
+ if (timer)
280
+ clearTimeout(timer);
281
+ if (!closed) {
282
+ stopping = (0, tool_command_1.terminateProcess)(child, 'SIGKILL');
283
+ try {
284
+ await stopping;
285
+ }
286
+ catch {
287
+ await finish(null, signal);
288
+ }
289
+ }
290
+ return completion.catch(() => undefined);
291
+ })();
292
+ stopPromise.catch(() => { });
293
+ return stopPromise;
294
+ }
295
+ process.once('SIGINT', interrupt);
296
+ process.once('SIGTERM', terminate);
297
+ if (options.signal?.aborted)
298
+ abort();
299
+ else
300
+ options.signal?.addEventListener('abort', abort, { once: true });
301
+ return {
302
+ child,
303
+ completion,
304
+ getStderr: () => stderr.toString(),
305
+ getStdout: () => stdout.toString(),
306
+ stop,
307
+ wasStopped: () => stopped,
308
+ };
309
+ }
310
+ async function runCheckedAsync(command, args, options) {
311
+ const result = await spawnAsync(command, args, {
312
+ capture: true,
313
+ cwd: options.cwd,
314
+ env: options.env,
315
+ operation: options.operation,
316
+ outputLimit: options.outputLimit || 2 * 1024 * 1024,
317
+ timeoutMs: options.timeoutMs,
318
+ });
319
+ if (result.code !== 0 || result.timedOut) {
320
+ const diagnostics = formatDiagnostics(result);
321
+ 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 });
322
+ }
323
+ return result;
324
+ }
@@ -0,0 +1 @@
1
+ export declare function timedAsync<T>(steps: Record<string, number>, name: string, operation: () => Promise<T> | T): Promise<T>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.timedAsync = timedAsync;
4
+ async function timedAsync(steps, name, operation) {
5
+ const started = performance.now();
6
+ try {
7
+ return await operation();
8
+ }
9
+ finally {
10
+ steps[name] = Math.max(0, Math.round(performance.now() - started));
11
+ }
12
+ }
@@ -0,0 +1,2 @@
1
+ declare function resolveProject(start?: string): string;
2
+ export { resolveProject };