@expo-harmony/expo-modules-autolinking 55.0.25-harmony.1

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 (103) hide show
  1. package/README.md +12 -0
  2. package/bin/expo-harmony-autolinking.ts +14 -0
  3. package/build/bin/expo-harmony-autolinking.d.ts +2 -0
  4. package/build/bin/expo-harmony-autolinking.js +11 -0
  5. package/build/index.d.ts +8 -0
  6. package/build/index.js +15 -0
  7. package/build/src/autolinking/link.d.ts +3 -0
  8. package/build/src/autolinking/link.js +150 -0
  9. package/build/src/autolinking/resolve.d.ts +4 -0
  10. package/build/src/autolinking/resolve.js +25 -0
  11. package/build/src/autolinking/search.d.ts +3 -0
  12. package/build/src/autolinking/search.js +357 -0
  13. package/build/src/autolinking/verify.d.ts +4 -0
  14. package/build/src/autolinking/verify.js +366 -0
  15. package/build/src/commands/autolinkingOptions.d.ts +12 -0
  16. package/build/src/commands/autolinkingOptions.js +35 -0
  17. package/build/src/commands/cli.d.ts +2 -0
  18. package/build/src/commands/cli.js +50 -0
  19. package/build/src/commands/link.d.ts +2 -0
  20. package/build/src/commands/link.js +27 -0
  21. package/build/src/commands/output.d.ts +3 -0
  22. package/build/src/commands/output.js +33 -0
  23. package/build/src/commands/resolve.d.ts +2 -0
  24. package/build/src/commands/resolve.js +13 -0
  25. package/build/src/commands/search.d.ts +2 -0
  26. package/build/src/commands/search.js +13 -0
  27. package/build/src/commands/verify.d.ts +2 -0
  28. package/build/src/commands/verify.js +17 -0
  29. package/build/src/config/constants.d.ts +17 -0
  30. package/build/src/config/constants.js +40 -0
  31. package/build/src/config/options.d.ts +13 -0
  32. package/build/src/config/options.js +202 -0
  33. package/build/src/errors.d.ts +18 -0
  34. package/build/src/errors.js +35 -0
  35. package/build/src/harmony/manifest/generate.d.ts +4 -0
  36. package/build/src/harmony/manifest/generate.js +55 -0
  37. package/build/src/harmony/manifest/index.d.ts +7 -0
  38. package/build/src/harmony/manifest/index.js +64 -0
  39. package/build/src/harmony/materialize.d.ts +12 -0
  40. package/build/src/harmony/materialize.js +278 -0
  41. package/build/src/harmony/ohpm/dependencies.d.ts +7 -0
  42. package/build/src/harmony/ohpm/dependencies.js +52 -0
  43. package/build/src/harmony/persistence/canonicalize.d.ts +15 -0
  44. package/build/src/harmony/persistence/canonicalize.js +108 -0
  45. package/build/src/harmony/providers/host.d.ts +3 -0
  46. package/build/src/harmony/providers/host.js +88 -0
  47. package/build/src/harmony/rnoh/cli.d.ts +2 -0
  48. package/build/src/harmony/rnoh/cli.js +55 -0
  49. package/build/src/harmony/rnoh/link.d.ts +2 -0
  50. package/build/src/harmony/rnoh/link.js +419 -0
  51. package/build/src/harmony/rnoh/packageMetadata.d.ts +12 -0
  52. package/build/src/harmony/rnoh/packageMetadata.js +125 -0
  53. package/build/src/harmony/transaction/previousState.d.ts +15 -0
  54. package/build/src/harmony/transaction/previousState.js +96 -0
  55. package/build/src/harmony/transaction/publish.d.ts +6 -0
  56. package/build/src/harmony/transaction/publish.js +272 -0
  57. package/build/src/harmony/transaction/stageProject.d.ts +18 -0
  58. package/build/src/harmony/transaction/stageProject.js +313 -0
  59. package/build/src/metadata/descriptor.d.ts +19 -0
  60. package/build/src/metadata/descriptor.js +306 -0
  61. package/build/src/metadata/host.d.ts +5 -0
  62. package/build/src/metadata/host.js +37 -0
  63. package/build/src/metadata/schema.d.ts +10 -0
  64. package/build/src/metadata/schema.js +171 -0
  65. package/build/src/types.d.ts +194 -0
  66. package/build/src/types.js +2 -0
  67. package/build/src/utilities/values.d.ts +17 -0
  68. package/build/src/utilities/values.js +205 -0
  69. package/harmony-module.schema.json +28 -0
  70. package/index.ts +41 -0
  71. package/package.json +66 -0
  72. package/src/autolinking/link.ts +170 -0
  73. package/src/autolinking/resolve.ts +33 -0
  74. package/src/autolinking/search.ts +369 -0
  75. package/src/autolinking/verify.ts +415 -0
  76. package/src/commands/autolinkingOptions.ts +38 -0
  77. package/src/commands/cli.ts +56 -0
  78. package/src/commands/link.ts +28 -0
  79. package/src/commands/output.ts +36 -0
  80. package/src/commands/resolve.ts +14 -0
  81. package/src/commands/search.ts +14 -0
  82. package/src/commands/verify.ts +17 -0
  83. package/src/config/constants.ts +45 -0
  84. package/src/config/options.ts +195 -0
  85. package/src/errors.ts +48 -0
  86. package/src/harmony/manifest/generate.ts +65 -0
  87. package/src/harmony/manifest/index.ts +81 -0
  88. package/src/harmony/materialize.ts +382 -0
  89. package/src/harmony/ohpm/dependencies.ts +59 -0
  90. package/src/harmony/persistence/canonicalize.ts +156 -0
  91. package/src/harmony/providers/host.ts +147 -0
  92. package/src/harmony/rnoh/cli.ts +59 -0
  93. package/src/harmony/rnoh/link.ts +481 -0
  94. package/src/harmony/rnoh/packageMetadata.ts +105 -0
  95. package/src/harmony/transaction/previousState.ts +71 -0
  96. package/src/harmony/transaction/publish.ts +287 -0
  97. package/src/harmony/transaction/stageProject.ts +345 -0
  98. package/src/metadata/descriptor.ts +324 -0
  99. package/src/metadata/host.ts +41 -0
  100. package/src/metadata/schema.ts +184 -0
  101. package/src/types.ts +220 -0
  102. package/src/utilities/values.ts +190 -0
  103. package/tsconfig.json +24 -0
