@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,345 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import JSON5 from 'json5';
5
+
6
+ import { RnohArtifacts, RnohCliPackage } from '../../config/constants';
7
+ import { HarmonyAutolinkingError } from '../../errors';
8
+ import { createManifest } from '../manifest/generate';
9
+ import { normalizeLocalOhpmSpecifier } from '../ohpm/dependencies';
10
+ import { canonicalizeOhpmManifest } from '../persistence/canonicalize';
11
+ import {
12
+ isPathInside, pathExistsAsync, realpathExistingAsync, resolveInsideAsync,
13
+ resolvePackageFromProject, stringifyJson, sortedUniqueStrings,
14
+ } from '../../utilities/values';
15
+
16
+ async function findLocalOhpmDepsAsync(content, harmony) {
17
+ let dependencies;
18
+ try {
19
+ const parsed = JSON5.parse(content);
20
+ dependencies = parsed && typeof parsed.dependencies === 'object' && !Array.isArray(parsed.dependencies)
21
+ ? parsed.dependencies
22
+ : {};
23
+ } catch (_cause) {
24
+ return [];
25
+ }
26
+
27
+ const result = [];
28
+
29
+ for (const [name, specifier] of Object.entries(dependencies)) {
30
+ const relative = normalizeLocalOhpmSpecifier(specifier);
31
+ if (!relative) continue;
32
+
33
+ const harPath = path.resolve(harmony, relative);
34
+ if (!(await pathExistsAsync(harPath))) continue;
35
+
36
+ const marker = `${path.sep}node_modules${path.sep}`;
37
+ const markerIndex = harPath.lastIndexOf(marker);
38
+ if (markerIndex < 0) continue;
39
+
40
+ const rest = harPath.slice(markerIndex + marker.length).split(path.sep);
41
+ const packageSegments = rest[0]?.startsWith('@') ? rest.slice(0, 2) : rest.slice(0, 1);
42
+ if (packageSegments.length === 0 || packageSegments.some(segment => !segment)) continue;
43
+
44
+ const packageName = packageSegments.join('/');
45
+ const packageRoot = path.join(harPath.slice(0, markerIndex + marker.length), ...packageSegments);
46
+
47
+ try {
48
+ const packageJson = JSON.parse(await fs.promises.readFile(path.join(packageRoot, 'package.json'), 'utf8'));
49
+ if (packageJson.name !== packageName) continue;
50
+ result.push({
51
+ ohPackageName: name,
52
+ packageName,
53
+ packageRoot: await fs.promises.realpath(packageRoot),
54
+ });
55
+ } catch (_cause) {
56
+ // RNOH would not be able to preserve an unreadable package reference either.
57
+ }
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ async function findNodeModulesAsync(projectRoot) {
64
+ let directory = projectRoot;
65
+
66
+ while (true) {
67
+ const candidate = path.join(directory, 'node_modules');
68
+ if (await pathExistsAsync(candidate)) return candidate;
69
+ const parent = path.dirname(directory);
70
+ if (parent === directory) return path.join(projectRoot, 'node_modules');
71
+ directory = parent;
72
+ }
73
+ }
74
+
75
+ function buildRnohMetadata(descriptor) {
76
+ return {
77
+ ...(descriptor.rnoh.ohPackageName === undefined
78
+ ? {}
79
+ : {
80
+ ohPackageName: Array.isArray(descriptor.rnoh.ohPackageName)
81
+ ? descriptor.rnoh.ohPackageName.map(mapping => ({ ...mapping }))
82
+ : descriptor.rnoh.ohPackageName,
83
+ }),
84
+ mainHarPath: descriptor.rnoh.mainHarPath,
85
+ ...(descriptor.rnoh.etsPackageClassName
86
+ ? { etsPackageClassName: descriptor.rnoh.etsPackageClassName }
87
+ : {}),
88
+ ...(descriptor.rnoh.cppPackageClassName
89
+ ? { cppPackageClassName: descriptor.rnoh.cppPackageClassName }
90
+ : {}),
91
+ ...(descriptor.rnoh.cmakeLibraryTargetName
92
+ ? { cmakeLibraryTargetName: descriptor.rnoh.cmakeLibraryTargetName }
93
+ : {}),
94
+ };
95
+ }
96
+
97
+ async function stageCuratedPackageAsync(stage, descriptor) {
98
+ await fs.promises.mkdir(stage, { recursive: true });
99
+ await fs.promises.writeFile(path.join(stage, 'package.json'), stringifyJson({
100
+ name: descriptor.packageName,
101
+ version: descriptor.packageVersion,
102
+ ...(descriptor.rnoh.harPaths.length > 0
103
+ ? { harmony: { autolinking: buildRnohMetadata(descriptor) } }
104
+ : {}),
105
+ }));
106
+
107
+ const harPaths = sortedUniqueStrings([
108
+ ...descriptor.rnoh.harPaths,
109
+ ...(descriptor.arkTs ? [descriptor.arkTs.harPath] : []),
110
+ ]);
111
+
112
+ for (const harPath of harPaths) {
113
+ const source = await resolveInsideAsync(descriptor.packageRoot, harPath, 'descriptor HAR path', { packageName: descriptor.packageName, type: 'file' });
114
+ const destination = path.resolve(stage, ...harPath.split('/'));
115
+ if (!isPathInside(stage, destination)) {
116
+ throw new HarmonyAutolinkingError('PATH_OUTSIDE_PACKAGE', 'A curated HAR path escapes its staging package.', {
117
+ packageName: descriptor.packageName,
118
+ stage: 'staging',
119
+ });
120
+ }
121
+
122
+ await fs.promises.mkdir(path.dirname(destination), { recursive: true });
123
+ await fs.promises.copyFile(source, destination, fs.constants.COPYFILE_FICLONE);
124
+ }
125
+ }
126
+
127
+ async function updateOhpmManifestAsync(manifestPath, descriptors, options) {
128
+ let source;
129
+ try {
130
+ source = await fs.promises.readFile(manifestPath, 'utf8');
131
+ } catch (cause) {
132
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'Unable to read harmony/oh-package.json5.', {
133
+ cause,
134
+ stage: 'staging',
135
+ });
136
+ }
137
+ const manifest = createManifest(descriptors, { buildType: options.buildType });
138
+ const canonical = canonicalizeOhpmManifest(source, manifest, {
139
+ previousManagedOhpmPackageNames: options.previousManagedOhpmPackageNames,
140
+ harmonyProjectPath: options.harmonyProjectPath,
141
+ nodeModulesPath: options.nodeModulesPath,
142
+ stage: 'staging',
143
+ });
144
+
145
+ await fs.promises.writeFile(manifestPath, canonical.source);
146
+ }
147
+
148
+ async function stageProjectAsync(options) {
149
+ const projectRoot = await realpathExistingAsync(options.projectRoot, {
150
+ type: 'directory',
151
+ field: 'projectRoot',
152
+ stage: 'staging',
153
+ });
154
+
155
+ const harmony = await realpathExistingAsync(options.harmonyProjectPath, {
156
+ type: 'directory',
157
+ field: 'harmonyProjectPath',
158
+ stage: 'staging',
159
+ });
160
+
161
+ if (!isPathInside(projectRoot, harmony)) {
162
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'harmonyProjectPath must be inside projectRoot.', { stage: 'staging' });
163
+ }
164
+
165
+ const requested = options.nodeModulesPath || await findNodeModulesAsync(projectRoot);
166
+ const source = await realpathExistingAsync(requested, {
167
+ type: 'directory',
168
+ field: 'nodeModulesPath',
169
+ stage: 'staging',
170
+ });
171
+
172
+ const tmp = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'expo-harmony-autolinking-'));
173
+ try {
174
+ const stageRoot = path.join(tmp, 'project');
175
+ const relative = path.relative(projectRoot, harmony);
176
+ const stageHarmony = path.join(stageRoot, relative);
177
+ const modules = path.join(stageRoot, 'node_modules');
178
+ await fs.promises.mkdir(stageHarmony, { recursive: true });
179
+ await fs.promises.writeFile(path.join(stageRoot, 'package.json'), stringifyJson({
180
+ name: 'expo-harmony-autolinking-stage',
181
+ private: true,
182
+ }));
183
+
184
+ let plugin = `${RnohCliPackage}/react-native.config.js`;
185
+ try {
186
+ const pkg = options.rnohCliPackageJsonPath
187
+ ? await fs.promises.realpath(options.rnohCliPackageJsonPath)
188
+ : resolvePackageFromProject(projectRoot, `${RnohCliPackage}/package.json`);
189
+ plugin = await fs.promises.realpath(path.join(path.dirname(pkg), 'react-native.config.js'));
190
+ } catch (_cause) {
191
+ // Preflight emits the actionable package/CLI diagnostic. Keeping the public
192
+ // package request here also lets callers inspect a stage without installing RNOH.
193
+ }
194
+
195
+ await fs.promises.writeFile(
196
+ path.join(stageRoot, 'react-native.config.js'),
197
+ `module.exports = require(${JSON.stringify(plugin)});\n`
198
+ );
199
+
200
+ const runtime = [];
201
+ if (options.modules === undefined) {
202
+ await fs.promises.symlink(source, modules, process.platform === 'win32' ? 'junction' : 'dir');
203
+ } else {
204
+ await fs.promises.mkdir(modules, { recursive: true });
205
+ const staged = options.modules.filter(descriptor => (
206
+ descriptor.rnoh.harPaths.length > 0 || descriptor.arkTs
207
+ ));
208
+
209
+ for (const descriptor of staged) {
210
+ const segments = descriptor.packageName.split('/');
211
+ const validName = segments.length === 1
212
+ ? segments[0] && !segments[0].startsWith('@')
213
+ : segments.length === 2 && segments[0].startsWith('@') && segments[0].length > 1 && segments[1];
214
+ if (!validName || segments.some(segment => segment === '.' || segment === '..' || segment.includes('\\'))) {
215
+ throw new HarmonyAutolinkingError('INVALID_METADATA', `Invalid npm package name for curated staging: ${descriptor.packageName}`, {
216
+ packageName: descriptor.packageName,
217
+ stage: 'staging',
218
+ });
219
+ }
220
+ const stage = path.join(modules, ...segments);
221
+ await fs.promises.mkdir(path.dirname(stage), { recursive: true });
222
+ await stageCuratedPackageAsync(stage, descriptor);
223
+ }
224
+
225
+ const actual = path.join(harmony, RnohArtifacts.ohPackage);
226
+ if (await pathExistsAsync(actual)) {
227
+ const content = await fs.promises.readFile(actual, 'utf8');
228
+ const previous = new Set(options.previousManagedOhpmPackageNames || []);
229
+ const verified = new Set(staged.map(item => item.packageName));
230
+
231
+ for (const dependency of await findLocalOhpmDepsAsync(content, harmony)) {
232
+ if (previous.has(dependency.ohPackageName)) continue;
233
+ if (verified.has(dependency.packageName)) continue;
234
+ const target = path.join(modules, ...dependency.packageName.split('/'));
235
+ if (!(await pathExistsAsync(target))) {
236
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
237
+ await fs.promises.symlink(
238
+ dependency.packageRoot,
239
+ target,
240
+ process.platform === 'win32' ? 'junction' : 'dir'
241
+ );
242
+ }
243
+ runtime.push(dependency.packageName);
244
+ }
245
+ }
246
+
247
+ try {
248
+ const packageName = '@react-native-community/cli';
249
+ const pkg = resolvePackageFromProject(projectRoot, `${packageName}/package.json`);
250
+ const packageRoot = await fs.promises.realpath(path.dirname(pkg));
251
+ const target = path.join(modules, ...packageName.split('/'));
252
+ if (!(await pathExistsAsync(target))) {
253
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
254
+ await fs.promises.symlink(packageRoot, target, process.platform === 'win32' ? 'junction' : 'dir');
255
+ }
256
+ runtime.push(packageName);
257
+ } catch (_cause) {
258
+ // Explicit/fake executables need not install the React Native wrapper runtime.
259
+ }
260
+ }
261
+
262
+ for (const relativePath of Object.values(RnohArtifacts)) {
263
+ await fs.promises.mkdir(path.dirname(path.join(stageHarmony, relativePath)), { recursive: true });
264
+ }
265
+
266
+ const actual = path.join(harmony, RnohArtifacts.ohPackage);
267
+ const stage = path.join(stageHarmony, RnohArtifacts.ohPackage);
268
+ if (await pathExistsAsync(actual)) {
269
+ const stat = await fs.promises.lstat(actual);
270
+ if (!stat.isFile() || stat.isSymbolicLink()) {
271
+ throw new HarmonyAutolinkingError('INVALID_OPTIONS', 'harmony/oh-package.json5 must be a regular file.', { stage: 'staging' });
272
+ }
273
+ await fs.promises.copyFile(actual, stage);
274
+ } else {
275
+ await fs.promises.writeFile(stage, '{\n dependencies: {}\n}\n');
276
+ }
277
+
278
+ if (options.modules !== undefined) {
279
+ await updateOhpmManifestAsync(stage, options.modules, {
280
+ buildType: options.buildType,
281
+ harmonyProjectPath: stageHarmony,
282
+ nodeModulesPath: modules,
283
+ previousManagedOhpmPackageNames: options.previousManagedOhpmPackageNames,
284
+ });
285
+ }
286
+
287
+ return {
288
+ projectRoot,
289
+ harmonyProjectPath: harmony,
290
+ sourceNodeModulesPath: source,
291
+ nodeModulesPath: modules,
292
+ rnohRuntimePackages: runtime,
293
+ temporaryRoot: tmp,
294
+ stageProjectRoot: stageRoot,
295
+ stageHarmonyProjectPath: stageHarmony,
296
+ };
297
+ } catch (cause) {
298
+ let warning;
299
+ try {
300
+ await fs.promises.rm(tmp, { recursive: true, force: true });
301
+ } catch (cleanupCause) {
302
+ warning = {
303
+ severity: 'warning',
304
+ code: 'ERR_EXPO_HARMONY_STAGING_CLEANUP_FAILED',
305
+ message: 'Unable to remove a partially-created Harmony autolinking staging directory.',
306
+ stage: 'cleanup',
307
+ details: { message: cleanupCause.message },
308
+ };
309
+ }
310
+
311
+ throw new HarmonyAutolinkingError(
312
+ typeof cause.code === 'string' ? cause.code : 'STAGING_FAILED',
313
+ cause.message || 'Unable to stage the Harmony project.',
314
+ {
315
+ cause,
316
+ stage: cause.stage || 'staging',
317
+ packageName: cause.packageName,
318
+ diagnostics: cause.diagnostics,
319
+ details: warning
320
+ ? { ...(cause.details || {}), cleanupWarning: warning }
321
+ : cause.details,
322
+ }
323
+ );
324
+ }
325
+ }
326
+
327
+ async function cleanupStagingProjectAsync(staging) {
328
+ try {
329
+ await fs.promises.rm(staging.temporaryRoot, { recursive: true, force: true });
330
+ return null;
331
+ } catch (cause) {
332
+ return {
333
+ severity: 'warning',
334
+ code: 'ERR_EXPO_HARMONY_STAGING_CLEANUP_FAILED',
335
+ message: 'Unable to remove the Harmony autolinking staging directory.',
336
+ stage: 'cleanup',
337
+ cause,
338
+ };
339
+ }
340
+ }
341
+
342
+ export {
343
+ cleanupStagingProjectAsync,
344
+ stageProjectAsync,
345
+ };
@@ -0,0 +1,324 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ import JSON5 from 'json5';
5
+
6
+ import { HarmonyAutolinkingError } from '../errors';
7
+ import type { BuildType, RnohMetadata } from '../types';
8
+ import {
9
+ isValidOhpmPackageName,
10
+ normalizeHarmonyModuleMetadata,
11
+ normalizeRnohPackageMetadata,
12
+ } from './schema';
13
+ import { materializeModuleArtifactAsync } from '../harmony/materialize';
14
+ import {
15
+ defaultCmakeTargetForNpmPackage, defaultOhPackageNameForNpmPackage,
16
+ ohPackageNameForHar,
17
+ } from '../harmony/rnoh/packageMetadata';
18
+ import {
19
+ compareText, isPathInside, isObject, normalizeSlashes, requireNonEmptyString,
20
+ resolveInsideAsync, sortedUniqueStrings,
21
+ } from '../utilities/values';
22
+
23
+ const HarScanIgnoredDirectories = new Set(['.git', 'build', 'node_modules', 'oh_modules']);
24
+ const ArkTsLibraryManifestPath = 'harmony/library/oh-package.json5';
25
+ const ArkTsLibraryHarPath = 'harmony/library.har';
26
+
27
+ async function findHarPathsAsync(packageRoot, scanRoot, record): Promise<string[]> {
28
+ const harPaths: string[] = [];
29
+ const visited = new Set();
30
+
31
+ async function visit(directory) {
32
+ let real;
33
+ let entries;
34
+ try {
35
+ real = await fs.promises.realpath(directory);
36
+ entries = await fs.promises.readdir(directory, { withFileTypes: true });
37
+ } catch (cause) {
38
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'Unable to scan harmony.autolinking.mainHarPath.', {
39
+ cause,
40
+ packageName: record.packageName,
41
+ stage: 'metadata',
42
+ });
43
+ }
44
+
45
+ if (!isPathInside(packageRoot, real)) {
46
+ throw new HarmonyAutolinkingError('PATH_OUTSIDE_PACKAGE', 'harmony.autolinking.mainHarPath resolves outside package root.', { packageName: record.packageName, stage: 'metadata' });
47
+ }
48
+ if (visited.has(real)) return;
49
+ visited.add(real);
50
+
51
+ entries.sort((left, right) => compareText(left.name, right.name));
52
+ for (const entry of entries) {
53
+ const target = path.join(directory, entry.name);
54
+ let stat;
55
+ try {
56
+ stat = await fs.promises.lstat(target);
57
+ } catch (cause) {
58
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'A HAR scan entry is missing or unreadable.', {
59
+ cause,
60
+ packageName: record.packageName,
61
+ stage: 'metadata',
62
+ });
63
+ }
64
+
65
+ if (stat.isDirectory()) {
66
+ if (HarScanIgnoredDirectories.has(entry.name)) continue;
67
+ await visit(target);
68
+ } else if (path.extname(entry.name) === '.har') {
69
+ let realTarget;
70
+ try {
71
+ realTarget = await fs.promises.realpath(target);
72
+ if (!(await fs.promises.stat(realTarget)).isFile()) continue;
73
+ } catch (cause) {
74
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'A HAR scan entry is missing or unreadable.', {
75
+ cause,
76
+ packageName: record.packageName,
77
+ stage: 'metadata',
78
+ });
79
+ }
80
+
81
+ if (!isPathInside(packageRoot, realTarget)) {
82
+ throw new HarmonyAutolinkingError('PATH_OUTSIDE_PACKAGE', 'A HAR scan path resolves outside package root.', { packageName: record.packageName, stage: 'metadata' });
83
+ }
84
+
85
+ harPaths.push(normalizeSlashes(path.relative(packageRoot, target)));
86
+ }
87
+ }
88
+ }
89
+
90
+ await visit(scanRoot);
91
+
92
+ return sortedUniqueStrings(harPaths);
93
+ }
94
+
95
+ function resolveOhpmPackageName(raw, harPaths, record) {
96
+ const base = defaultOhPackageNameForNpmPackage(record.packageName);
97
+ const fallback = harPath => ({
98
+ harName: path.basename(harPath),
99
+ packageName: ohPackageNameForHar(base, harPath, harPaths.length),
100
+ });
101
+
102
+ if (raw === undefined) {
103
+ return harPaths.length === 1 ? base : harPaths.map(fallback);
104
+ }
105
+
106
+ if (typeof raw === 'string') {
107
+ return harPaths.length === 1
108
+ ? raw
109
+ : harPaths.map(harPath => ({
110
+ harName: path.basename(harPath),
111
+ packageName: ohPackageNameForHar(raw, harPath, harPaths.length),
112
+ }));
113
+ }
114
+
115
+ const harNames = new Set(harPaths.map(harPath => path.basename(harPath)));
116
+ const mappings = raw.map((mapping) => {
117
+ if (!harNames.has(mapping.harName)) {
118
+ throw new HarmonyAutolinkingError('INVALID_METADATA', `package.json#harmony.autolinking.ohPackageName mapping ${mapping.harName} does not match a discovered HAR.`, {
119
+ packageName: record.packageName,
120
+ stage: 'metadata',
121
+ });
122
+ }
123
+ return mapping;
124
+ });
125
+ const mapped = new Set(mappings.map(mapping => mapping.harName));
126
+ for (const harPath of harPaths) {
127
+ if (!mapped.has(path.basename(harPath))) mappings.push(fallback(harPath));
128
+ }
129
+
130
+ return mappings;
131
+ }
132
+
133
+ async function resolveArkTsModulePackageAsync(record, harmony) {
134
+ const hasHostExtensions = harmony.rootViewComponents.length > 0;
135
+ if (harmony.modules.length === 0 && !hasHostExtensions) return undefined;
136
+
137
+ const manifestPath = await resolveInsideAsync(
138
+ record.packageRoot,
139
+ ArkTsLibraryManifestPath,
140
+ 'Harmony library OHPM manifest',
141
+ { packageName: record.packageName, type: 'file' }
142
+ );
143
+ let manifest;
144
+ try {
145
+ manifest = JSON5.parse(await fs.promises.readFile(manifestPath, 'utf8'));
146
+ } catch (cause) {
147
+ throw new HarmonyAutolinkingError(
148
+ 'INVALID_METADATA',
149
+ `${ArkTsLibraryManifestPath} must contain valid JSON5.`,
150
+ { cause, packageName: record.packageName, stage: 'metadata' }
151
+ );
152
+ }
153
+ if (!isObject(manifest) || !isValidOhpmPackageName(manifest.name)) {
154
+ throw new HarmonyAutolinkingError(
155
+ 'INVALID_METADATA',
156
+ `${ArkTsLibraryManifestPath} must declare a valid OHPM package name.`,
157
+ { packageName: record.packageName, stage: 'metadata' }
158
+ );
159
+ }
160
+ return {
161
+ harPath: ArkTsLibraryHarPath,
162
+ ohPackageName: manifest.name,
163
+ };
164
+ }
165
+
166
+ function normalizeExpoMetadata(record, canonical) {
167
+ if (!record.supportsHarmony) return {
168
+ rootViewComponents: [],
169
+ };
170
+
171
+ return {
172
+ rootViewComponents: canonical.rootViewComponents,
173
+ };
174
+ }
175
+
176
+ async function normalizeRnohMetadataAsync(record) {
177
+ if (record.rnohMetadata == null) {
178
+ return {
179
+ ohPackageName: undefined,
180
+ mainHarPath: undefined,
181
+ harPaths: [],
182
+ etsPackageClassName: undefined,
183
+ cppPackageClassName: undefined,
184
+ cmakeLibraryTargetName: undefined,
185
+ };
186
+ }
187
+
188
+ const metadata = normalizeRnohPackageMetadata(record.rnohMetadata, {
189
+ packageName: record.packageName,
190
+ packageVersion: record.packageVersion,
191
+ });
192
+
193
+ let main;
194
+ if (metadata.mainHarPath !== undefined) {
195
+ main = metadata.mainHarPath;
196
+ } else {
197
+ try {
198
+ const harmonyStat = await fs.promises.stat(path.join(record.packageRoot, 'harmony'));
199
+ main = harmonyStat.isDirectory() ? 'harmony' : '.';
200
+ } catch (_cause) {
201
+ main = '.';
202
+ }
203
+ }
204
+
205
+ const scanRoot = await resolveInsideAsync(
206
+ record.packageRoot,
207
+ main,
208
+ 'harmony.autolinking.mainHarPath',
209
+ { packageName: record.packageName, type: 'directory' }
210
+ );
211
+ main = normalizeSlashes(path.relative(record.packageRoot, scanRoot)) || '.';
212
+
213
+ const harPaths = await findHarPathsAsync(record.packageRoot, scanRoot, record);
214
+ if (harPaths.length === 0) {
215
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'harmony.autolinking.mainHarPath does not contain a HAR file.', {
216
+ packageName: record.packageName,
217
+ stage: 'metadata',
218
+ });
219
+ }
220
+
221
+ const harBasenames = harPaths.map(harPath => path.basename(harPath));
222
+ if (new Set(harBasenames).size !== harBasenames.length) {
223
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'harmony.autolinking.mainHarPath contains duplicate HAR filenames.', {
224
+ packageName: record.packageName,
225
+ stage: 'metadata',
226
+ });
227
+ }
228
+
229
+ const name = resolveOhpmPackageName(metadata.ohPackageName, harPaths, record);
230
+ if (Array.isArray(name)) {
231
+ const order = new Map(name.map((mapping, index) => [mapping.harName, index]));
232
+ harPaths.sort((left, right) => {
233
+ return order.get(path.basename(left)) - order.get(path.basename(right));
234
+ });
235
+ }
236
+
237
+ const rnoh: RnohMetadata = {
238
+ ohPackageName: name,
239
+ mainHarPath: main,
240
+ harPaths,
241
+ etsPackageClassName: metadata.etsPackageClassName,
242
+ etsPackageImport: metadata.etsPackageImport,
243
+ cppPackageClassName: metadata.cppPackageClassName,
244
+ cmakeLibraryTargetName: metadata.cmakeLibraryTargetName
245
+ || defaultCmakeTargetForNpmPackage(record.packageName),
246
+ };
247
+
248
+ return rnoh;
249
+ }
250
+
251
+ function assertSearchRecord(record) {
252
+ if (!isObject(record)) {
253
+ throw new HarmonyAutolinkingError('INVALID_METADATA', 'Search record must be an object.', {
254
+ stage: 'metadata',
255
+ });
256
+ }
257
+
258
+ for (const field of ['packageName', 'packageVersion', 'packageRoot', 'source']) {
259
+ requireNonEmptyString(record[field], field, { packageName: record.packageName });
260
+ }
261
+ }
262
+
263
+ async function createDescriptorFromSearchRecordAsync(record, buildType: BuildType = 'debug') {
264
+ assertSearchRecord(record);
265
+
266
+ let packageRoot;
267
+ try {
268
+ packageRoot = await fs.promises.realpath(record.packageRoot);
269
+ if (!(await fs.promises.stat(packageRoot)).isDirectory()) throw new TypeError('not a directory');
270
+ } catch (cause) {
271
+ throw new HarmonyAutolinkingError('INVALID_METADATA', `${record.packageName} packageRoot is not an existing directory.`, {
272
+ cause,
273
+ packageName: record.packageName,
274
+ stage: 'metadata',
275
+ });
276
+ }
277
+
278
+ const base = { ...record, packageRoot };
279
+ let link = packageRoot;
280
+ if (typeof record.revision?.originPath === 'string') {
281
+ const candidate = path.resolve(record.revision.originPath);
282
+ try {
283
+ if (await fs.promises.realpath(candidate) === packageRoot) link = candidate;
284
+ } catch (_cause) {
285
+ // The canonical package root remains the safe fallback for direct callers.
286
+ }
287
+ }
288
+
289
+ const expoConfig = isObject(base.expoModuleConfig) ? base.expoModuleConfig : null;
290
+ const harmony = normalizeHarmonyModuleMetadata(
291
+ expoConfig?.harmony,
292
+ { packageName: record.packageName, packageVersion: record.packageVersion }
293
+ );
294
+ const arkTs = await resolveArkTsModulePackageAsync(base, harmony);
295
+ const rnoh = await normalizeRnohMetadataAsync(base);
296
+ const expo = normalizeExpoMetadata(base, harmony);
297
+ const artifact = await materializeModuleArtifactAsync({
298
+ packageName: record.packageName,
299
+ packageRoot,
300
+ source: record.source,
301
+ buildType,
302
+ arkTs,
303
+ rnoh,
304
+ });
305
+
306
+ const descriptor = {
307
+ packageName: record.packageName,
308
+ packageVersion: record.packageVersion,
309
+ packageRoot,
310
+ packageLinkPath: link,
311
+ source: record.source,
312
+ expo,
313
+ rnoh,
314
+ harmony,
315
+ ...(arkTs ? { arkTs } : {}),
316
+ artifact,
317
+ };
318
+
319
+ return descriptor;
320
+ }
321
+
322
+ export {
323
+ createDescriptorFromSearchRecordAsync,
324
+ };