@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,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;
@@ -0,0 +1,44 @@
1
+ import type { ExportTemporary } from './export';
2
+ export interface HarmonyExportFile {
3
+ path: string;
4
+ sha256: string;
5
+ size: number;
6
+ }
7
+ export interface HarmonyExportManifest {
8
+ assets: HarmonyExportFile[];
9
+ bundle: HarmonyExportFile & {
10
+ bytecodeVersion: number;
11
+ };
12
+ entryFile: string;
13
+ platform: 'harmony';
14
+ schemaVersion: 1;
15
+ sourceMap: HarmonyExportFile;
16
+ transformProfile: 'hermes-stable';
17
+ }
18
+ declare const HermesMagic: Buffer;
19
+ declare function exportPaths(plan: {
20
+ exportPaths: {
21
+ bundle: string;
22
+ manifest: string;
23
+ metadataRoot: string;
24
+ rawfileRoot: string;
25
+ sourceMap: string;
26
+ };
27
+ }): {
28
+ bundle: string;
29
+ manifest: string;
30
+ metadataRoot: string;
31
+ rawfileRoot: string;
32
+ sourceMap: string;
33
+ };
34
+ declare function assertHermesBundle(file: any): Promise<{
35
+ bytecodeVersion: number;
36
+ size: any;
37
+ }>;
38
+ declare function assertSourceMap(file: any): Promise<{
39
+ sha256: string;
40
+ size: number;
41
+ }>;
42
+ declare function publishExportAsync(projectRoot: string, paths: ReturnType<typeof exportPaths>, temporary: ExportTemporary, entryFile: string, bytecode: Awaited<ReturnType<typeof assertHermesBundle>>): Promise<HarmonyExportManifest>;
43
+ declare function validatePublishedExportAsync(paths: ReturnType<typeof exportPaths>): Promise<HarmonyExportManifest>;
44
+ export { HermesMagic, assertHermesBundle, assertSourceMap, exportPaths, publishExportAsync, validatePublishedExportAsync, };
@@ -0,0 +1,171 @@
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.HermesMagic = void 0;
7
+ exports.assertHermesBundle = assertHermesBundle;
8
+ exports.assertSourceMap = assertSourceMap;
9
+ exports.exportPaths = exportPaths;
10
+ exports.publishExportAsync = publishExportAsync;
11
+ exports.validatePublishedExportAsync = validatePublishedExportAsync;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_os_1 = __importDefault(require("node:os"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const errors_1 = require("../errors");
16
+ const file_1 = require("../file");
17
+ const path_1 = require("../path");
18
+ const HermesMagic = Buffer.from('c61fbc03c103191f', 'hex');
19
+ exports.HermesMagic = HermesMagic;
20
+ const ManifestSchemaVersion = 1;
21
+ function exportPaths(plan) {
22
+ return plan.exportPaths;
23
+ }
24
+ async function assertHermesBundle(file) {
25
+ let handle;
26
+ try {
27
+ handle = await node_fs_1.default.promises.open(file, 'r');
28
+ const header = Buffer.alloc(12);
29
+ const { bytesRead } = await handle.read(header, 0, header.length, 0);
30
+ const stat = await handle.stat();
31
+ if (bytesRead < header.length || stat.size <= header.length
32
+ || header.subarray(0, HermesMagic.length).toString('hex') !== HermesMagic.toString('hex')) {
33
+ throw new Error('Expo export did not produce non-empty Hermes bytecode.');
34
+ }
35
+ return { bytecodeVersion: header.readUInt32LE(8), size: stat.size };
36
+ }
37
+ catch (cause) {
38
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_INVALID_BUNDLE', 'The Harmony export output is not a valid Hermes bytecode bundle.', { cause, operation: 'validate-export' });
39
+ }
40
+ finally {
41
+ await handle?.close();
42
+ }
43
+ }
44
+ async function assertSourceMap(file) {
45
+ try {
46
+ const contents = await node_fs_1.default.promises.readFile(file, 'utf8');
47
+ const sourceMap = JSON.parse(contents);
48
+ const paths = JSON.stringify({ sourceRoot: sourceMap?.sourceRoot, sources: sourceMap?.sources });
49
+ const sourcePaths = [sourceMap?.sourceRoot, ...(sourceMap?.sources || [])]
50
+ .filter(value => typeof value === 'string');
51
+ const hasAbsoluteSource = sourcePaths.some(value => (node_path_1.default.posix.isAbsolute(value) || node_path_1.default.win32.isAbsolute(value) || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(value)));
52
+ if (sourceMap?.version !== 3 || !Array.isArray(sourceMap.sources)
53
+ || hasAbsoluteSource || paths.includes(node_os_1.default.homedir())) {
54
+ throw new Error('Invalid or host-specific source map.');
55
+ }
56
+ return await (0, file_1.describeFile)(file);
57
+ }
58
+ catch (cause) {
59
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_INVALID_SOURCEMAP', 'Expo export did not produce a portable source map.', { cause, operation: 'validate-export' });
60
+ }
61
+ }
62
+ async function readPreviousManifest(file) {
63
+ try {
64
+ const manifest = JSON.parse(await node_fs_1.default.promises.readFile(file, 'utf8'));
65
+ if (manifest?.schemaVersion !== ManifestSchemaVersion || !Array.isArray(manifest.assets)) {
66
+ throw new Error('Unsupported export manifest schema.');
67
+ }
68
+ for (const asset of manifest.assets) {
69
+ (0, path_1.assertSafeRelative)(asset?.path, 'Previous export manifest');
70
+ }
71
+ return manifest;
72
+ }
73
+ catch (cause) {
74
+ if (cause.code === 'ENOENT')
75
+ return null;
76
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_MANIFEST', 'Cannot read the previous Harmony export manifest.', { cause, operation: 'publish-export' });
77
+ }
78
+ }
79
+ async function publishExportAsync(projectRoot, paths, temporary, entryFile, bytecode) {
80
+ const previous = await readPreviousManifest(paths.manifest);
81
+ const previouslyOwned = new Set((previous?.assets || []).map(asset => asset.path));
82
+ const assetFiles = await (0, file_1.listFiles)(temporary.assets);
83
+ const assetOutputs = [];
84
+ for (const source of assetFiles) {
85
+ const relative = (0, path_1.toPosixPath)(node_path_1.default.relative(temporary.assets, source));
86
+ const nativeRelative = (0, path_1.assertSafeRelative)(relative, 'Expo asset output');
87
+ const destination = node_path_1.default.join(paths.rawfileRoot, nativeRelative);
88
+ if (await node_fs_1.default.promises.lstat(destination).catch(error => error.code === 'ENOENT' ? null : Promise.reject(error))) {
89
+ if (!previouslyOwned.has(relative)) {
90
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_COLLISION', `Expo asset output collides with an unmanaged raw resource: ${relative}.`, { operation: 'publish-export' });
91
+ }
92
+ }
93
+ assetOutputs.push({ descriptor: { path: relative, ...(await (0, file_1.describeFile)(source)) }, source });
94
+ }
95
+ assetOutputs.sort((left, right) => left.descriptor.path.localeCompare(right.descriptor.path, 'en'));
96
+ const assets = assetOutputs.map(output => output.descriptor);
97
+ const bundle = await (0, file_1.describeFile)(temporary.bundle);
98
+ const sourceMap = await (0, file_1.describeFile)(temporary.sourceMap);
99
+ const manifest = {
100
+ assets,
101
+ bundle: {
102
+ bytecodeVersion: bytecode.bytecodeVersion,
103
+ path: (0, path_1.toPosixPath)(node_path_1.default.relative(projectRoot, paths.bundle)),
104
+ ...bundle,
105
+ },
106
+ entryFile: (0, path_1.toPosixPath)(node_path_1.default.relative(projectRoot, entryFile)),
107
+ platform: 'harmony',
108
+ schemaVersion: ManifestSchemaVersion,
109
+ sourceMap: {
110
+ path: (0, path_1.toPosixPath)(node_path_1.default.relative(projectRoot, paths.sourceMap)),
111
+ ...sourceMap,
112
+ },
113
+ transformProfile: 'hermes-stable',
114
+ };
115
+ try {
116
+ await (0, file_1.atomicCopy)(temporary.bundle, paths.bundle, paths.rawfileRoot);
117
+ await (0, file_1.atomicCopy)(temporary.sourceMap, paths.sourceMap, paths.metadataRoot);
118
+ for (const output of assetOutputs) {
119
+ const relative = output.descriptor.path;
120
+ await (0, file_1.atomicCopy)(output.source, node_path_1.default.join(paths.rawfileRoot, (0, path_1.assertSafeRelative)(relative, 'Expo asset output')), paths.rawfileRoot);
121
+ }
122
+ await (0, file_1.atomicWriteJson)(manifest, paths.manifest, node_path_1.default.dirname(paths.manifest));
123
+ const nextOwned = new Set(assets.map(asset => asset.path));
124
+ for (const stale of previouslyOwned) {
125
+ if (nextOwned.has(stale))
126
+ continue;
127
+ const destination = node_path_1.default.join(paths.rawfileRoot, (0, path_1.assertSafeRelative)(stale, 'Previous export manifest'));
128
+ if (!(0, path_1.isInside)(paths.rawfileRoot, destination))
129
+ continue;
130
+ await node_fs_1.default.promises.rm(destination, { force: true });
131
+ await (0, file_1.removeEmptyParents)(destination, paths.rawfileRoot);
132
+ }
133
+ }
134
+ catch (cause) {
135
+ if (cause instanceof errors_1.HarmonyCliError) {
136
+ throw new errors_1.HarmonyCliError(cause.code, cause.message, {
137
+ cause,
138
+ exitCode: cause.exitCode,
139
+ operation: cause.operation,
140
+ });
141
+ }
142
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_PUBLISH', 'Cannot publish the validated Harmony bundle and assets.', { cause, operation: 'publish-export' });
143
+ }
144
+ return manifest;
145
+ }
146
+ async function validatePublishedExportAsync(paths) {
147
+ const manifest = await readPreviousManifest(paths.manifest);
148
+ if (!manifest) {
149
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_MANIFEST', 'Harmony export manifest does not exist. Run expo-harmony export:embed first.', { operation: 'check-export' });
150
+ }
151
+ const bytecode = await assertHermesBundle(paths.bundle);
152
+ const sourceMap = await assertSourceMap(paths.sourceMap);
153
+ const expectedBundle = await (0, file_1.describeFile)(paths.bundle);
154
+ if (manifest.bundle?.sha256 !== expectedBundle.sha256
155
+ || manifest.bundle?.size !== expectedBundle.size
156
+ || manifest.bundle?.bytecodeVersion !== bytecode.bytecodeVersion
157
+ || manifest.sourceMap?.sha256 !== sourceMap.sha256
158
+ || manifest.sourceMap?.size !== sourceMap.size) {
159
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_MANIFEST', 'Harmony bundle or source map differs from the export manifest.', { operation: 'check-export' });
160
+ }
161
+ for (const asset of manifest.assets) {
162
+ const file = node_path_1.default.join(paths.rawfileRoot, (0, path_1.assertSafeRelative)(asset.path, 'Export manifest'));
163
+ const actual = await (0, file_1.describeFile)(file).catch((cause) => {
164
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_MANIFEST', `Harmony export asset is missing or invalid: ${asset.path}.`, { cause, operation: 'check-export' });
165
+ });
166
+ if (actual.sha256 !== asset.sha256 || actual.size !== asset.size) {
167
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_MANIFEST', `Harmony export asset differs from the manifest: ${asset.path}.`, { operation: 'check-export' });
168
+ }
169
+ }
170
+ return manifest;
171
+ }
@@ -0,0 +1,7 @@
1
+ declare function parseExportEmbedArgs(argv: string[]): {
2
+ check: boolean;
3
+ help: boolean;
4
+ project: string;
5
+ resetCache: boolean;
6
+ };
7
+ export { parseExportEmbedArgs };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseExportEmbedArgs = parseExportEmbedArgs;
4
+ const errors_1 = require("../errors");
5
+ const args_1 = require("../args");
6
+ const ExportEmbedOptions = {
7
+ ...args_1.CommonOptions,
8
+ 'check': { type: 'boolean' },
9
+ 'reset-cache': { type: 'boolean' },
10
+ };
11
+ function parseExportEmbedArgs(argv) {
12
+ const { positionals, values } = (0, args_1.parseArgs)(ExportEmbedOptions, argv);
13
+ if (positionals.length > 1) {
14
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_CONFIG_INVALID', `Unexpected export:embed positional argument: ${positionals[1]}`, { operation: 'parse-arguments' });
15
+ }
16
+ return {
17
+ check: Boolean(values.check),
18
+ help: Boolean(values.help),
19
+ project: positionals[0],
20
+ resetCache: Boolean(values['reset-cache']),
21
+ };
22
+ }
@@ -0,0 +1,10 @@
1
+ declare function describeFile(file: string): Promise<{
2
+ sha256: string;
3
+ size: number;
4
+ }>;
5
+ declare function listFiles(root: string): Promise<string[]>;
6
+ declare function atomicCopy(source: string, destination: string, root: string): Promise<void>;
7
+ declare function atomicWriteJson(value: unknown, destination: string, root: string): Promise<void>;
8
+ declare function removeEmptyParents(file: string, stop: string): Promise<void>;
9
+ declare function isNonEmptyRegularFile(file: string): boolean;
10
+ export { atomicCopy, atomicWriteJson, describeFile, isNonEmptyRegularFile, listFiles, removeEmptyParents };
package/build/file.js ADDED
@@ -0,0 +1,110 @@
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.atomicCopy = atomicCopy;
7
+ exports.atomicWriteJson = atomicWriteJson;
8
+ exports.describeFile = describeFile;
9
+ exports.isNonEmptyRegularFile = isNonEmptyRegularFile;
10
+ exports.listFiles = listFiles;
11
+ exports.removeEmptyParents = removeEmptyParents;
12
+ const node_crypto_1 = __importDefault(require("node:crypto"));
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const path_1 = require("./path");
16
+ async function sha256File(file) {
17
+ return await new Promise((resolve, reject) => {
18
+ const hash = node_crypto_1.default.createHash('sha256');
19
+ const input = node_fs_1.default.createReadStream(file);
20
+ input.on('error', reject);
21
+ input.on('data', (chunk) => {
22
+ const bytes = typeof chunk === 'string' ? new TextEncoder().encode(chunk) : Uint8Array.from(chunk);
23
+ hash.update(bytes);
24
+ });
25
+ input.on('end', () => resolve(hash.digest('hex')));
26
+ });
27
+ }
28
+ async function describeFile(file) {
29
+ const stat = await node_fs_1.default.promises.stat(file);
30
+ if (!stat.isFile())
31
+ throw new Error(`Expected a regular file: ${file}`);
32
+ return { sha256: await sha256File(file), size: stat.size };
33
+ }
34
+ async function listFiles(root) {
35
+ const files = [];
36
+ async function visit(directory) {
37
+ const entries = await node_fs_1.default.promises.readdir(directory, { withFileTypes: true });
38
+ entries.sort((left, right) => left.name.localeCompare(right.name, 'en'));
39
+ for (const entry of entries) {
40
+ const file = node_path_1.default.join(directory, entry.name);
41
+ if (entry.isSymbolicLink())
42
+ throw new Error(`Asset output contains a symbolic link: ${entry.name}`);
43
+ if (entry.isDirectory())
44
+ await visit(file);
45
+ else if (entry.isFile())
46
+ files.push(file);
47
+ else
48
+ throw new Error(`Asset output contains an unsupported file: ${entry.name}`);
49
+ }
50
+ }
51
+ await visit(root);
52
+ return files;
53
+ }
54
+ async function ensureSafeParent(root, destination) {
55
+ if (!(0, path_1.isInside)(root, destination) || destination === root) {
56
+ throw new Error('Export destination escapes its owned root.');
57
+ }
58
+ await node_fs_1.default.promises.mkdir(root, { recursive: true });
59
+ await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(destination), { recursive: true });
60
+ const [realRoot, realParent] = await Promise.all([
61
+ node_fs_1.default.promises.realpath(root),
62
+ node_fs_1.default.promises.realpath(node_path_1.default.dirname(destination)),
63
+ ]);
64
+ if (!(0, path_1.isInside)(realRoot, realParent)) {
65
+ throw new Error('Export destination follows a link outside its owned root.');
66
+ }
67
+ }
68
+ async function atomicCopy(source, destination, root) {
69
+ await ensureSafeParent(root, destination);
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`);
71
+ try {
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);
74
+ }
75
+ finally {
76
+ await node_fs_1.default.promises.rm(temp, { force: true });
77
+ }
78
+ }
79
+ async function atomicWriteJson(value, destination, root) {
80
+ await ensureSafeParent(root, destination);
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`);
82
+ try {
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);
85
+ }
86
+ finally {
87
+ await node_fs_1.default.promises.rm(temp, { force: true });
88
+ }
89
+ }
90
+ async function removeEmptyParents(file, stop) {
91
+ let directory = node_path_1.default.dirname(file);
92
+ while (directory !== stop && (0, path_1.isInside)(stop, directory)) {
93
+ try {
94
+ await node_fs_1.default.promises.rmdir(directory);
95
+ }
96
+ catch {
97
+ return;
98
+ }
99
+ directory = node_path_1.default.dirname(directory);
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 runtime Describe project requirements; --client file checks compatibility\n runtime --publish Embed capabilities from prepared native build inputs\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 Export an OTA update, assets, metadata, and source map\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 --host <hostname> Advertised HTTP LAN hostname (start)\n --private-key-path <path>\n Sign development manifests using the configured Updates certificate\n --port <number> Metro and device reverse port (default: 8081)\n --sync Re-run prebuild before building\n --output-dir <path> OTA export directory (default: dist)\n --asset-url <url> HTTP(S) root hosting exported OTA assets\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,44 @@
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
+ runtime Describe project requirements; --client file checks compatibility
9
+ runtime --publish Embed capabilities from prepared native build inputs
10
+ prebuild Generate the Harmony native project with Expo CNG
11
+ prebuild --clean Safely recreate the managed Harmony directory
12
+ prebuild --check Compare generated desired state without project writes
13
+ build Build a HAP without selecting or contacting a device
14
+ doctor Validate config, versions, Metro, RNOH, SDK, and signing
15
+ modules list List native module candidates and their discovery source
16
+ modules inspect Resolve Harmony metadata (--package narrows the result)
17
+ modules verify Validate module config, registration conflicts and safe paths
18
+ export Export an OTA update, assets, metadata, and source map
19
+ export:embed Export validated Hermes bytecode, assets, and a source map
20
+ run Build, install, and launch the Harmony app
21
+
22
+ Options:
23
+ --no-install Skip dependency install (prebuild) or HAP install (run)
24
+ --npm|--yarn|--pnpm|--bun
25
+ Select the package manager used by prebuild dependency install
26
+ --skip-dependency-update <packages>
27
+ Preserve comma-separated dependency versions
28
+ --device <id-or-name> Select an HDC target or start a local emulator by name
29
+ --variant <mode> Build debug or release (default: debug)
30
+ --no-bundler Use an already-running Expo Metro server
31
+ --app-id <bundleName>
32
+ Launch another installed app (requires --no-install when different)
33
+ --host <hostname> Advertised HTTP LAN hostname (start)
34
+ --private-key-path <path>
35
+ Sign development manifests using the configured Updates certificate
36
+ --port <number> Metro and device reverse port (default: 8081)
37
+ --sync Re-run prebuild before building
38
+ --output-dir <path> OTA export directory (default: dist)
39
+ --asset-url <url> HTTP(S) root hosting exported OTA assets
40
+ --check Validate an existing export without writing
41
+ --reset-cache Reset Metro while exporting or starting
42
+ -c, --clear Alias for --reset-cache (start)
43
+ -h, --help Show this help
44
+ `;
@@ -0,0 +1,14 @@
1
+ export { runAsync } from './cli';
2
+ export { buildHarmonyAsync } from './buildHap/build';
3
+ export type { HarmonyBuildOptions, HarmonyBuildResult } from './buildHap/build';
4
+ export { exportEmbedAsync } from './exportEmbed/export';
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';
9
+ export { formatModulesResult, runModulesCommandAsync } from './modules/modules';
10
+ export type { ModulesCommandResult } from './modules/modules';
11
+ export { commitHarmonyNativeBuildCacheAsync, prepareHarmonyNativeBuildCacheAsync } from './native/cache';
12
+ export { runHarmonyAsync } from './run/run';
13
+ export type { HarmonyExportManifest } from './exportEmbed/manifest';
14
+ export type { HarmonyRunOptions, HarmonyRunResult } from './run/run';
package/build/index.js CHANGED
@@ -7,15 +7,16 @@ var build_1 = require("./buildHap/build");
7
7
  Object.defineProperty(exports, "buildHarmonyAsync", { enumerable: true, get: function () { return build_1.buildHarmonyAsync; } });
8
8
  var export_1 = require("./exportEmbed/export");
9
9
  Object.defineProperty(exports, "exportEmbedAsync", { enumerable: true, get: function () { return export_1.exportEmbedAsync; } });
10
- var tools_1 = require("./tools");
11
- Object.defineProperty(exports, "resolveHarmonyBuildPlanAsync", { enumerable: true, get: function () { return tools_1.resolveHarmonyBuildPlanAsync; } });
12
- Object.defineProperty(exports, "resolveHarmonyToolchain", { enumerable: true, get: function () { return tools_1.resolveHarmonyToolchain; } });
13
- var install_1 = require("./run/install");
10
+ var project_1 = require("./native/project");
11
+ Object.defineProperty(exports, "resolveHarmonyBuildPlanAsync", { enumerable: true, get: function () { return project_1.resolveHarmonyBuildPlanAsync; } });
12
+ var toolchain_1 = require("./native/toolchain");
13
+ Object.defineProperty(exports, "resolveHarmonyToolchain", { enumerable: true, get: function () { return toolchain_1.resolveHarmonyToolchain; } });
14
+ var install_1 = require("./native/install");
14
15
  Object.defineProperty(exports, "installHarmonyDependenciesAsync", { enumerable: true, get: function () { return install_1.installHarmonyDependenciesAsync; } });
15
16
  var modules_1 = require("./modules/modules");
16
17
  Object.defineProperty(exports, "formatModulesResult", { enumerable: true, get: function () { return modules_1.formatModulesResult; } });
17
18
  Object.defineProperty(exports, "runModulesCommandAsync", { enumerable: true, get: function () { return modules_1.runModulesCommandAsync; } });
18
- var cache_1 = require("./run/cache");
19
+ var cache_1 = require("./native/cache");
19
20
  Object.defineProperty(exports, "commitHarmonyNativeBuildCacheAsync", { enumerable: true, get: function () { return cache_1.commitHarmonyNativeBuildCacheAsync; } });
20
21
  Object.defineProperty(exports, "prepareHarmonyNativeBuildCacheAsync", { enumerable: true, get: function () { return cache_1.prepareHarmonyNativeBuildCacheAsync; } });
21
22
  var run_1 = require("./run/run");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_module_1 = require("node:module");
4
+ const manifest_1 = require("../development/manifest");
5
+ (0, manifest_1.installHarmonyManifest)(process.cwd());
6
+ process.argv.splice(1, 1);
7
+ const run = (0, node_module_1.createRequire)(__filename);
8
+ run(process.argv[1]);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
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
+ const node_crypto_1 = __importDefault(require("node:crypto"));
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_module_1 = require("node:module");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const errors_1 = require("../errors");
11
+ async function main() {
12
+ const [root, serialized, destination] = process.argv.slice(2);
13
+ const project = (0, node_module_1.createRequire)(node_path_1.default.join(root, 'package.json'));
14
+ const expo = (0, node_module_1.createRequire)(project.resolve('expo/package.json'));
15
+ const require = (0, node_module_1.createRequire)(expo.resolve('@expo/cli/package.json'));
16
+ if (require('./package.json').version !== '55.0.32') {
17
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_UPSTREAM', 'Harmony asset export requires Expo CLI 55.0.32.', { operation: 'export' });
18
+ }
19
+ const options = JSON.parse(serialized);
20
+ require('./build/src/utils/nodeEnv').setNodeEnv('production');
21
+ require('./build/src/utils/nodeEnv').loadEnvFiles(root);
22
+ if (options.update) {
23
+ const directory = node_path_1.default.join(node_path_1.default.dirname(destination), 'export');
24
+ await require('./build/src/export/exportApp').exportAppAsync(root, {
25
+ platforms: ['harmony'], outputDir: directory, clear: options.resetCache,
26
+ dev: false, sourceMaps: true, minify: false, bytecode: false,
27
+ });
28
+ const metadata = JSON.parse(await promises_1.default.readFile(node_path_1.default.join(directory, 'metadata.json'), 'utf8')).fileMetadata.harmony;
29
+ const mime = require('mime-types');
30
+ const assets = [];
31
+ for (const asset of metadata.assets) {
32
+ const source = node_path_1.default.join(directory, asset.path);
33
+ const data = await promises_1.default.readFile(source);
34
+ const key = node_path_1.default.basename(asset.path, node_path_1.default.extname(asset.path));
35
+ const relative = `assets/${key}.${asset.ext}`;
36
+ await promises_1.default.mkdir(node_path_1.default.join(options.assetsDest, 'assets'), { recursive: true });
37
+ await promises_1.default.copyFile(source, node_path_1.default.join(options.assetsDest, relative));
38
+ assets.push({ key, hash: node_crypto_1.default.createHash('sha256').update(Uint8Array.from(data)).digest('base64url'),
39
+ contentType: mime.lookup(asset.ext) || 'application/octet-stream', fileExtension: '.' + asset.ext,
40
+ embeddedAssetFilename: relative });
41
+ }
42
+ await require('@expo/metro/metro/shared/output/bundle').save({
43
+ code: await promises_1.default.readFile(node_path_1.default.join(directory, metadata.bundle), 'utf8'),
44
+ map: await promises_1.default.readFile(node_path_1.default.join(directory, metadata.bundle + '.map'), 'utf8'),
45
+ }, options, console.log);
46
+ await promises_1.default.writeFile(destination, JSON.stringify(assets));
47
+ return;
48
+ }
49
+ const { bundle, assets, files } = await require('./build/src/export/embed/exportEmbedAsync').exportEmbedBundleAndAssetsAsync(root, options);
50
+ const { getAssetLocalPath } = require('./build/src/export/metroAssetLocalPath');
51
+ const mime = require('mime-types');
52
+ const metadata = [];
53
+ for (const asset of assets) {
54
+ if (!Array.isArray(asset.fileHashes) || asset.fileHashes.length !== asset.files.length) {
55
+ throw new errors_1.HarmonyCliError('ERR_HARMONY_EXPORT_ASSETS', 'Metro assets must include expo-asset fileHashes.', { operation: 'export' });
56
+ }
57
+ for (let index = 0; index < asset.files.length; index++) {
58
+ const data = await promises_1.default.readFile(asset.files[index]);
59
+ metadata.push({ key: asset.fileHashes[index], hash: node_crypto_1.default.createHash('sha256').update(Uint8Array.from(data)).digest('base64url'),
60
+ contentType: mime.lookup(asset.type) || 'application/octet-stream', fileExtension: '.' + asset.type,
61
+ embeddedAssetFilename: getAssetLocalPath(asset, { platform: 'harmony', scale: asset.scales[index] }) });
62
+ }
63
+ }
64
+ await promises_1.default.mkdir(node_path_1.default.dirname(options.bundleOutput), { recursive: true });
65
+ await Promise.all([
66
+ require('@expo/metro/metro/shared/output/bundle').save(bundle, options, console.log),
67
+ require('./build/src/export/persistMetroAssets').persistMetroAssetsAsync(root, assets, { platform: 'harmony', outputDirectory: options.assetsDest }),
68
+ ]);
69
+ if (files.size > 0) {
70
+ const { copyPublicFolderAsync, getPublicFolderPath } = require('./build/src/export/publicFolder');
71
+ const { DOM_COMPONENTS_BUNDLE_DIR } = require('./build/src/start/server/middleware/DomComponentsMiddleware');
72
+ await require('./build/src/export/saveAssets').persistMetroFilesAsync(files, options.assetsDest);
73
+ await copyPublicFolderAsync(getPublicFolderPath(root), node_path_1.default.join(options.assetsDest, DOM_COMPONENTS_BUNDLE_DIR));
74
+ }
75
+ await promises_1.default.writeFile(destination, JSON.stringify(metadata));
76
+ }
77
+ main().catch((error) => {
78
+ console.error(error);
79
+ process.exitCode = 1;
80
+ });
@@ -0,0 +1,3 @@
1
+ export { withGeneratedProjectAsync } from '../prebuild/check';
2
+ export { assertSafeCleanTarget } from '../prebuild/clean';
3
+ export { withHarmonyProjectLockAsync } from '../projectLock';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withHarmonyProjectLockAsync = exports.assertSafeCleanTarget = exports.withGeneratedProjectAsync = void 0;
4
+ var check_1 = require("../prebuild/check");
5
+ Object.defineProperty(exports, "withGeneratedProjectAsync", { enumerable: true, get: function () { return check_1.withGeneratedProjectAsync; } });
6
+ var clean_1 = require("../prebuild/clean");
7
+ Object.defineProperty(exports, "assertSafeCleanTarget", { enumerable: true, get: function () { return clean_1.assertSafeCleanTarget; } });
8
+ var projectLock_1 = require("../projectLock");
9
+ Object.defineProperty(exports, "withHarmonyProjectLockAsync", { enumerable: true, get: function () { return projectLock_1.withHarmonyProjectLockAsync; } });
package/build/log.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type Log = Pick<Console, 'error' | 'log' | 'warn'>;
2
+ export declare function progress(options: {
3
+ io?: Log;
4
+ }, message: string): void;
package/build/log.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.progress = progress;
4
+ function progress(options, message) {
5
+ options.io?.log(`› ${message}`);
6
+ }
@@ -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.parseModulesArgs, argv, io);
8
+ if (!args)
9
+ return 0;
10
+ const { projectRoot: root, options } = args;
11
+ const { formatModulesResult, runModulesCommandAsync } = await import('./modules.js');
12
+ const result = await runModulesCommandAsync(root, options);
13
+ io.log(formatModulesResult(result));
14
+ return result.ok ? 0 : 1;
15
+ };
16
+ exports.command = command;