@@ -0,0 +1,415 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import type { VerificationResult, VerifyOptions } from '../types';
5
+ import { HarmonyAutolinkingError } from '../errors';
6
+ import { HostMetadataFields } from '../metadata/host';
7
+ import { createDescriptorFromSearchRecordAsync } from '../metadata/descriptor';
8
+ import { searchModulesAsync } from './search';
9
+ import { compareModuleDescriptors } from './resolve';
10
+ import { resolveRnohMetadata } from '../harmony/rnoh/packageMetadata';
11
+ import { moduleRegistrationId } from '../harmony/providers/host';
12
+ import { compareText, emitLog, isObject, resolvePackageFromProject, sortedUniqueStrings } from '../utilities/values';
13
+
14
+ function diagnosticFromError(error, fallback) {
15
+ const normalized = error instanceof HarmonyAutolinkingError
16
+ ? error
17
+ : new HarmonyAutolinkingError('INVALID_METADATA', error.message || String(error), { cause: error, packageName: fallback, stage: 'verify' });
18
+
19
+ return {
20
+ severity: 'error',
21
+ code: normalized.code,
22
+ message: normalized.message,
23
+ ...(normalized.packageName || fallback ? { packageName: normalized.packageName || fallback } : {}),
24
+ stage: normalized.stage,
25
+ };
26
+ }
27
+
28
+ function compareDiagnostics(left, right) {
29
+ const order = { error: 0, warning: 1 };
30
+
31
+ return (order[left.severity] - order[right.severity])
32
+ || compareText(left.code, right.code)
33
+ || compareText(left.packageName || '', right.packageName || '')
34
+ || compareText(left.message, right.message)
35
+ || compareText(JSON.stringify(left.sources || []), JSON.stringify(right.sources || []));
36
+ }
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ function diagnosticSource(descriptor, extra: Record<string, any> = {}) {
40
+ return {
41
+ packageName: descriptor.packageName,
42
+ packageVersion: descriptor.packageVersion,
43
+ packageRoot: descriptor.packageRoot,
44
+ ...extra,
45
+ };
46
+ }
47
+
48
+ function conflictDiagnostic(code, label, value, entries, packageName?: string, severity = 'error') {
49
+ return {
50
+ severity,
51
+ code: `ERR_EXPO_HARMONY_${code}`,
52
+ message: `${label} "${value}" is declared more than once.`,
53
+ packageName,
54
+ stage: 'verify',
55
+ sources: entries.map(entry => entry.source).sort((left, right) => {
56
+ return compareText(left.packageName, right.packageName)
57
+ || compareText(left.packageRoot, right.packageRoot)
58
+ || compareText(left.field || '', right.field || '');
59
+ }),
60
+ };
61
+ }
62
+
63
+ function collectDescriptorConflicts(descriptors) {
64
+ const diagnostics = [];
65
+ const registrations = new Map();
66
+ const oh = new Map();
67
+ const rnohCmakeTargets = new Map();
68
+ const rnohEtsSymbols = new Map();
69
+ const rnohCppSymbols = new Map();
70
+
71
+ function push(map, key, source, value = key) {
72
+ const entries = map.get(key) || [];
73
+ entries.push({ value, source });
74
+ map.set(key, entries);
75
+ }
76
+
77
+ for (const descriptor of descriptors) {
78
+ if (descriptor.arkTs) {
79
+ const arkTs = descriptor.arkTs;
80
+ push(oh, arkTs.ohPackageName, diagnosticSource(descriptor, {
81
+ field: `HAR:${arkTs.harPath}`,
82
+ }), `${descriptor.packageRoot}\0${arkTs.harPath}`);
83
+ for (const moduleClass of descriptor.harmony.modules) {
84
+ const registrationId = moduleRegistrationId(arkTs.ohPackageName, moduleClass);
85
+ push(registrations, registrationId, diagnosticSource(descriptor, {
86
+ field: `module:${moduleClass}`,
87
+ }));
88
+ }
89
+ }
90
+ if (descriptor.rnoh.harPaths.length > 0) {
91
+ const rnohMetadata = resolveRnohMetadata(descriptor);
92
+ const packageSource = `${descriptor.packageRoot}\0${rnohMetadata.harMappings
93
+ .map(mapping => mapping.harPath)
94
+ .join('\0')}`;
95
+ for (const mapping of rnohMetadata.harMappings) {
96
+ push(oh, mapping.ohPackageName, diagnosticSource(descriptor, {
97
+ field: `HAR:${mapping.harPath}`,
98
+ }), `${descriptor.packageRoot}\0${mapping.harPath}`);
99
+ }
100
+ push(rnohEtsSymbols, rnohMetadata.etsPackageClassName, diagnosticSource(descriptor, {
101
+ field: 'etsPackageClassName',
102
+ }), packageSource);
103
+ push(rnohCppSymbols, rnohMetadata.cppPackageClassName, diagnosticSource(descriptor, {
104
+ field: 'cppPackageClassName',
105
+ }), packageSource);
106
+ push(rnohCmakeTargets, rnohMetadata.cmakeLibraryTargetName, diagnosticSource(descriptor, {
107
+ field: 'cmakeLibraryTargetName',
108
+ }), packageSource);
109
+ }
110
+ }
111
+
112
+ for (const [value, entries] of registrations) {
113
+ if (entries.length > 1) {
114
+ diagnostics.push(conflictDiagnostic('DUPLICATE_MODULE', 'ArkTS module registration', value, entries));
115
+ }
116
+ }
117
+
118
+ for (const [value, entries] of rnohCmakeTargets) {
119
+ if (new Set(entries.map(entry => entry.value)).size > 1) {
120
+ diagnostics.push(conflictDiagnostic('DUPLICATE_MODULE', 'CMake target', value, entries));
121
+ }
122
+ }
123
+
124
+ for (const [value, entries] of rnohEtsSymbols) {
125
+ if (new Set(entries.map(entry => entry.value)).size > 1) {
126
+ diagnostics.push(conflictDiagnostic('DUPLICATE_MODULE', 'ETS Package class', value, entries));
127
+ }
128
+ }
129
+
130
+ for (const [value, entries] of rnohCppSymbols) {
131
+ if (new Set(entries.map(entry => entry.value)).size > 1) {
132
+ diagnostics.push(conflictDiagnostic('DUPLICATE_MODULE', 'C++ Package class', value, entries));
133
+ }
134
+ }
135
+
136
+ for (const [value, entries] of oh) {
137
+ if (new Set(entries.map(entry => entry.value)).size > 1) {
138
+ diagnostics.push(conflictDiagnostic('DUPLICATE_MODULE', 'OH package', value, entries));
139
+ }
140
+ }
141
+
142
+ return diagnostics;
143
+ }
144
+
145
+ function normalizeDescriptorHarPaths(descriptor) {
146
+ if (!Array.isArray(descriptor.rnoh.harPaths)) {
147
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'descriptor.rnoh.harPaths must be an array.', {
148
+ packageName: descriptor.packageName,
149
+ stage: 'verify',
150
+ });
151
+ }
152
+
153
+ const harPaths = descriptor.rnoh.harPaths.map((harPath, index) => {
154
+ if (typeof harPath !== 'string' || !harPath.trim() || harPath.includes('\0')
155
+ || path.win32.isAbsolute(harPath)) {
156
+ throw new HarmonyAutolinkingError(
157
+ 'INVALID_METADATA',
158
+ `descriptor.rnoh.harPaths[${index}] must be a non-empty package-relative HAR path.`,
159
+ { packageName: descriptor.packageName, stage: 'verify' }
160
+ );
161
+ }
162
+ const normalized = path.posix.normalize(harPath.replace(/\\/g, '/'));
163
+ if (path.posix.isAbsolute(normalized) || normalized === '..' || normalized.startsWith('../')
164
+ || path.posix.extname(normalized) !== '.har') {
165
+ throw new HarmonyAutolinkingError(
166
+ 'INVALID_METADATA',
167
+ `descriptor.rnoh.harPaths[${index}] must be a package-relative .har file.`,
168
+ { packageName: descriptor.packageName, stage: 'verify' }
169
+ );
170
+ }
171
+ return normalized;
172
+ });
173
+
174
+ return sortedUniqueStrings(harPaths);
175
+ }
176
+
177
+ function harRoot(harPaths) {
178
+ const directories = harPaths.map(harPath => path.posix.dirname(harPath).split('/'));
179
+ const common = [];
180
+
181
+ for (let index = 0; index < directories[0].length; index += 1) {
182
+ const segment = directories[0][index];
183
+ if (directories.every(parts => parts[index] === segment)) common.push(segment);
184
+ else break;
185
+ }
186
+ return common.length === 0 ? '.' : common.join('/');
187
+ }
188
+
189
+ function searchRecordFromDescriptor(descriptor, declared) {
190
+ if (!isObject(descriptor)) {
191
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'Harmony module descriptor must be an object.', {
192
+ stage: 'verify',
193
+ });
194
+ }
195
+ if (!isObject(descriptor.expo) || !isObject(descriptor.harmony)) {
196
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'descriptor.expo and descriptor.harmony must be objects.', {
197
+ packageName: descriptor.packageName,
198
+ stage: 'verify',
199
+ });
200
+ }
201
+ if (!isObject(descriptor.rnoh)) {
202
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'descriptor.rnoh must be an object.', {
203
+ packageName: descriptor.packageName,
204
+ stage: 'verify',
205
+ });
206
+ }
207
+
208
+ const host = HostMetadataFields.some(field => descriptor.expo[field]?.length > 0);
209
+ const modules = Array.isArray(descriptor.harmony.modules)
210
+ ? [...descriptor.harmony.modules]
211
+ : [];
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const rnoh: Record<string, any> = {};
214
+ if (descriptor.rnoh.ohPackageName !== undefined) {
215
+ rnoh.ohPackageName = Array.isArray(descriptor.rnoh.ohPackageName)
216
+ ? descriptor.rnoh.ohPackageName.map(mapping => ({ ...mapping }))
217
+ : descriptor.rnoh.ohPackageName;
218
+ }
219
+ if (descriptor.rnoh.mainHarPath !== undefined) rnoh.mainHarPath = descriptor.rnoh.mainHarPath;
220
+ else if (declared.length > 0) rnoh.mainHarPath = harRoot(declared);
221
+ if (descriptor.rnoh.etsPackageImport !== undefined) {
222
+ rnoh.etsPackageImport = descriptor.rnoh.etsPackageImport;
223
+ }
224
+ if (descriptor.rnoh.etsPackageClassName !== undefined) {
225
+ rnoh.etsPackageClassName = descriptor.rnoh.etsPackageClassName;
226
+ }
227
+ if (descriptor.rnoh.cppPackageClassName !== undefined) {
228
+ rnoh.cppPackageClassName = descriptor.rnoh.cppPackageClassName;
229
+ }
230
+ if (descriptor.rnoh.cmakeLibraryTargetName !== undefined) {
231
+ rnoh.cmakeLibraryTargetName = descriptor.rnoh.cmakeLibraryTargetName;
232
+ }
233
+
234
+ return {
235
+ packageName: descriptor.packageName,
236
+ packageVersion: descriptor.packageVersion,
237
+ packageRoot: descriptor.packageRoot,
238
+ source: descriptor.source,
239
+ supportsHarmony: modules.length > 0 || host,
240
+ expoModuleConfig: modules.length > 0 || host
241
+ ? {
242
+ platforms: ['harmony'],
243
+ harmony: {
244
+ modules,
245
+ ...Object.fromEntries(HostMetadataFields.map(field => [
246
+ field,
247
+ [...(descriptor.expo[field] || [])],
248
+ ])),
249
+ },
250
+ }
251
+ : null,
252
+ rnohMetadata: Object.keys(rnoh).length > 0 ? rnoh : null,
253
+ revision: {
254
+ originPath: descriptor.packageLinkPath || descriptor.packageRoot,
255
+ },
256
+ };
257
+ }
258
+
259
+ async function validateDescriptorAsync(descriptor, buildType) {
260
+ if (!isObject(descriptor) || !isObject(descriptor.rnoh)) {
261
+ return createDescriptorFromSearchRecordAsync(searchRecordFromDescriptor(descriptor, []), buildType);
262
+ }
263
+
264
+ const declared = normalizeDescriptorHarPaths(descriptor);
265
+ const normalized = await createDescriptorFromSearchRecordAsync(
266
+ searchRecordFromDescriptor(descriptor, declared),
267
+ buildType
268
+ );
269
+
270
+ if (normalized.rnoh.harPaths.length !== declared.length
271
+ || normalized.rnoh.harPaths.some((harPath, index) => harPath !== declared[index])) {
272
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'descriptor.rnoh.harPaths must exactly list the HAR files selected by descriptor.rnoh.mainHarPath.', {
273
+ packageName: descriptor.packageName,
274
+ stage: 'verify',
275
+ details: { declared, discovered: normalized.rnoh.harPaths },
276
+ });
277
+ }
278
+
279
+ const packageRoot = await fs.promises.realpath(descriptor.packageRoot);
280
+ if (packageRoot !== descriptor.packageRoot) {
281
+ normalized.packageRoot = packageRoot;
282
+ }
283
+ return normalized;
284
+ }
285
+
286
+ async function verifyModulesAsync(options: VerifyOptions = {}): Promise<VerificationResult> {
287
+ let searchResult = options.searchResult;
288
+ const inputs = options.modules;
289
+
290
+ if (inputs !== undefined && !Array.isArray(inputs)) {
291
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'modules must be an array when provided.', { stage: 'verify' });
292
+ }
293
+ if (!searchResult && inputs === undefined) {
294
+ const runtime = [
295
+ '@react-native-oh/react-native-harmony',
296
+ 'react-native',
297
+ 'react-native-tvos',
298
+ ].filter((packageName) => {
299
+ try {
300
+ resolvePackageFromProject(
301
+ options.projectRoot || process.cwd(),
302
+ `${packageName}/package.json`
303
+ );
304
+ return true;
305
+ } catch (_cause) {
306
+ return false;
307
+ }
308
+ });
309
+ searchResult = await searchModulesAsync({
310
+ ...options,
311
+ include: [...(options.include || []), ...runtime],
312
+ includeReactNativeProjectConfig: true,
313
+ } as VerifyOptions & { includeReactNativeProjectConfig: true });
314
+ }
315
+
316
+ const descriptors = [];
317
+ const diagnostics = [];
318
+ const buildType = searchResult?.options.buildType || options.buildType || 'debug';
319
+
320
+ if (inputs !== undefined) {
321
+ for (const input of inputs) {
322
+ try {
323
+ descriptors.push(await validateDescriptorAsync(input, buildType));
324
+ } catch (error) {
325
+ diagnostics.push(diagnosticFromError(error, input?.packageName));
326
+ }
327
+ }
328
+ } else {
329
+ const records = [...searchResult.modules].sort((left, right) => {
330
+ return compareText(left.packageName, right.packageName)
331
+ || compareText(left.packageVersion, right.packageVersion)
332
+ || compareText(left.packageRoot, right.packageRoot);
333
+ });
334
+ for (const record of records) {
335
+ try {
336
+ descriptors.push(await createDescriptorFromSearchRecordAsync(record, buildType));
337
+ } catch (error) {
338
+ diagnostics.push(diagnosticFromError(error, record.packageName));
339
+ }
340
+ }
341
+ for (const packageName of sortedUniqueStrings(searchResult.missingIncludes || [])) {
342
+ diagnostics.push({
343
+ severity: 'warning',
344
+ code: 'ERR_EXPO_HARMONY_INCLUDED_MODULE_NOT_FOUND',
345
+ message: `Included package "${packageName}" was not found.`,
346
+ packageName,
347
+ stage: 'verify',
348
+ });
349
+ }
350
+ }
351
+
352
+ descriptors.sort(compareModuleDescriptors);
353
+ diagnostics.push(...collectDescriptorConflicts(descriptors));
354
+
355
+ if (searchResult) {
356
+ const seen = new Map();
357
+ for (const descriptor of descriptors) {
358
+ const set = seen.get(descriptor.packageName) || new Set();
359
+ set.add(`${descriptor.packageVersion}\0${descriptor.packageRoot}`);
360
+ seen.set(descriptor.packageName, set);
361
+ }
362
+ for (const duplicate of searchResult.duplicates || []) {
363
+ const list = [...new Map(duplicate.revisions.map(revision => [
364
+ `${revision.version}\0${revision.path}`, revision,
365
+ ])).values()] as Array<{ version: string; path: string }>;
366
+ if (list.length < 2 || (seen.get(duplicate.packageName)?.size || 0) >= list.length) {
367
+ continue;
368
+ }
369
+ diagnostics.push(conflictDiagnostic(
370
+ 'DUPLICATE_MODULE',
371
+ 'npm package',
372
+ duplicate.packageName,
373
+ list.map(revision => ({
374
+ source: {
375
+ packageName: duplicate.packageName,
376
+ packageVersion: revision.version,
377
+ packageRoot: revision.path,
378
+ },
379
+ })),
380
+ duplicate.packageName,
381
+ 'warning'
382
+ ));
383
+ }
384
+ }
385
+
386
+ diagnostics.sort(compareDiagnostics);
387
+ const result = {
388
+ valid: diagnostics.every(diagnostic => diagnostic.severity !== 'error'),
389
+ diagnostics,
390
+ modules: descriptors,
391
+ };
392
+
393
+ emitLog(options.logger, result.valid ? 'debug' : 'warn', 'Harmony module verification completed.', {
394
+ valid: result.valid,
395
+ diagnosticCount: diagnostics.length,
396
+ moduleCount: descriptors.length,
397
+ });
398
+ return result;
399
+ }
400
+
401
+ function assertVerificationSucceeded(result, stage = 'verify') {
402
+ if (result.valid) return;
403
+
404
+ const first = result.diagnostics.find(diagnostic => diagnostic.severity === 'error');
405
+ throw new HarmonyAutolinkingError(first?.code || 'INVALID_METADATA', 'Harmony module verification failed.', {
406
+ diagnostics: result.diagnostics,
407
+ packageName: first?.packageName,
408
+ stage,
409
+ });
410
+ }
411
+
412
+ export {
413
+ assertVerificationSucceeded,
414
+ verifyModulesAsync,
415
+ };
@@ -0,0 +1,38 @@
1
+ import { InvalidOptionArgumentError } from 'commander';
2
+
3
+ import { Platform } from '../config/constants';
4
+
5
+ function parsePositiveInt(value) {
6
+ const parsed = Number(value);
7
+
8
+ if (!Number.isInteger(parsed) || parsed <= 0) {
9
+ throw new InvalidOptionArgumentError('Expected a positive integer.');
10
+ }
11
+ return parsed;
12
+ }
13
+
14
+ function addCommonOptions(command) {
15
+ command
16
+ .option('-e, --exclude <exclude...>', 'package names to exclude when looking up for modules', (value, previous = []) => previous.concat(value))
17
+ .option('-p, --platform [platform]', 'the platform that the resulting modules must support. Available options: "harmony"', Platform)
18
+ .option('--project-root <projectRoot>', 'the path to the root of the project. Defaults to current working directory', process.cwd())
19
+ .option('--native-modules-dir <path>', 'replace configured local native modules directory')
20
+ .option('--include <include...>', 'package names to verify for duplicate revisions', (value, previous = []) => previous.concat(value))
21
+ .option('--build-type <type>', 'debug or release');
22
+
23
+ return command;
24
+ }
25
+
26
+ function toApiOptions(options, searchPaths = undefined) {
27
+ return {
28
+ platform: options.platform,
29
+ projectRoot: options.projectRoot,
30
+ ...(searchPaths !== undefined && searchPaths !== null ? { searchPaths } : {}),
31
+ ...(options.nativeModulesDir !== undefined ? { nativeModulesDir: options.nativeModulesDir } : {}),
32
+ ...(options.exclude !== undefined ? { exclude: options.exclude } : {}),
33
+ ...(options.include !== undefined ? { include: options.include } : {}),
34
+ ...(options.buildType !== undefined ? { buildType: options.buildType } : {}),
35
+ };
36
+ }
37
+
38
+ export { addCommonOptions, parsePositiveInt, toApiOptions };
@@ -0,0 +1,56 @@
1
+ import { Command, CommanderError } from 'commander';
2
+
3
+ import { HarmonyAutolinkingError } from '../errors';
4
+ import { ToolVersion } from '../config/constants';
5
+ import { stringifyJson } from '../utilities/values';
6
+
7
+ import { registerLinkCommand } from './link';
8
+ import { registerResolveCommand } from './resolve';
9
+ import { registerSearchCommand } from './search';
10
+ import { registerVerifyCommand } from './verify';
11
+
12
+ function createProgram(io, state) {
13
+ const program = new Command();
14
+
15
+ program
16
+ .name('expo-harmony-autolinking')
17
+ .description('Expo Modules and RNOH autolinking for HarmonyOS')
18
+ .version(ToolVersion)
19
+ .exitOverride()
20
+ .configureOutput({
21
+ writeOut: value => io.stdout(value),
22
+ writeErr: () => {},
23
+ });
24
+
25
+ registerSearchCommand(program, io);
26
+ registerResolveCommand(program, io);
27
+ registerVerifyCommand(program, io, state);
28
+ registerLinkCommand(program, io);
29
+ return program;
30
+ }
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ async function runCliAsync(argv: string[], io: Record<string, any> = {}): Promise<number> {
34
+ const streams = {
35
+ stdout: io.stdout || (value => process.stdout.write(value)),
36
+ stderr: io.stderr || (value => process.stderr.write(value)),
37
+ };
38
+ const state = { exitCode: 0 };
39
+ const program = createProgram(streams, state);
40
+
41
+ try {
42
+ await program.parseAsync(argv, { from: 'user' });
43
+ return state.exitCode;
44
+ } catch (error) {
45
+ if (error instanceof CommanderError && error.exitCode === 0) return 0;
46
+
47
+ const normalized = error instanceof HarmonyAutolinkingError
48
+ ? error
49
+ : new HarmonyAutolinkingError('INVALID_OPTIONS', error.message || String(error), { cause: error, stage: 'cli' });
50
+
51
+ streams.stderr(stringifyJson(normalized.toJSON()));
52
+ return error instanceof CommanderError ? Math.max(1, error.exitCode || 2) : 2;
53
+ }
54
+ }
55
+
56
+ export { runCliAsync };
@@ -0,0 +1,28 @@
1
+ import { linkModulesAsync } from '../autolinking/link';
2
+
3
+ import { addCommonOptions, parsePositiveInt, toApiOptions } from './autolinkingOptions';
4
+ import { writeResult } from './output';
5
+
6
+ function registerLinkCommand(program, io) {
7
+ addCommonOptions(program.command('link [searchPaths...]').description('transactionally link Expo and RNOH modules'))
8
+ .requiredOption('--harmony-project-path <path>', 'Harmony project root')
9
+ .option('--node-modules-path <path>', 'node_modules directory')
10
+ .option('--react-native-executable <path>', 'explicit project-local react-native executable')
11
+ .option('--rnoh-cli-package-json <path>', 'explicit RNOH CLI package.json')
12
+ .option('--timeout-ms <milliseconds>', 'RNOH command timeout', parsePositiveInt)
13
+ .option('--output-limit <bytes>', 'per-stream output capture limit', parsePositiveInt)
14
+ .option('-j, --json', 'output results in the plain JSON format', false)
15
+ .action(async (searchPaths, options) => {
16
+ writeResult(io, await linkModulesAsync({
17
+ ...toApiOptions(options, searchPaths),
18
+ harmonyProjectPath: options.harmonyProjectPath,
19
+ nodeModulesPath: options.nodeModulesPath,
20
+ reactNativeExecutable: options.reactNativeExecutable,
21
+ rnohCliPackageJsonPath: options.rnohCliPackageJson,
22
+ timeoutMs: options.timeoutMs,
23
+ outputLimit: options.outputLimit,
24
+ }), options);
25
+ });
26
+ }
27
+
28
+ export { registerLinkCommand };
@@ -0,0 +1,36 @@
1
+ import { inspect } from 'node:util';
2
+
3
+ import { stringifyJson } from '../utilities/values';
4
+
5
+ function formatResult(value) {
6
+ return inspect(value, { depth: null, colors: false });
7
+ }
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ function writeResult(io, value, options: Record<string, any> = {}) {
11
+ io.stdout(options.json ? stringifyJson(value) : `${formatResult(value)}\n`);
12
+ }
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ function writeVerifyResult(io, result, options: Record<string, any> = {}) {
16
+ if (options.json) {
17
+ writeResult(io, result, options);
18
+ return;
19
+ }
20
+
21
+ if (result.diagnostics.length === 0) {
22
+ io.stdout('✅ Everything is fine!\n');
23
+ } else {
24
+ for (const diagnostic of result.diagnostics) {
25
+ const icon = diagnostic.severity === 'error' ? '❌' : '⚠️';
26
+ const suffix = diagnostic.packageName ? ` (${diagnostic.packageName})` : '';
27
+ io.stdout(`${icon} [${diagnostic.code}] ${diagnostic.message}${suffix}\n`);
28
+ }
29
+ }
30
+
31
+ if (options.verbose) {
32
+ io.stdout(`${formatResult({ modules: result.modules })}\n`);
33
+ }
34
+ }
35
+
36
+ export { writeResult, writeVerifyResult };
@@ -0,0 +1,14 @@
1
+ import { resolveModulesAsync } from '../autolinking/resolve';
2
+
3
+ import { addCommonOptions, toApiOptions } from './autolinkingOptions';
4
+ import { writeResult } from './output';
5
+
6
+ function registerResolveCommand(program, io) {
7
+ addCommonOptions(program.command('resolve [searchPaths...]').description('resolve Harmony module metadata'))
8
+ .option('-j, --json', 'output results in the plain JSON format', false)
9
+ .action(async (searchPaths, options) => {
10
+ writeResult(io, await resolveModulesAsync(toApiOptions(options, searchPaths)), options);
11
+ });
12
+ }
13
+
14
+ export { registerResolveCommand };
@@ -0,0 +1,14 @@
1
+ import { searchModulesAsync } from '../autolinking/search';
2
+
3
+ import { addCommonOptions, toApiOptions } from './autolinkingOptions';
4
+ import { writeResult } from './output';
5
+
6
+ function registerSearchCommand(program, io) {
7
+ addCommonOptions(program.command('search [searchPaths...]').description('discover Harmony native module candidates'))
8
+ .option('-j, --json', 'output results in the plain JSON format', false)
9
+ .action(async (searchPaths, options) => {
10
+ writeResult(io, await searchModulesAsync(toApiOptions(options, searchPaths)), options);
11
+ });
12
+ }
13
+
14
+ export { registerSearchCommand };
@@ -0,0 +1,17 @@
1
+ import { verifyModulesAsync } from '../autolinking/verify';
2
+
3
+ import { addCommonOptions, toApiOptions } from './autolinkingOptions';
4
+ import { writeVerifyResult } from './output';
5
+
6
+ function registerVerifyCommand(program, io, state) {
7
+ addCommonOptions(program.command('verify').description('verify Harmony module metadata and conflicts'))
8
+ .option('-v, --verbose', 'output all verification details', false)
9
+ .option('-j, --json', 'output results in the plain JSON format', false)
10
+ .action(async (options) => {
11
+ const result = await verifyModulesAsync(toApiOptions(options));
12
+ writeVerifyResult(io, result, options);
13
+ if (!result.valid) state.exitCode = 1;
14
+ });
15
+ }
16
+
17
+ export { registerVerifyCommand };
@@ -0,0 +1,45 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const Platform = 'harmony';
5
+ const RnohCliPackage = '@react-native-oh/react-native-harmony-cli';
6
+ const ToolVersion = JSON.parse(readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf8')).version;
7
+ const ManifestSchemaVersion = 3;
8
+
9
+ const RnohArtifacts = Object.freeze({
10
+ etsFactory: 'entry/src/main/ets/RNOHPackagesFactory.ets',
11
+ cppFactory: 'entry/src/main/cpp/RNOHPackagesFactory.h',
12
+ cmake: 'entry/src/main/cpp/autolinking.cmake',
13
+ ohPackage: 'oh-package.json5',
14
+ });
15
+
16
+ const ExpoArtifacts = Object.freeze({
17
+ hostProvider: 'entry/src/main/ets/generated/ExpoHarmonyHostProvider.ets',
18
+ });
19
+
20
+ const ManifestArtifact = '.expo/harmony/autolinking.json';
21
+
22
+ function managedArtifactsForHarmonyRoot(harmonyRoot) {
23
+ const prefix = String(harmonyRoot).replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
24
+
25
+ const withinHarmony = [
26
+ ...Object.values(RnohArtifacts),
27
+ ...Object.values(ExpoArtifacts),
28
+ ].map(relative => prefix ? `${prefix}/${relative}` : relative);
29
+
30
+ return Object.freeze([...withinHarmony, ManifestArtifact]);
31
+ }
32
+
33
+ const ManagedArtifacts = managedArtifactsForHarmonyRoot('harmony');
34
+
35
+ export {
36
+ ExpoArtifacts,
37
+ ManagedArtifacts,
38
+ ManifestArtifact,
39
+ ManifestSchemaVersion,
40
+ managedArtifactsForHarmonyRoot,
41
+ Platform,
42
+ RnohArtifacts,
43
+ RnohCliPackage,
44
+ ToolVersion,
45
+ };