@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.
- package/README.md +12 -0
- package/bin/expo-harmony-autolinking.ts +14 -0
- package/build/bin/expo-harmony-autolinking.d.ts +2 -0
- package/build/bin/expo-harmony-autolinking.js +11 -0
- package/build/index.d.ts +8 -0
- package/build/index.js +15 -0
- package/build/src/autolinking/link.d.ts +3 -0
- package/build/src/autolinking/link.js +150 -0
- package/build/src/autolinking/resolve.d.ts +4 -0
- package/build/src/autolinking/resolve.js +25 -0
- package/build/src/autolinking/search.d.ts +3 -0
- package/build/src/autolinking/search.js +357 -0
- package/build/src/autolinking/verify.d.ts +4 -0
- package/build/src/autolinking/verify.js +366 -0
- package/build/src/commands/autolinkingOptions.d.ts +12 -0
- package/build/src/commands/autolinkingOptions.js +35 -0
- package/build/src/commands/cli.d.ts +2 -0
- package/build/src/commands/cli.js +50 -0
- package/build/src/commands/link.d.ts +2 -0
- package/build/src/commands/link.js +27 -0
- package/build/src/commands/output.d.ts +3 -0
- package/build/src/commands/output.js +33 -0
- package/build/src/commands/resolve.d.ts +2 -0
- package/build/src/commands/resolve.js +13 -0
- package/build/src/commands/search.d.ts +2 -0
- package/build/src/commands/search.js +13 -0
- package/build/src/commands/verify.d.ts +2 -0
- package/build/src/commands/verify.js +17 -0
- package/build/src/config/constants.d.ts +17 -0
- package/build/src/config/constants.js +40 -0
- package/build/src/config/options.d.ts +13 -0
- package/build/src/config/options.js +202 -0
- package/build/src/errors.d.ts +18 -0
- package/build/src/errors.js +35 -0
- package/build/src/harmony/manifest/generate.d.ts +4 -0
- package/build/src/harmony/manifest/generate.js +55 -0
- package/build/src/harmony/manifest/index.d.ts +7 -0
- package/build/src/harmony/manifest/index.js +64 -0
- package/build/src/harmony/materialize.d.ts +12 -0
- package/build/src/harmony/materialize.js +278 -0
- package/build/src/harmony/ohpm/dependencies.d.ts +7 -0
- package/build/src/harmony/ohpm/dependencies.js +52 -0
- package/build/src/harmony/persistence/canonicalize.d.ts +15 -0
- package/build/src/harmony/persistence/canonicalize.js +108 -0
- package/build/src/harmony/providers/host.d.ts +3 -0
- package/build/src/harmony/providers/host.js +88 -0
- package/build/src/harmony/rnoh/cli.d.ts +2 -0
- package/build/src/harmony/rnoh/cli.js +55 -0
- package/build/src/harmony/rnoh/link.d.ts +2 -0
- package/build/src/harmony/rnoh/link.js +419 -0
- package/build/src/harmony/rnoh/packageMetadata.d.ts +12 -0
- package/build/src/harmony/rnoh/packageMetadata.js +125 -0
- package/build/src/harmony/transaction/previousState.d.ts +15 -0
- package/build/src/harmony/transaction/previousState.js +96 -0
- package/build/src/harmony/transaction/publish.d.ts +6 -0
- package/build/src/harmony/transaction/publish.js +272 -0
- package/build/src/harmony/transaction/stageProject.d.ts +18 -0
- package/build/src/harmony/transaction/stageProject.js +313 -0
- package/build/src/metadata/descriptor.d.ts +19 -0
- package/build/src/metadata/descriptor.js +306 -0
- package/build/src/metadata/host.d.ts +5 -0
- package/build/src/metadata/host.js +37 -0
- package/build/src/metadata/schema.d.ts +10 -0
- package/build/src/metadata/schema.js +171 -0
- package/build/src/types.d.ts +194 -0
- package/build/src/types.js +2 -0
- package/build/src/utilities/values.d.ts +17 -0
- package/build/src/utilities/values.js +205 -0
- package/harmony-module.schema.json +28 -0
- package/index.ts +41 -0
- package/package.json +66 -0
- package/src/autolinking/link.ts +170 -0
- package/src/autolinking/resolve.ts +33 -0
- package/src/autolinking/search.ts +369 -0
- package/src/autolinking/verify.ts +415 -0
- package/src/commands/autolinkingOptions.ts +38 -0
- package/src/commands/cli.ts +56 -0
- package/src/commands/link.ts +28 -0
- package/src/commands/output.ts +36 -0
- package/src/commands/resolve.ts +14 -0
- package/src/commands/search.ts +14 -0
- package/src/commands/verify.ts +17 -0
- package/src/config/constants.ts +45 -0
- package/src/config/options.ts +195 -0
- package/src/errors.ts +48 -0
- package/src/harmony/manifest/generate.ts +65 -0
- package/src/harmony/manifest/index.ts +81 -0
- package/src/harmony/materialize.ts +382 -0
- package/src/harmony/ohpm/dependencies.ts +59 -0
- package/src/harmony/persistence/canonicalize.ts +156 -0
- package/src/harmony/providers/host.ts +147 -0
- package/src/harmony/rnoh/cli.ts +59 -0
- package/src/harmony/rnoh/link.ts +481 -0
- package/src/harmony/rnoh/packageMetadata.ts +105 -0
- package/src/harmony/transaction/previousState.ts +71 -0
- package/src/harmony/transaction/publish.ts +287 -0
- package/src/harmony/transaction/stageProject.ts +345 -0
- package/src/metadata/descriptor.ts +324 -0
- package/src/metadata/host.ts +41 -0
- package/src/metadata/schema.ts +184 -0
- package/src/types.ts +220 -0
- package/src/utilities/values.ts +190 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
import { HarmonyAutolinkingError } from '../errors';
|
|
6
|
+
import type {
|
|
7
|
+
ArkTsModulePackage,
|
|
8
|
+
BuildType,
|
|
9
|
+
FixedHvigorBuildDescriptor,
|
|
10
|
+
MaterializedLocalSource,
|
|
11
|
+
MaterializeLocalSourceOptions,
|
|
12
|
+
ModuleArtifactDescriptor,
|
|
13
|
+
ModuleSource,
|
|
14
|
+
RnohMetadata,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import { compareText, isPathInside, normalizeSlashes, resolveInsideAsync, sanitizeOutput } from '../utilities/values';
|
|
17
|
+
import { publishArtifactsAsync } from './transaction/publish';
|
|
18
|
+
|
|
19
|
+
const DefaultBuildTimeoutMs = 10 * 60_000;
|
|
20
|
+
const DefaultOutputLimit = 64 * 1024;
|
|
21
|
+
const HarmonyProjectPath = 'harmony';
|
|
22
|
+
const HarmonyBuildOutputPath = 'library/build/default/outputs/default/library.har';
|
|
23
|
+
|
|
24
|
+
interface FixedBuildStep {
|
|
25
|
+
readonly id: 'ohpm-install' | 'hvigor-build';
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly executable: string;
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
readonly args: ReadonlyArray<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface MaterializeModuleArtifactOptions {
|
|
33
|
+
readonly packageName: string;
|
|
34
|
+
readonly packageRoot: string;
|
|
35
|
+
readonly source: ModuleSource;
|
|
36
|
+
readonly buildType: BuildType;
|
|
37
|
+
readonly arkTs?: ArkTsModulePackage;
|
|
38
|
+
readonly rnoh: RnohMetadata;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function materializationError(
|
|
42
|
+
code: string,
|
|
43
|
+
message: string,
|
|
44
|
+
options: MaterializeModuleArtifactOptions,
|
|
45
|
+
details?: unknown
|
|
46
|
+
): never {
|
|
47
|
+
throw new HarmonyAutolinkingError(code, message, {
|
|
48
|
+
packageName: options.packageName,
|
|
49
|
+
stage: 'artifact-materialize',
|
|
50
|
+
details,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createFixedHvigorBuildDescriptor(
|
|
55
|
+
projectPath: string,
|
|
56
|
+
buildType: BuildType
|
|
57
|
+
): FixedHvigorBuildDescriptor {
|
|
58
|
+
return {
|
|
59
|
+
executable: 'hvigorw',
|
|
60
|
+
cwd: projectPath,
|
|
61
|
+
args: [
|
|
62
|
+
'--mode', 'module',
|
|
63
|
+
'-p', 'module=library@default',
|
|
64
|
+
'-p', 'product=default',
|
|
65
|
+
'-p', `buildMode=${buildType}`,
|
|
66
|
+
'--no-daemon',
|
|
67
|
+
'assembleHar',
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function isNonEmptyRegularHarAsync(
|
|
73
|
+
harPath: string,
|
|
74
|
+
allowedRoot: string,
|
|
75
|
+
packageName: string
|
|
76
|
+
): Promise<boolean> {
|
|
77
|
+
const root = path.resolve(allowedRoot);
|
|
78
|
+
const target = path.resolve(harPath);
|
|
79
|
+
if (!isPathInside(root, target)) {
|
|
80
|
+
throw new HarmonyAutolinkingError(
|
|
81
|
+
'UNSAFE_SOURCE_ARTIFACT',
|
|
82
|
+
'Harmony HAR path must stay inside its expected package or project root.',
|
|
83
|
+
{ packageName, stage: 'artifact-materialize' }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let stat;
|
|
88
|
+
try {
|
|
89
|
+
stat = await fs.promises.lstat(target);
|
|
90
|
+
} catch (cause) {
|
|
91
|
+
if ((cause as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
|
92
|
+
throw new HarmonyAutolinkingError(
|
|
93
|
+
'UNSAFE_SOURCE_ARTIFACT',
|
|
94
|
+
'Harmony HAR could not be checked safely.',
|
|
95
|
+
{ cause, packageName, stage: 'artifact-materialize' }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
99
|
+
throw new HarmonyAutolinkingError(
|
|
100
|
+
'UNSAFE_SOURCE_ARTIFACT',
|
|
101
|
+
'Harmony HAR must be a regular non-symlink file.',
|
|
102
|
+
{ packageName, stage: 'artifact-materialize' }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const [realRoot, realTarget] = await Promise.all([
|
|
107
|
+
fs.promises.realpath(root),
|
|
108
|
+
fs.promises.realpath(target),
|
|
109
|
+
]);
|
|
110
|
+
if (!isPathInside(realRoot, realTarget)) {
|
|
111
|
+
throw new HarmonyAutolinkingError(
|
|
112
|
+
'UNSAFE_SOURCE_ARTIFACT',
|
|
113
|
+
'Harmony HAR resolves outside its expected package or project root.',
|
|
114
|
+
{ packageName, stage: 'artifact-materialize' }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return stat.size > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function validateBundledArtifactsAsync(
|
|
121
|
+
options: MaterializeModuleArtifactOptions
|
|
122
|
+
): Promise<ModuleArtifactDescriptor> {
|
|
123
|
+
const harPaths = [...new Set([
|
|
124
|
+
...options.rnoh.harPaths,
|
|
125
|
+
...(options.arkTs ? [options.arkTs.harPath] : []),
|
|
126
|
+
])].sort(compareText);
|
|
127
|
+
for (const relative of harPaths) {
|
|
128
|
+
const harPath = path.resolve(options.packageRoot, relative);
|
|
129
|
+
if (!await isNonEmptyRegularHarAsync(harPath, options.packageRoot, options.packageName)) {
|
|
130
|
+
materializationError(
|
|
131
|
+
'SOURCE_ARTIFACT_NOT_PUBLISHED',
|
|
132
|
+
`${options.packageName} has a missing or empty Harmony HAR.`,
|
|
133
|
+
options,
|
|
134
|
+
{ harPath: relative }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { kind: 'bundled', harPaths };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function materializeModuleArtifactAsync(
|
|
142
|
+
options: MaterializeModuleArtifactOptions
|
|
143
|
+
): Promise<ModuleArtifactDescriptor> {
|
|
144
|
+
const localArkTs = options.source === 'nativeModulesDir' && options.arkTs;
|
|
145
|
+
if (!localArkTs && (
|
|
146
|
+
options.rnoh.harPaths.length > 0
|
|
147
|
+
|| options.arkTs
|
|
148
|
+
)) {
|
|
149
|
+
return validateBundledArtifactsAsync(options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!localArkTs) {
|
|
153
|
+
return { kind: 'bundled', harPaths: [] };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const packageRoot = await fs.promises.realpath(options.packageRoot);
|
|
157
|
+
const sourceRoot = await resolveInsideAsync(packageRoot, HarmonyProjectPath, 'Harmony source project', {
|
|
158
|
+
packageName: options.packageName,
|
|
159
|
+
type: 'directory',
|
|
160
|
+
});
|
|
161
|
+
const outputPath = await resolveInsideAsync(sourceRoot, HarmonyBuildOutputPath, 'Harmony build output', {
|
|
162
|
+
packageName: options.packageName,
|
|
163
|
+
mustExist: false,
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
kind: 'local-source',
|
|
167
|
+
outputPath,
|
|
168
|
+
materialized: false,
|
|
169
|
+
build: createFixedHvigorBuildDescriptor(sourceRoot, options.buildType),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function appendBounded(current: string, chunk: Buffer, limit: number): string {
|
|
174
|
+
if (current.length >= limit) return current;
|
|
175
|
+
return current + chunk.toString('utf8').slice(0, limit - current.length);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function fixedLocalSourceBuildSteps(build: FixedHvigorBuildDescriptor): ReadonlyArray<FixedBuildStep> {
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
id: 'ohpm-install',
|
|
182
|
+
label: 'OHPM dependency install',
|
|
183
|
+
executable: 'ohpm',
|
|
184
|
+
cwd: build.cwd,
|
|
185
|
+
args: ['install', '--all'],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'hvigor-build',
|
|
189
|
+
label: 'Hvigor build',
|
|
190
|
+
...build,
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function resolveFixedBuildStep(step: FixedBuildStep, env: NodeJS.ProcessEnv): FixedBuildStep {
|
|
196
|
+
if (step.id === 'ohpm-install') {
|
|
197
|
+
return env.HARMONY_OHPM ? { ...step, executable: env.HARMONY_OHPM } : step;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const configured = env.HARMONY_HVIGORW;
|
|
201
|
+
if (configured) {
|
|
202
|
+
return /\.(?:c|m)?js$/iu.test(configured)
|
|
203
|
+
? { ...step, executable: env.HARMONY_NODE || process.execPath, args: [configured, ...step.args] }
|
|
204
|
+
: { ...step, executable: configured };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return step;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runFixedBuildStepAsync(
|
|
211
|
+
step: FixedBuildStep,
|
|
212
|
+
packageName: string,
|
|
213
|
+
options: Pick<MaterializeLocalSourceOptions, 'env' | 'outputLimit' | 'timeoutMs'>
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
const timeoutMs = options.timeoutMs ?? DefaultBuildTimeoutMs;
|
|
216
|
+
const outputLimit = options.outputLimit ?? DefaultOutputLimit;
|
|
217
|
+
const stage = `artifact-materialize:${step.id}`;
|
|
218
|
+
const env = { ...process.env, ...options.env };
|
|
219
|
+
const resolvedStep = resolveFixedBuildStep(step, env);
|
|
220
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0
|
|
221
|
+
|| !Number.isInteger(outputLimit) || outputLimit <= 0) {
|
|
222
|
+
throw new HarmonyAutolinkingError(
|
|
223
|
+
'INVALID_OPTIONS',
|
|
224
|
+
'timeoutMs and outputLimit must be positive integers.',
|
|
225
|
+
{ packageName, stage }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = await new Promise<{
|
|
230
|
+
code: number | null;
|
|
231
|
+
signal: NodeJS.Signals | null;
|
|
232
|
+
output: string;
|
|
233
|
+
timedOut: boolean;
|
|
234
|
+
}>((resolve, reject) => {
|
|
235
|
+
let output = '';
|
|
236
|
+
let timedOut = false;
|
|
237
|
+
let settled = false;
|
|
238
|
+
let killTimer: NodeJS.Timeout | undefined;
|
|
239
|
+
let child;
|
|
240
|
+
try {
|
|
241
|
+
child = spawn(resolvedStep.executable, [...resolvedStep.args], {
|
|
242
|
+
cwd: step.cwd,
|
|
243
|
+
env,
|
|
244
|
+
shell: false,
|
|
245
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
246
|
+
});
|
|
247
|
+
} catch (cause) {
|
|
248
|
+
reject(cause);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
252
|
+
output = appendBounded(output, chunk, outputLimit);
|
|
253
|
+
});
|
|
254
|
+
child.stderr.on('data', (chunk: Buffer) => {
|
|
255
|
+
output = appendBounded(output, chunk, outputLimit);
|
|
256
|
+
});
|
|
257
|
+
const timeout = setTimeout(() => {
|
|
258
|
+
timedOut = true;
|
|
259
|
+
child.kill('SIGTERM');
|
|
260
|
+
killTimer = setTimeout(() => child.kill('SIGKILL'), 1_000);
|
|
261
|
+
killTimer.unref?.();
|
|
262
|
+
}, timeoutMs);
|
|
263
|
+
timeout.unref?.();
|
|
264
|
+
child.on('error', (cause) => {
|
|
265
|
+
if (settled) return;
|
|
266
|
+
settled = true;
|
|
267
|
+
clearTimeout(timeout);
|
|
268
|
+
if (killTimer) clearTimeout(killTimer);
|
|
269
|
+
reject(cause);
|
|
270
|
+
});
|
|
271
|
+
child.on('close', (code, signal) => {
|
|
272
|
+
if (settled) return;
|
|
273
|
+
settled = true;
|
|
274
|
+
clearTimeout(timeout);
|
|
275
|
+
if (killTimer) clearTimeout(killTimer);
|
|
276
|
+
resolve({ code, signal, output, timedOut });
|
|
277
|
+
});
|
|
278
|
+
}).catch((cause) => {
|
|
279
|
+
throw new HarmonyAutolinkingError(
|
|
280
|
+
'SOURCE_BUILD_FAILED',
|
|
281
|
+
`Unable to execute the fixed ${step.label} for local Harmony source at ${step.cwd}.`,
|
|
282
|
+
{ cause, packageName, stage }
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (result.code !== 0 || result.signal || result.timedOut) {
|
|
287
|
+
const reason = result.timedOut
|
|
288
|
+
? 'timed out'
|
|
289
|
+
: result.signal ? `was terminated by ${result.signal}` : `exited with code ${result.code}`;
|
|
290
|
+
const output = sanitizeOutput(result.output, [step.cwd], outputLimit);
|
|
291
|
+
throw new HarmonyAutolinkingError(
|
|
292
|
+
'SOURCE_BUILD_FAILED',
|
|
293
|
+
`Fixed ${step.label} ${reason}${output ? `: ${output}` : '.'}`,
|
|
294
|
+
{ packageName, stage }
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function materializeLocalSourceAsync(
|
|
300
|
+
options: MaterializeLocalSourceOptions
|
|
301
|
+
): Promise<MaterializedLocalSource> {
|
|
302
|
+
const module = options.module;
|
|
303
|
+
if (module.source !== 'nativeModulesDir' || module.artifact.kind !== 'local-source') {
|
|
304
|
+
throw new HarmonyAutolinkingError(
|
|
305
|
+
'INVALID_OPTIONS',
|
|
306
|
+
`${module.packageName} is not a nativeModulesDir local-source module.`,
|
|
307
|
+
{ packageName: module.packageName, stage: 'artifact-materialize' }
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const artifact = module.artifact;
|
|
311
|
+
const projectRoot = await fs.promises.realpath(options.projectRoot);
|
|
312
|
+
const packageRoot = await fs.promises.realpath(module.packageRoot);
|
|
313
|
+
if (!isPathInside(projectRoot, packageRoot)) {
|
|
314
|
+
throw new HarmonyAutolinkingError(
|
|
315
|
+
'UNSAFE_SOURCE_ARTIFACT',
|
|
316
|
+
'Harmony local modules must live inside the application project root.',
|
|
317
|
+
{ packageName: module.packageName, stage: 'artifact-materialize' }
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (!module.arkTs) {
|
|
321
|
+
throw new HarmonyAutolinkingError(
|
|
322
|
+
'INVALID_METADATA',
|
|
323
|
+
`${module.packageName} local-source descriptor has no derived ArkTS package.`,
|
|
324
|
+
{ packageName: module.packageName, stage: 'artifact-materialize' }
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
const harPath = await resolveInsideAsync(
|
|
328
|
+
packageRoot,
|
|
329
|
+
module.arkTs.harPath,
|
|
330
|
+
'Harmony materialized HAR path',
|
|
331
|
+
{ packageName: module.packageName, mustExist: false }
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
for (const step of fixedLocalSourceBuildSteps(artifact.build)) {
|
|
335
|
+
await runFixedBuildStepAsync(step, module.packageName, options);
|
|
336
|
+
}
|
|
337
|
+
const outputReady = await isNonEmptyRegularHarAsync(
|
|
338
|
+
artifact.outputPath,
|
|
339
|
+
artifact.build.cwd,
|
|
340
|
+
module.packageName
|
|
341
|
+
);
|
|
342
|
+
if (!outputReady) {
|
|
343
|
+
throw new HarmonyAutolinkingError(
|
|
344
|
+
'SOURCE_BUILD_FAILED',
|
|
345
|
+
`Hvigor did not produce a non-empty HAR at ${normalizeSlashes(path.relative(packageRoot, artifact.outputPath))}.`,
|
|
346
|
+
{ packageName: module.packageName, stage: 'artifact-materialize' }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await publishArtifactsAsync({
|
|
351
|
+
allowedRoot: projectRoot,
|
|
352
|
+
lockPath: path.join(projectRoot, '.expo/harmony/module-materialize.lock'),
|
|
353
|
+
files: [{ source: artifact.outputPath, target: harPath }],
|
|
354
|
+
});
|
|
355
|
+
if (!await isNonEmptyRegularHarAsync(harPath, packageRoot, module.packageName)) {
|
|
356
|
+
throw new HarmonyAutolinkingError(
|
|
357
|
+
'SOURCE_BUILD_FAILED',
|
|
358
|
+
'Harmony HAR materialization produced a missing or empty file.',
|
|
359
|
+
{ packageName: module.packageName, stage: 'artifact-materialize' }
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
packageName: module.packageName,
|
|
364
|
+
harPath,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function materializeLocalSourcesAsync(
|
|
369
|
+
modules: ReadonlyArray<MaterializeLocalSourceOptions['module']>,
|
|
370
|
+
options: Omit<MaterializeLocalSourceOptions, 'module'>
|
|
371
|
+
): Promise<ReadonlyArray<MaterializedLocalSource>> {
|
|
372
|
+
const results = [];
|
|
373
|
+
for (const module of [...modules].sort((left, right) => compareText(left.packageName, right.packageName))) {
|
|
374
|
+
results.push(await materializeLocalSourceAsync({ ...options, module }));
|
|
375
|
+
}
|
|
376
|
+
return results;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export {
|
|
380
|
+
materializeLocalSourcesAsync,
|
|
381
|
+
materializeModuleArtifactAsync,
|
|
382
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { resolveRnohMetadata } from '../rnoh/packageMetadata';
|
|
4
|
+
import { normalizeSlashes } from '../../utilities/values';
|
|
5
|
+
|
|
6
|
+
function collectOhpmDeps(descriptors) {
|
|
7
|
+
const dependencies = new Map();
|
|
8
|
+
|
|
9
|
+
for (const descriptor of descriptors) {
|
|
10
|
+
if (!descriptor.arkTs) continue;
|
|
11
|
+
dependencies.set(descriptor.arkTs.ohPackageName, {
|
|
12
|
+
descriptor,
|
|
13
|
+
mapping: {
|
|
14
|
+
harPath: descriptor.arkTs.harPath,
|
|
15
|
+
ohPackageName: descriptor.arkTs.ohPackageName,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const descriptor of descriptors.filter(item => item.rnoh.harPaths.length > 0)) {
|
|
21
|
+
for (const mapping of resolveRnohMetadata(descriptor).harMappings) {
|
|
22
|
+
dependencies.set(mapping.ohPackageName, { descriptor, mapping });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [...dependencies.values()].sort((left, right) => {
|
|
27
|
+
return left.mapping.ohPackageName.localeCompare(right.mapping.ohPackageName, 'en');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveOhpmSpecifier(
|
|
32
|
+
descriptor,
|
|
33
|
+
mapping,
|
|
34
|
+
options: { harmonyProjectPath?: string; nodeModulesPath?: string } = {}
|
|
35
|
+
) {
|
|
36
|
+
if (options.harmonyProjectPath) {
|
|
37
|
+
const packageRoot = options.nodeModulesPath
|
|
38
|
+
? path.join(options.nodeModulesPath, ...descriptor.packageName.split('/'))
|
|
39
|
+
: descriptor.packageLinkPath;
|
|
40
|
+
const target = path.join(packageRoot, ...mapping.harPath.split('/'));
|
|
41
|
+
const relative = normalizeSlashes(path.relative(options.harmonyProjectPath, target));
|
|
42
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
43
|
+
}
|
|
44
|
+
return mapping.version || descriptor.packageVersion;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeLocalOhpmSpecifier(specifier) {
|
|
48
|
+
if (typeof specifier !== 'string' || !specifier) return null;
|
|
49
|
+
if (path.isAbsolute(specifier) || path.win32.isAbsolute(specifier) || !/^\.\.?[\\/]/u.test(specifier)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return normalizeSlashes(specifier);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
collectOhpmDeps,
|
|
57
|
+
resolveOhpmSpecifier,
|
|
58
|
+
normalizeLocalOhpmSpecifier,
|
|
59
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import JSON5 from 'json5';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
CanonicalizeAutolinkingArtifactsOptions,
|
|
7
|
+
CanonicalizedAutolinkingArtifacts,
|
|
8
|
+
Manifest,
|
|
9
|
+
} from '../../types';
|
|
10
|
+
import { HarmonyAutolinkingError } from '../../errors';
|
|
11
|
+
import { ohpmDependenciesFromManifest, validateManifest } from '../manifest';
|
|
12
|
+
import { isObject, isPathInside, stringifyJson } from '../../utilities/values';
|
|
13
|
+
|
|
14
|
+
interface CanonicalizeOhpmManifestOptions {
|
|
15
|
+
previousManagedOhpmPackageNames?: ReadonlyArray<string>;
|
|
16
|
+
requireManagedEntries?: boolean;
|
|
17
|
+
harmonyProjectPath?: string;
|
|
18
|
+
nodeModulesPath?: string;
|
|
19
|
+
errorCode?: string;
|
|
20
|
+
stage?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireProjectRoot(value: unknown, field: string): string {
|
|
24
|
+
if (typeof value !== 'string' || !path.isAbsolute(value)) {
|
|
25
|
+
throw new HarmonyAutolinkingError('INVALID_OPTIONS', `${field} must be an absolute path.`, { stage: 'canonicalization' });
|
|
26
|
+
}
|
|
27
|
+
return path.resolve(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function rebaseProjectPath(value: string, fromRoot: string, toRoot: string): string {
|
|
31
|
+
if (!path.isAbsolute(value) || !isPathInside(fromRoot, value)) return value;
|
|
32
|
+
return path.join(toRoot, path.relative(fromRoot, value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function canonicalizeAutolinkingManifest(
|
|
36
|
+
manifest: unknown,
|
|
37
|
+
options: { generatedProjectRoot: string; canonicalProjectRoot: string }
|
|
38
|
+
): { manifest: Manifest; source: string } {
|
|
39
|
+
const generatedProjectRoot = requireProjectRoot(options.generatedProjectRoot, 'generatedProjectRoot');
|
|
40
|
+
const canonicalProjectRoot = requireProjectRoot(options.canonicalProjectRoot, 'canonicalProjectRoot');
|
|
41
|
+
const validated = validateManifest(manifest);
|
|
42
|
+
const canonical = {
|
|
43
|
+
...validated,
|
|
44
|
+
modules: validated.modules.map(module => ({
|
|
45
|
+
...module,
|
|
46
|
+
packageRoot: rebaseProjectPath(module.packageRoot, generatedProjectRoot, canonicalProjectRoot),
|
|
47
|
+
packageLinkPath: rebaseProjectPath(module.packageLinkPath, generatedProjectRoot, canonicalProjectRoot),
|
|
48
|
+
})),
|
|
49
|
+
} as Manifest;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
manifest: canonical,
|
|
53
|
+
source: stringifyJson(canonical),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function canonicalizeOhpmManifest(
|
|
58
|
+
source: string,
|
|
59
|
+
manifest: Manifest,
|
|
60
|
+
options: CanonicalizeOhpmManifestOptions = {}
|
|
61
|
+
): { manifest: Readonly<Record<string, unknown>>; source: string } {
|
|
62
|
+
const errorCode = options.errorCode || 'INVALID_OPTIONS';
|
|
63
|
+
const stage = options.stage || 'canonicalization';
|
|
64
|
+
let candidate;
|
|
65
|
+
try {
|
|
66
|
+
candidate = JSON5.parse(source);
|
|
67
|
+
} catch (cause) {
|
|
68
|
+
throw new HarmonyAutolinkingError(errorCode, 'harmony/oh-package.json5 must contain a JSON5 object.', { cause, stage });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!isObject(candidate)) {
|
|
72
|
+
throw new HarmonyAutolinkingError(errorCode, 'harmony/oh-package.json5 must contain a JSON5 object.', { stage });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const field of ['dependencies', 'overrides']) {
|
|
76
|
+
if (candidate[field] !== undefined && !isObject(candidate[field])) {
|
|
77
|
+
throw new HarmonyAutolinkingError(errorCode, `harmony/oh-package.json5 ${field} must be an object.`, { stage });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const specifiers = ohpmDependenciesFromManifest(manifest, {
|
|
82
|
+
harmonyProjectPath: options.harmonyProjectPath,
|
|
83
|
+
nodeModulesPath: options.nodeModulesPath,
|
|
84
|
+
});
|
|
85
|
+
const currentNames = Object.keys(specifiers).sort((left, right) => left.localeCompare(right, 'en'));
|
|
86
|
+
const managedNames = new Set([
|
|
87
|
+
...(options.previousManagedOhpmPackageNames || []),
|
|
88
|
+
...currentNames,
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
if (options.requireManagedEntries) {
|
|
92
|
+
for (const field of ['dependencies', 'overrides']) {
|
|
93
|
+
if (!isObject(candidate[field])) {
|
|
94
|
+
throw new HarmonyAutolinkingError(
|
|
95
|
+
errorCode,
|
|
96
|
+
`harmony/oh-package.json5 ${field} must be an object.`,
|
|
97
|
+
{ stage }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
for (const packageName of currentNames) {
|
|
101
|
+
if (!Object.hasOwn(candidate[field], packageName)) {
|
|
102
|
+
throw new HarmonyAutolinkingError(
|
|
103
|
+
errorCode,
|
|
104
|
+
`RNOH omitted the managed ${field} entry for ${packageName}.`,
|
|
105
|
+
{ stage, details: { packageName, field } }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const field of ['dependencies', 'overrides']) {
|
|
113
|
+
const values = { ...(candidate[field] || {}) };
|
|
114
|
+
for (const packageName of managedNames) delete values[packageName];
|
|
115
|
+
for (const packageName of currentNames) values[packageName] = specifiers[packageName];
|
|
116
|
+
candidate[field] = values;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
manifest: candidate,
|
|
121
|
+
source: `${JSON5.stringify(candidate, null, 2)}\n`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function canonicalizeAutolinkingArtifacts(
|
|
126
|
+
options: CanonicalizeAutolinkingArtifactsOptions
|
|
127
|
+
): CanonicalizedAutolinkingArtifacts {
|
|
128
|
+
let parsedManifest;
|
|
129
|
+
try {
|
|
130
|
+
parsedManifest = JSON.parse(options.manifestSource);
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
throw new HarmonyAutolinkingError(
|
|
133
|
+
'INVALID_MANIFEST',
|
|
134
|
+
'Harmony autolinking manifest must contain valid JSON.',
|
|
135
|
+
{ cause, stage: 'canonicalization' }
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const canonicalManifest = canonicalizeAutolinkingManifest(parsedManifest, options);
|
|
140
|
+
const canonicalOhpm = canonicalizeOhpmManifest(
|
|
141
|
+
options.ohPackageSource,
|
|
142
|
+
canonicalManifest.manifest,
|
|
143
|
+
{ requireManagedEntries: true, harmonyProjectPath: options.harmonyProjectPath }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
manifest: canonicalManifest.manifest,
|
|
148
|
+
manifestSource: canonicalManifest.source,
|
|
149
|
+
ohPackageSource: canonicalOhpm.source,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
canonicalizeAutolinkingArtifacts,
|
|
155
|
+
canonicalizeOhpmManifest,
|
|
156
|
+
};
|