@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,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.publishArtifactsAsync = publishArtifactsAsync;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const errors_1 = require("../../errors");
|
|
10
|
+
const values_1 = require("../../utilities/values");
|
|
11
|
+
const MALFORMED_LOCK_GRACE_MS = 30_000;
|
|
12
|
+
async function processIsAliveAsync(pid) {
|
|
13
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (cause) {
|
|
20
|
+
return cause.code !== 'ESRCH';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function removeStaleLockAsync(lockPath) {
|
|
24
|
+
let observed;
|
|
25
|
+
try {
|
|
26
|
+
observed = await node_fs_1.default.promises.lstat(lockPath);
|
|
27
|
+
if (!observed.isFile() || observed.isSymbolicLink())
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
catch (_cause) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
let contents;
|
|
34
|
+
try {
|
|
35
|
+
contents = JSON.parse(await node_fs_1.default.promises.readFile(lockPath, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch (_cause) {
|
|
38
|
+
if (Date.now() - observed.mtimeMs < MALFORMED_LOCK_GRACE_MS)
|
|
39
|
+
return false;
|
|
40
|
+
contents = null;
|
|
41
|
+
}
|
|
42
|
+
if (contents && await processIsAliveAsync(contents.pid))
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
const current = await node_fs_1.default.promises.lstat(lockPath);
|
|
46
|
+
if (current.dev !== observed.dev || current.ino !== observed.ino
|
|
47
|
+
|| current.mtimeMs !== observed.mtimeMs || current.size !== observed.size) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
await node_fs_1.default.promises.unlink(lockPath);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (_cause) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function releasePublishLockAsync(handle, lockPath) {
|
|
58
|
+
let owned;
|
|
59
|
+
try {
|
|
60
|
+
owned = await handle.stat();
|
|
61
|
+
}
|
|
62
|
+
catch (_cause) {
|
|
63
|
+
// Closing the descriptor is still required even if its metadata is unavailable.
|
|
64
|
+
}
|
|
65
|
+
await handle.close().catch(() => { });
|
|
66
|
+
if (!owned)
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
const current = await node_fs_1.default.promises.lstat(lockPath);
|
|
70
|
+
if (current.dev !== owned.dev || current.ino !== owned.ino)
|
|
71
|
+
return false;
|
|
72
|
+
await node_fs_1.default.promises.unlink(lockPath);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch (_cause) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function acquirePublishLockAsync(lockPath) {
|
|
80
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(lockPath), { recursive: true });
|
|
81
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
82
|
+
try {
|
|
83
|
+
const handle = await node_fs_1.default.promises.open(lockPath, 'wx', 0o600);
|
|
84
|
+
try {
|
|
85
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
|
|
86
|
+
await handle.sync();
|
|
87
|
+
return handle;
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
await releasePublishLockAsync(handle, lockPath);
|
|
91
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Unable to initialize the Harmony autolinking publish lock.', { cause, stage: 'publish' });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (cause) {
|
|
95
|
+
if (cause.code === 'EEXIST' && attempt === 0 && await removeStaleLockAsync(lockPath))
|
|
96
|
+
continue;
|
|
97
|
+
if (cause.code === 'EEXIST') {
|
|
98
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Another Harmony autolinking publish is in progress.', { cause, stage: 'publish' });
|
|
99
|
+
}
|
|
100
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Unable to acquire the Harmony autolinking lock.', { cause, stage: 'publish' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Unable to acquire the Harmony autolinking lock.', { stage: 'publish' });
|
|
104
|
+
}
|
|
105
|
+
function assertLexicallyContained(target, allowedRoot) {
|
|
106
|
+
const resolved = node_path_1.default.resolve(target);
|
|
107
|
+
const absoluteRoot = node_path_1.default.resolve(allowedRoot);
|
|
108
|
+
if (!(0, values_1.isPathInside)(absoluteRoot, resolved)) {
|
|
109
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target escapes the allowed root: ${resolved}`, { stage: 'publish' });
|
|
110
|
+
}
|
|
111
|
+
if (resolved.split(node_path_1.default.sep).includes('node_modules')) {
|
|
112
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Publishing into node_modules is forbidden.', { stage: 'publish' });
|
|
113
|
+
}
|
|
114
|
+
return resolved;
|
|
115
|
+
}
|
|
116
|
+
async function assertSafeTargetAsync(target, allowedRoot) {
|
|
117
|
+
const resolved = assertLexicallyContained(target, allowedRoot);
|
|
118
|
+
const absoluteRoot = node_path_1.default.resolve(allowedRoot);
|
|
119
|
+
if (await (0, values_1.pathExistsAsync)(resolved)) {
|
|
120
|
+
const stat = await node_fs_1.default.promises.lstat(resolved);
|
|
121
|
+
if (stat.isSymbolicLink()) {
|
|
122
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target must not be a symbolic link: ${resolved}`, { stage: 'publish' });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (await (0, values_1.pathExistsAsync)(absoluteRoot)) {
|
|
126
|
+
const realRoot = await node_fs_1.default.promises.realpath(absoluteRoot);
|
|
127
|
+
let ancestor = node_path_1.default.dirname(resolved);
|
|
128
|
+
while (!(await (0, values_1.pathExistsAsync)(ancestor))) {
|
|
129
|
+
const parent = node_path_1.default.dirname(ancestor);
|
|
130
|
+
if (parent === ancestor)
|
|
131
|
+
break;
|
|
132
|
+
ancestor = parent;
|
|
133
|
+
}
|
|
134
|
+
const realAncestor = await node_fs_1.default.promises.realpath(ancestor);
|
|
135
|
+
if (!(0, values_1.isPathInside)(realRoot, realAncestor)) {
|
|
136
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', `Publish target resolves outside the allowed root: ${resolved}`, { stage: 'publish' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return resolved;
|
|
140
|
+
}
|
|
141
|
+
async function stageFileAsync(file, stagedTarget) {
|
|
142
|
+
if ((file.source === undefined) === (file.content === undefined)) {
|
|
143
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'Each publish file needs exactly one source or content.', { stage: 'publish' });
|
|
144
|
+
}
|
|
145
|
+
if (file.source !== undefined) {
|
|
146
|
+
await node_fs_1.default.promises.copyFile(file.source, stagedTarget);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
if (typeof file.content !== 'string' && !Buffer.isBuffer(file.content)) {
|
|
150
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'Publish file content must be a string or Buffer.', { stage: 'publish' });
|
|
151
|
+
}
|
|
152
|
+
await node_fs_1.default.promises.writeFile(stagedTarget, file.content, file.mode ? { mode: file.mode } : undefined);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function filesEqualAsync(left, right) {
|
|
156
|
+
if (!(await (0, values_1.pathExistsAsync)(left)) || !(await (0, values_1.pathExistsAsync)(right)))
|
|
157
|
+
return false;
|
|
158
|
+
const [leftBuffer, rightBuffer] = await Promise.all([
|
|
159
|
+
node_fs_1.default.promises.readFile(left),
|
|
160
|
+
node_fs_1.default.promises.readFile(right),
|
|
161
|
+
]);
|
|
162
|
+
return leftBuffer.equals(rightBuffer);
|
|
163
|
+
}
|
|
164
|
+
async function removeEmptyParentsAsync(directory, stopAt) {
|
|
165
|
+
let current = directory;
|
|
166
|
+
while ((0, values_1.isPathInside)(stopAt, current) && current !== stopAt) {
|
|
167
|
+
try {
|
|
168
|
+
await node_fs_1.default.promises.rmdir(current);
|
|
169
|
+
}
|
|
170
|
+
catch (_cause) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
current = node_path_1.default.dirname(current);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function publishArtifactsAsync(options) {
|
|
177
|
+
if (!Array.isArray(options?.files) || options.files.length === 0) {
|
|
178
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'A publish transaction requires at least one file.', { stage: 'publish' });
|
|
179
|
+
}
|
|
180
|
+
if (typeof options.allowedRoot !== 'string' || !options.allowedRoot) {
|
|
181
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'A publish transaction requires allowedRoot.', { stage: 'publish' });
|
|
182
|
+
}
|
|
183
|
+
const allowedRoot = node_path_1.default.resolve(options.allowedRoot);
|
|
184
|
+
const files = [];
|
|
185
|
+
for (const file of options.files) {
|
|
186
|
+
if (!file || typeof file.target !== 'string' || !file.target) {
|
|
187
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'Publish file target must be a non-empty string.', { stage: 'publish' });
|
|
188
|
+
}
|
|
189
|
+
files.push({ ...file, target: await assertSafeTargetAsync(file.target, allowedRoot) });
|
|
190
|
+
}
|
|
191
|
+
if (new Set(files.map(file => file.target)).size !== files.length) {
|
|
192
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'A publish transaction contains duplicate targets.', { stage: 'publish' });
|
|
193
|
+
}
|
|
194
|
+
const stale = [];
|
|
195
|
+
for (const target of options.stale || []) {
|
|
196
|
+
const safeTarget = await assertSafeTargetAsync(target, allowedRoot);
|
|
197
|
+
if (!files.some(file => file.target === safeTarget) && !stale.includes(safeTarget))
|
|
198
|
+
stale.push(safeTarget);
|
|
199
|
+
}
|
|
200
|
+
const lockPath = await assertSafeTargetAsync(options.lockPath || node_path_1.default.join(allowedRoot, '.expo-harmony-autolinking.lock'), allowedRoot);
|
|
201
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(lockPath), { recursive: true });
|
|
202
|
+
const tmp = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_path_1.default.dirname(lockPath), '.expo-harmony-publish-'));
|
|
203
|
+
const prepared = [];
|
|
204
|
+
let lockHandle;
|
|
205
|
+
const touched = [];
|
|
206
|
+
const changed = [];
|
|
207
|
+
const unchanged = [];
|
|
208
|
+
try {
|
|
209
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
210
|
+
const staged = node_path_1.default.join(tmp, `new-${index}`);
|
|
211
|
+
await stageFileAsync(files[index], staged);
|
|
212
|
+
prepared.push({ ...files[index], staged });
|
|
213
|
+
}
|
|
214
|
+
lockHandle = await acquirePublishLockAsync(lockPath);
|
|
215
|
+
for (let index = 0; index < prepared.length; index += 1) {
|
|
216
|
+
const file = prepared[index];
|
|
217
|
+
if (await filesEqualAsync(file.staged, file.target)) {
|
|
218
|
+
unchanged.push(file.target);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const existed = await (0, values_1.pathExistsAsync)(file.target);
|
|
222
|
+
const backup = existed ? node_path_1.default.join(tmp, `backup-${index}`) : null;
|
|
223
|
+
if (backup)
|
|
224
|
+
await node_fs_1.default.promises.copyFile(file.target, backup);
|
|
225
|
+
touched.push({ target: file.target, existed, backup });
|
|
226
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(file.target), { recursive: true });
|
|
227
|
+
await node_fs_1.default.promises.rename(file.staged, file.target);
|
|
228
|
+
if (file.mode)
|
|
229
|
+
await node_fs_1.default.promises.chmod(file.target, file.mode);
|
|
230
|
+
changed.push(file.target);
|
|
231
|
+
}
|
|
232
|
+
for (let index = 0; index < stale.length; index += 1) {
|
|
233
|
+
const target = stale[index];
|
|
234
|
+
if (!(await (0, values_1.pathExistsAsync)(target)))
|
|
235
|
+
continue;
|
|
236
|
+
const backup = node_path_1.default.join(tmp, `stale-${index}`);
|
|
237
|
+
await node_fs_1.default.promises.copyFile(target, backup);
|
|
238
|
+
touched.push({ target, existed: true, backup });
|
|
239
|
+
await node_fs_1.default.promises.unlink(target);
|
|
240
|
+
changed.push(target);
|
|
241
|
+
}
|
|
242
|
+
return { changed, unchanged, warnings: [] };
|
|
243
|
+
}
|
|
244
|
+
catch (cause) {
|
|
245
|
+
const failures = [];
|
|
246
|
+
for (const entry of [...touched].reverse()) {
|
|
247
|
+
try {
|
|
248
|
+
if (entry.existed) {
|
|
249
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(entry.target), { recursive: true });
|
|
250
|
+
await node_fs_1.default.promises.copyFile(entry.backup, entry.target);
|
|
251
|
+
}
|
|
252
|
+
else if (await (0, values_1.pathExistsAsync)(entry.target)) {
|
|
253
|
+
await node_fs_1.default.promises.unlink(entry.target);
|
|
254
|
+
await removeEmptyParentsAsync(node_path_1.default.dirname(entry.target), allowedRoot);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
failures.push({ target: entry.target, message: error.message });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
throw new errors_1.HarmonyAutolinkingError('PUBLISH_FAILED', 'Harmony autolinking publish failed and was rolled back.', {
|
|
262
|
+
cause,
|
|
263
|
+
details: failures.length > 0 ? { rollbackFailures: failures } : undefined,
|
|
264
|
+
stage: 'publish',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
if (lockHandle)
|
|
269
|
+
await releasePublishLockAsync(lockHandle, lockPath);
|
|
270
|
+
await node_fs_1.default.promises.rm(tmp, { recursive: true, force: true }).catch(() => { });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare function stageProjectAsync(options: any): Promise<{
|
|
2
|
+
projectRoot: string;
|
|
3
|
+
harmonyProjectPath: string;
|
|
4
|
+
sourceNodeModulesPath: string;
|
|
5
|
+
nodeModulesPath: string;
|
|
6
|
+
rnohRuntimePackages: any[];
|
|
7
|
+
temporaryRoot: string;
|
|
8
|
+
stageProjectRoot: string;
|
|
9
|
+
stageHarmonyProjectPath: string;
|
|
10
|
+
}>;
|
|
11
|
+
declare function cleanupStagingProjectAsync(staging: any): Promise<{
|
|
12
|
+
severity: string;
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
stage: string;
|
|
16
|
+
cause: any;
|
|
17
|
+
}>;
|
|
18
|
+
export { cleanupStagingProjectAsync, stageProjectAsync, };
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanupStagingProjectAsync = cleanupStagingProjectAsync;
|
|
7
|
+
exports.stageProjectAsync = stageProjectAsync;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const json5_1 = __importDefault(require("json5"));
|
|
12
|
+
const constants_1 = require("../../config/constants");
|
|
13
|
+
const errors_1 = require("../../errors");
|
|
14
|
+
const generate_1 = require("../manifest/generate");
|
|
15
|
+
const dependencies_1 = require("../ohpm/dependencies");
|
|
16
|
+
const canonicalize_1 = require("../persistence/canonicalize");
|
|
17
|
+
const values_1 = require("../../utilities/values");
|
|
18
|
+
async function findLocalOhpmDepsAsync(content, harmony) {
|
|
19
|
+
let dependencies;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = json5_1.default.parse(content);
|
|
22
|
+
dependencies = parsed && typeof parsed.dependencies === 'object' && !Array.isArray(parsed.dependencies)
|
|
23
|
+
? parsed.dependencies
|
|
24
|
+
: {};
|
|
25
|
+
}
|
|
26
|
+
catch (_cause) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const result = [];
|
|
30
|
+
for (const [name, specifier] of Object.entries(dependencies)) {
|
|
31
|
+
const relative = (0, dependencies_1.normalizeLocalOhpmSpecifier)(specifier);
|
|
32
|
+
if (!relative)
|
|
33
|
+
continue;
|
|
34
|
+
const harPath = node_path_1.default.resolve(harmony, relative);
|
|
35
|
+
if (!(await (0, values_1.pathExistsAsync)(harPath)))
|
|
36
|
+
continue;
|
|
37
|
+
const marker = `${node_path_1.default.sep}node_modules${node_path_1.default.sep}`;
|
|
38
|
+
const markerIndex = harPath.lastIndexOf(marker);
|
|
39
|
+
if (markerIndex < 0)
|
|
40
|
+
continue;
|
|
41
|
+
const rest = harPath.slice(markerIndex + marker.length).split(node_path_1.default.sep);
|
|
42
|
+
const packageSegments = rest[0]?.startsWith('@') ? rest.slice(0, 2) : rest.slice(0, 1);
|
|
43
|
+
if (packageSegments.length === 0 || packageSegments.some(segment => !segment))
|
|
44
|
+
continue;
|
|
45
|
+
const packageName = packageSegments.join('/');
|
|
46
|
+
const packageRoot = node_path_1.default.join(harPath.slice(0, markerIndex + marker.length), ...packageSegments);
|
|
47
|
+
try {
|
|
48
|
+
const packageJson = JSON.parse(await node_fs_1.default.promises.readFile(node_path_1.default.join(packageRoot, 'package.json'), 'utf8'));
|
|
49
|
+
if (packageJson.name !== packageName)
|
|
50
|
+
continue;
|
|
51
|
+
result.push({
|
|
52
|
+
ohPackageName: name,
|
|
53
|
+
packageName,
|
|
54
|
+
packageRoot: await node_fs_1.default.promises.realpath(packageRoot),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (_cause) {
|
|
58
|
+
// RNOH would not be able to preserve an unreadable package reference either.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
async function findNodeModulesAsync(projectRoot) {
|
|
64
|
+
let directory = projectRoot;
|
|
65
|
+
while (true) {
|
|
66
|
+
const candidate = node_path_1.default.join(directory, 'node_modules');
|
|
67
|
+
if (await (0, values_1.pathExistsAsync)(candidate))
|
|
68
|
+
return candidate;
|
|
69
|
+
const parent = node_path_1.default.dirname(directory);
|
|
70
|
+
if (parent === directory)
|
|
71
|
+
return node_path_1.default.join(projectRoot, 'node_modules');
|
|
72
|
+
directory = parent;
|
|
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
|
+
async function stageCuratedPackageAsync(stage, descriptor) {
|
|
97
|
+
await node_fs_1.default.promises.mkdir(stage, { recursive: true });
|
|
98
|
+
await node_fs_1.default.promises.writeFile(node_path_1.default.join(stage, 'package.json'), (0, values_1.stringifyJson)({
|
|
99
|
+
name: descriptor.packageName,
|
|
100
|
+
version: descriptor.packageVersion,
|
|
101
|
+
...(descriptor.rnoh.harPaths.length > 0
|
|
102
|
+
? { harmony: { autolinking: buildRnohMetadata(descriptor) } }
|
|
103
|
+
: {}),
|
|
104
|
+
}));
|
|
105
|
+
const harPaths = (0, values_1.sortedUniqueStrings)([
|
|
106
|
+
...descriptor.rnoh.harPaths,
|
|
107
|
+
...(descriptor.arkTs ? [descriptor.arkTs.harPath] : []),
|
|
108
|
+
]);
|
|
109
|
+
for (const harPath of harPaths) {
|
|
110
|
+
const source = await (0, values_1.resolveInsideAsync)(descriptor.packageRoot, harPath, 'descriptor HAR path', { packageName: descriptor.packageName, type: 'file' });
|
|
111
|
+
const destination = node_path_1.default.resolve(stage, ...harPath.split('/'));
|
|
112
|
+
if (!(0, values_1.isPathInside)(stage, destination)) {
|
|
113
|
+
throw new errors_1.HarmonyAutolinkingError('PATH_OUTSIDE_PACKAGE', 'A curated HAR path escapes its staging package.', {
|
|
114
|
+
packageName: descriptor.packageName,
|
|
115
|
+
stage: 'staging',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(destination), { recursive: true });
|
|
119
|
+
await node_fs_1.default.promises.copyFile(source, destination, node_fs_1.default.constants.COPYFILE_FICLONE);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function updateOhpmManifestAsync(manifestPath, descriptors, options) {
|
|
123
|
+
let source;
|
|
124
|
+
try {
|
|
125
|
+
source = await node_fs_1.default.promises.readFile(manifestPath, 'utf8');
|
|
126
|
+
}
|
|
127
|
+
catch (cause) {
|
|
128
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'Unable to read harmony/oh-package.json5.', {
|
|
129
|
+
cause,
|
|
130
|
+
stage: 'staging',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const manifest = (0, generate_1.createManifest)(descriptors, { buildType: options.buildType });
|
|
134
|
+
const canonical = (0, canonicalize_1.canonicalizeOhpmManifest)(source, manifest, {
|
|
135
|
+
previousManagedOhpmPackageNames: options.previousManagedOhpmPackageNames,
|
|
136
|
+
harmonyProjectPath: options.harmonyProjectPath,
|
|
137
|
+
nodeModulesPath: options.nodeModulesPath,
|
|
138
|
+
stage: 'staging',
|
|
139
|
+
});
|
|
140
|
+
await node_fs_1.default.promises.writeFile(manifestPath, canonical.source);
|
|
141
|
+
}
|
|
142
|
+
async function stageProjectAsync(options) {
|
|
143
|
+
const projectRoot = await (0, values_1.realpathExistingAsync)(options.projectRoot, {
|
|
144
|
+
type: 'directory',
|
|
145
|
+
field: 'projectRoot',
|
|
146
|
+
stage: 'staging',
|
|
147
|
+
});
|
|
148
|
+
const harmony = await (0, values_1.realpathExistingAsync)(options.harmonyProjectPath, {
|
|
149
|
+
type: 'directory',
|
|
150
|
+
field: 'harmonyProjectPath',
|
|
151
|
+
stage: 'staging',
|
|
152
|
+
});
|
|
153
|
+
if (!(0, values_1.isPathInside)(projectRoot, harmony)) {
|
|
154
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'harmonyProjectPath must be inside projectRoot.', { stage: 'staging' });
|
|
155
|
+
}
|
|
156
|
+
const requested = options.nodeModulesPath || await findNodeModulesAsync(projectRoot);
|
|
157
|
+
const source = await (0, values_1.realpathExistingAsync)(requested, {
|
|
158
|
+
type: 'directory',
|
|
159
|
+
field: 'nodeModulesPath',
|
|
160
|
+
stage: 'staging',
|
|
161
|
+
});
|
|
162
|
+
const tmp = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'expo-harmony-autolinking-'));
|
|
163
|
+
try {
|
|
164
|
+
const stageRoot = node_path_1.default.join(tmp, 'project');
|
|
165
|
+
const relative = node_path_1.default.relative(projectRoot, harmony);
|
|
166
|
+
const stageHarmony = node_path_1.default.join(stageRoot, relative);
|
|
167
|
+
const modules = node_path_1.default.join(stageRoot, 'node_modules');
|
|
168
|
+
await node_fs_1.default.promises.mkdir(stageHarmony, { recursive: true });
|
|
169
|
+
await node_fs_1.default.promises.writeFile(node_path_1.default.join(stageRoot, 'package.json'), (0, values_1.stringifyJson)({
|
|
170
|
+
name: 'expo-harmony-autolinking-stage',
|
|
171
|
+
private: true,
|
|
172
|
+
}));
|
|
173
|
+
let plugin = `${constants_1.RnohCliPackage}/react-native.config.js`;
|
|
174
|
+
try {
|
|
175
|
+
const pkg = options.rnohCliPackageJsonPath
|
|
176
|
+
? await node_fs_1.default.promises.realpath(options.rnohCliPackageJsonPath)
|
|
177
|
+
: (0, values_1.resolvePackageFromProject)(projectRoot, `${constants_1.RnohCliPackage}/package.json`);
|
|
178
|
+
plugin = await node_fs_1.default.promises.realpath(node_path_1.default.join(node_path_1.default.dirname(pkg), 'react-native.config.js'));
|
|
179
|
+
}
|
|
180
|
+
catch (_cause) {
|
|
181
|
+
// Preflight emits the actionable package/CLI diagnostic. Keeping the public
|
|
182
|
+
// package request here also lets callers inspect a stage without installing RNOH.
|
|
183
|
+
}
|
|
184
|
+
await node_fs_1.default.promises.writeFile(node_path_1.default.join(stageRoot, 'react-native.config.js'), `module.exports = require(${JSON.stringify(plugin)});\n`);
|
|
185
|
+
const runtime = [];
|
|
186
|
+
if (options.modules === undefined) {
|
|
187
|
+
await node_fs_1.default.promises.symlink(source, modules, process.platform === 'win32' ? 'junction' : 'dir');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await node_fs_1.default.promises.mkdir(modules, { recursive: true });
|
|
191
|
+
const staged = options.modules.filter(descriptor => (descriptor.rnoh.harPaths.length > 0 || descriptor.arkTs));
|
|
192
|
+
for (const descriptor of staged) {
|
|
193
|
+
const segments = descriptor.packageName.split('/');
|
|
194
|
+
const validName = segments.length === 1
|
|
195
|
+
? segments[0] && !segments[0].startsWith('@')
|
|
196
|
+
: segments.length === 2 && segments[0].startsWith('@') && segments[0].length > 1 && segments[1];
|
|
197
|
+
if (!validName || segments.some(segment => segment === '.' || segment === '..' || segment.includes('\\'))) {
|
|
198
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_METADATA', `Invalid npm package name for curated staging: ${descriptor.packageName}`, {
|
|
199
|
+
packageName: descriptor.packageName,
|
|
200
|
+
stage: 'staging',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const stage = node_path_1.default.join(modules, ...segments);
|
|
204
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(stage), { recursive: true });
|
|
205
|
+
await stageCuratedPackageAsync(stage, descriptor);
|
|
206
|
+
}
|
|
207
|
+
const actual = node_path_1.default.join(harmony, constants_1.RnohArtifacts.ohPackage);
|
|
208
|
+
if (await (0, values_1.pathExistsAsync)(actual)) {
|
|
209
|
+
const content = await node_fs_1.default.promises.readFile(actual, 'utf8');
|
|
210
|
+
const previous = new Set(options.previousManagedOhpmPackageNames || []);
|
|
211
|
+
const verified = new Set(staged.map(item => item.packageName));
|
|
212
|
+
for (const dependency of await findLocalOhpmDepsAsync(content, harmony)) {
|
|
213
|
+
if (previous.has(dependency.ohPackageName))
|
|
214
|
+
continue;
|
|
215
|
+
if (verified.has(dependency.packageName))
|
|
216
|
+
continue;
|
|
217
|
+
const target = node_path_1.default.join(modules, ...dependency.packageName.split('/'));
|
|
218
|
+
if (!(await (0, values_1.pathExistsAsync)(target))) {
|
|
219
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(target), { recursive: true });
|
|
220
|
+
await node_fs_1.default.promises.symlink(dependency.packageRoot, target, process.platform === 'win32' ? 'junction' : 'dir');
|
|
221
|
+
}
|
|
222
|
+
runtime.push(dependency.packageName);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const packageName = '@react-native-community/cli';
|
|
227
|
+
const pkg = (0, values_1.resolvePackageFromProject)(projectRoot, `${packageName}/package.json`);
|
|
228
|
+
const packageRoot = await node_fs_1.default.promises.realpath(node_path_1.default.dirname(pkg));
|
|
229
|
+
const target = node_path_1.default.join(modules, ...packageName.split('/'));
|
|
230
|
+
if (!(await (0, values_1.pathExistsAsync)(target))) {
|
|
231
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(target), { recursive: true });
|
|
232
|
+
await node_fs_1.default.promises.symlink(packageRoot, target, process.platform === 'win32' ? 'junction' : 'dir');
|
|
233
|
+
}
|
|
234
|
+
runtime.push(packageName);
|
|
235
|
+
}
|
|
236
|
+
catch (_cause) {
|
|
237
|
+
// Explicit/fake executables need not install the React Native wrapper runtime.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const relativePath of Object.values(constants_1.RnohArtifacts)) {
|
|
241
|
+
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(node_path_1.default.join(stageHarmony, relativePath)), { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
const actual = node_path_1.default.join(harmony, constants_1.RnohArtifacts.ohPackage);
|
|
244
|
+
const stage = node_path_1.default.join(stageHarmony, constants_1.RnohArtifacts.ohPackage);
|
|
245
|
+
if (await (0, values_1.pathExistsAsync)(actual)) {
|
|
246
|
+
const stat = await node_fs_1.default.promises.lstat(actual);
|
|
247
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
248
|
+
throw new errors_1.HarmonyAutolinkingError('INVALID_OPTIONS', 'harmony/oh-package.json5 must be a regular file.', { stage: 'staging' });
|
|
249
|
+
}
|
|
250
|
+
await node_fs_1.default.promises.copyFile(actual, stage);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
await node_fs_1.default.promises.writeFile(stage, '{\n dependencies: {}\n}\n');
|
|
254
|
+
}
|
|
255
|
+
if (options.modules !== undefined) {
|
|
256
|
+
await updateOhpmManifestAsync(stage, options.modules, {
|
|
257
|
+
buildType: options.buildType,
|
|
258
|
+
harmonyProjectPath: stageHarmony,
|
|
259
|
+
nodeModulesPath: modules,
|
|
260
|
+
previousManagedOhpmPackageNames: options.previousManagedOhpmPackageNames,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
projectRoot,
|
|
265
|
+
harmonyProjectPath: harmony,
|
|
266
|
+
sourceNodeModulesPath: source,
|
|
267
|
+
nodeModulesPath: modules,
|
|
268
|
+
rnohRuntimePackages: runtime,
|
|
269
|
+
temporaryRoot: tmp,
|
|
270
|
+
stageProjectRoot: stageRoot,
|
|
271
|
+
stageHarmonyProjectPath: stageHarmony,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
catch (cause) {
|
|
275
|
+
let warning;
|
|
276
|
+
try {
|
|
277
|
+
await node_fs_1.default.promises.rm(tmp, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
catch (cleanupCause) {
|
|
280
|
+
warning = {
|
|
281
|
+
severity: 'warning',
|
|
282
|
+
code: 'ERR_EXPO_HARMONY_STAGING_CLEANUP_FAILED',
|
|
283
|
+
message: 'Unable to remove a partially-created Harmony autolinking staging directory.',
|
|
284
|
+
stage: 'cleanup',
|
|
285
|
+
details: { message: cleanupCause.message },
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
throw new errors_1.HarmonyAutolinkingError(typeof cause.code === 'string' ? cause.code : 'STAGING_FAILED', cause.message || 'Unable to stage the Harmony project.', {
|
|
289
|
+
cause,
|
|
290
|
+
stage: cause.stage || 'staging',
|
|
291
|
+
packageName: cause.packageName,
|
|
292
|
+
diagnostics: cause.diagnostics,
|
|
293
|
+
details: warning
|
|
294
|
+
? { ...(cause.details || {}), cleanupWarning: warning }
|
|
295
|
+
: cause.details,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function cleanupStagingProjectAsync(staging) {
|
|
300
|
+
try {
|
|
301
|
+
await node_fs_1.default.promises.rm(staging.temporaryRoot, { recursive: true, force: true });
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
catch (cause) {
|
|
305
|
+
return {
|
|
306
|
+
severity: 'warning',
|
|
307
|
+
code: 'ERR_EXPO_HARMONY_STAGING_CLEANUP_FAILED',
|
|
308
|
+
message: 'Unable to remove the Harmony autolinking staging directory.',
|
|
309
|
+
stage: 'cleanup',
|
|
310
|
+
cause,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BuildType, RnohMetadata } from '../types';
|
|
2
|
+
declare function createDescriptorFromSearchRecordAsync(record: any, buildType?: BuildType): Promise<{
|
|
3
|
+
artifact: import("../types").ModuleArtifactDescriptor;
|
|
4
|
+
arkTs?: {
|
|
5
|
+
harPath: string;
|
|
6
|
+
ohPackageName: any;
|
|
7
|
+
};
|
|
8
|
+
packageName: any;
|
|
9
|
+
packageVersion: any;
|
|
10
|
+
packageRoot: any;
|
|
11
|
+
packageLinkPath: any;
|
|
12
|
+
source: any;
|
|
13
|
+
expo: {
|
|
14
|
+
rootViewComponents: any;
|
|
15
|
+
};
|
|
16
|
+
rnoh: RnohMetadata;
|
|
17
|
+
harmony: import("../types").HarmonyModuleMetadata;
|
|
18
|
+
}>;
|
|
19
|
+
export { createDescriptorFromSearchRecordAsync, };
|