@abelxiaoxing/cadence 1.6.0 → 1.8.0
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 +25 -4
- package/config/.env.example +6 -0
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +764 -67
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +21 -3
- package/src/durable-workflow.ts +55 -40
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +664 -104
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +66 -18
- package/src/package-workflow.ts +6 -0
- package/src/phase-execution.ts +18 -7
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/remove-path.ts +32 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +18 -6
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +50 -3
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
- package/src/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/build-workspace-io.mjs; edit workspace-io-entry.mjs and its source modules.
|
|
2
2
|
// src/workspace-io-entry.mjs
|
|
3
|
-
import { rmSync as rmSync2 } from "node:fs";
|
|
4
3
|
import { parentPort, workerData } from "node:worker_threads";
|
|
5
4
|
|
|
6
5
|
// src/artifact-store.ts
|
|
@@ -211,28 +210,57 @@ class ArtifactStore {
|
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
212
|
|
|
213
|
+
// src/remove-path.ts
|
|
214
|
+
import { lstatSync as lstatSync2, readdirSync, rmdirSync, rmSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
215
|
+
import path2 from "node:path";
|
|
216
|
+
function removePathSync(target, options = {}) {
|
|
217
|
+
if (process.platform !== "win32") {
|
|
218
|
+
rmSync(target, options);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let stat;
|
|
222
|
+
try {
|
|
223
|
+
stat = lstatSync2(target);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (options.force && error.code === "ENOENT")
|
|
226
|
+
return;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
230
|
+
if (!options.recursive)
|
|
231
|
+
throw Object.assign(new Error("Path is a directory"), {
|
|
232
|
+
code: "ERR_FS_EISDIR"
|
|
233
|
+
});
|
|
234
|
+
for (const name of readdirSync(target))
|
|
235
|
+
removePathSync(path2.join(target, name), options);
|
|
236
|
+
rmdirSync(target);
|
|
237
|
+
} else
|
|
238
|
+
unlinkSync2(target);
|
|
239
|
+
}
|
|
240
|
+
|
|
214
241
|
// src/verification-environment-io.ts
|
|
215
242
|
import {
|
|
216
243
|
constants as constants3,
|
|
217
244
|
cpSync,
|
|
218
|
-
lstatSync as
|
|
245
|
+
lstatSync as lstatSync3,
|
|
219
246
|
mkdirSync as mkdirSync2,
|
|
220
|
-
readdirSync,
|
|
247
|
+
readdirSync as readdirSync2,
|
|
248
|
+
readlinkSync,
|
|
221
249
|
realpathSync,
|
|
222
|
-
rmSync,
|
|
223
250
|
symlinkSync,
|
|
224
251
|
writeFileSync as writeFileSync2
|
|
225
252
|
} from "node:fs";
|
|
226
|
-
import
|
|
253
|
+
import path3 from "node:path";
|
|
227
254
|
var CACHES = [".vite", ".vite-temp"];
|
|
228
255
|
function within(root, candidate) {
|
|
229
|
-
const relative =
|
|
230
|
-
return relative !== ".." && !relative.startsWith(`..${
|
|
256
|
+
const relative = path3.relative(root, candidate);
|
|
257
|
+
return relative !== ".." && !relative.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative);
|
|
231
258
|
}
|
|
232
259
|
function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings, profile, environment, checkCancelled, privateRoot) {
|
|
233
|
-
const cleanup = () =>
|
|
260
|
+
const cleanup = () => removePathSync(privateRoot, { recursive: true, force: true });
|
|
234
261
|
try {
|
|
235
|
-
const local = profile.mode
|
|
262
|
+
const local = profile.mode !== "isolated";
|
|
263
|
+
const native = profile.mode === "host-trusted";
|
|
236
264
|
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
237
265
|
throw new Error("trusted-candidate-required");
|
|
238
266
|
const mounts = [
|
|
@@ -245,23 +273,31 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
245
273
|
"reports",
|
|
246
274
|
"dependencies"
|
|
247
275
|
]) {
|
|
248
|
-
mkdirSync2(
|
|
276
|
+
mkdirSync2(path3.join(privateRoot, directory), { mode: 448 });
|
|
249
277
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
278
|
+
if (native) {
|
|
279
|
+
mkdirSync2(path3.join(privateRoot, "home/AppData/Roaming"), {
|
|
280
|
+
recursive: true
|
|
281
|
+
});
|
|
282
|
+
mkdirSync2(path3.join(privateRoot, "home/AppData/Local"), {
|
|
283
|
+
recursive: true
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
writeFileSync2(path3.join(privateRoot, "home/user.npmrc"), "");
|
|
287
|
+
writeFileSync2(path3.join(privateRoot, "home/global.npmrc"), "");
|
|
288
|
+
const dependencies = path3.join(dependencyOwner, "node_modules");
|
|
289
|
+
const stat = lstatSync3(dependencies, { throwIfNoEntry: false });
|
|
290
|
+
if (stat && !native) {
|
|
291
|
+
const target = path3.join(root, "node_modules");
|
|
292
|
+
const targetStat = lstatSync3(target, { throwIfNoEntry: false });
|
|
257
293
|
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink())) {
|
|
258
294
|
throw new Error("dependency-path-unsafe");
|
|
259
295
|
}
|
|
260
296
|
if (!targetStat)
|
|
261
297
|
mkdirSync2(target, { mode: 448 });
|
|
262
|
-
const view =
|
|
298
|
+
const view = path3.join(privateRoot, "dependencies");
|
|
263
299
|
mounts.push({ source: view, target: "/workspace/node_modules" });
|
|
264
|
-
const entries =
|
|
300
|
+
const entries = readdirSync2(dependencies);
|
|
265
301
|
if (entries.length > 20000)
|
|
266
302
|
throw new Error("dependency-view-too-large");
|
|
267
303
|
const canonical = realpathSync(dependencies);
|
|
@@ -272,25 +308,25 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
272
308
|
throw new Error("dependency-view-too-large");
|
|
273
309
|
if (CACHES.includes(name))
|
|
274
310
|
return;
|
|
275
|
-
const source =
|
|
276
|
-
const placeholder =
|
|
277
|
-
const entry =
|
|
311
|
+
const source = path3.join(dependencies, name);
|
|
312
|
+
const placeholder = path3.join(view, name);
|
|
313
|
+
const entry = lstatSync3(source);
|
|
278
314
|
if (entry.isSymbolicLink()) {
|
|
279
315
|
const resolved = realpathSync(source);
|
|
280
316
|
if (within(canonical, resolved)) {
|
|
281
|
-
symlinkSync(
|
|
317
|
+
symlinkSync(path3.relative(path3.dirname(source), resolved), placeholder);
|
|
282
318
|
} else if (within(realpathSync(dependencyOwner), resolved)) {
|
|
283
|
-
const relative =
|
|
284
|
-
const candidate =
|
|
285
|
-
if (!
|
|
319
|
+
const relative = path3.relative(realpathSync(dependencyOwner), resolved);
|
|
320
|
+
const candidate = path3.join(root, relative);
|
|
321
|
+
if (!lstatSync3(candidate, { throwIfNoEntry: false }))
|
|
286
322
|
throw new Error("workspace-dependency-missing");
|
|
287
|
-
symlinkSync(local ? candidate : `/workspace/${relative.split(
|
|
323
|
+
symlinkSync(local ? candidate : `/workspace/${relative.split(path3.sep).join("/")}`, placeholder);
|
|
288
324
|
} else
|
|
289
325
|
throw new Error("dependency-path-unsafe");
|
|
290
|
-
} else if (entry.isDirectory() && name.startsWith("@") && !name.includes(
|
|
326
|
+
} else if (entry.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
|
|
291
327
|
mkdirSync2(placeholder);
|
|
292
|
-
for (const child of
|
|
293
|
-
projectEntry(
|
|
328
|
+
for (const child of readdirSync2(source))
|
|
329
|
+
projectEntry(path3.join(name, child));
|
|
294
330
|
} else {
|
|
295
331
|
if (entry.isDirectory())
|
|
296
332
|
mkdirSync2(placeholder);
|
|
@@ -300,15 +336,15 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
300
336
|
throw new Error("dependency-path-unsafe");
|
|
301
337
|
mounts.push({
|
|
302
338
|
source,
|
|
303
|
-
target: `/workspace/node_modules/${name.split(
|
|
339
|
+
target: `/workspace/node_modules/${name.split(path3.sep).join("/")}`
|
|
304
340
|
});
|
|
305
341
|
}
|
|
306
342
|
};
|
|
307
343
|
for (const name of entries)
|
|
308
344
|
projectEntry(name);
|
|
309
345
|
for (const cache of CACHES) {
|
|
310
|
-
mkdirSync2(
|
|
311
|
-
const source =
|
|
346
|
+
mkdirSync2(path3.join(view, cache));
|
|
347
|
+
const source = path3.join(privateRoot, "cache", cache);
|
|
312
348
|
mkdirSync2(source);
|
|
313
349
|
mounts.push({
|
|
314
350
|
source,
|
|
@@ -317,25 +353,25 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
317
353
|
});
|
|
318
354
|
}
|
|
319
355
|
}
|
|
320
|
-
if (local && stat) {
|
|
356
|
+
if (local && stat && !native) {
|
|
321
357
|
const copyEntry = (name) => {
|
|
322
358
|
checkCancelled();
|
|
323
|
-
const source =
|
|
324
|
-
const destination =
|
|
359
|
+
const source = path3.join(dependencies, name);
|
|
360
|
+
const destination = path3.join(root, "node_modules", name);
|
|
325
361
|
if (CACHES.includes(name)) {
|
|
326
362
|
mkdirSync2(destination, { recursive: true });
|
|
327
363
|
return;
|
|
328
364
|
}
|
|
329
365
|
removeEntry(destination, checkCancelled);
|
|
330
|
-
const stat2 =
|
|
366
|
+
const stat2 = lstatSync3(source);
|
|
331
367
|
if (stat2.isSymbolicLink()) {
|
|
332
368
|
const resolved = realpathSync(source);
|
|
333
|
-
const translated = within(canonicalRoot(dependencyOwner), resolved) ?
|
|
369
|
+
const translated = within(canonicalRoot(dependencyOwner), resolved) ? path3.join(root, path3.relative(canonicalRoot(dependencyOwner), resolved)) : resolved;
|
|
334
370
|
symlinkSync(translated, destination);
|
|
335
|
-
} else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(
|
|
371
|
+
} else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
|
|
336
372
|
mkdirSync2(destination);
|
|
337
|
-
for (const child of
|
|
338
|
-
copyEntry(
|
|
373
|
+
for (const child of readdirSync2(source))
|
|
374
|
+
copyEntry(path3.join(name, child));
|
|
339
375
|
} else
|
|
340
376
|
cpSync(source, destination, {
|
|
341
377
|
recursive: true,
|
|
@@ -347,15 +383,67 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
347
383
|
verbatimSymlinks: true
|
|
348
384
|
});
|
|
349
385
|
};
|
|
350
|
-
for (const name of
|
|
386
|
+
for (const name of readdirSync2(path3.join(privateRoot, "dependencies")))
|
|
351
387
|
copyEntry(name);
|
|
352
388
|
}
|
|
389
|
+
if (native && stat) {
|
|
390
|
+
const target = path3.join(root, "node_modules");
|
|
391
|
+
const targetStat = lstatSync3(target, { throwIfNoEntry: false });
|
|
392
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink()))
|
|
393
|
+
throw new Error("dependency-path-unsafe");
|
|
394
|
+
const owner = realpathSync(dependencyOwner);
|
|
395
|
+
let count = 0;
|
|
396
|
+
const copy = (source, destination) => {
|
|
397
|
+
checkCancelled();
|
|
398
|
+
if (++count > 500000)
|
|
399
|
+
throw new Error("dependency-view-too-large");
|
|
400
|
+
const entry = lstatSync3(source);
|
|
401
|
+
if (entry.isSymbolicLink()) {
|
|
402
|
+
const resolved = realpathSync(source);
|
|
403
|
+
if (!within(owner, resolved))
|
|
404
|
+
throw new Error("dependency-path-unsafe");
|
|
405
|
+
const candidate = path3.join(root, path3.relative(owner, resolved));
|
|
406
|
+
if (!within(realpathSync(dependencies), resolved) && !lstatSync3(candidate, { throwIfNoEntry: false }))
|
|
407
|
+
throw new Error("workspace-dependency-missing");
|
|
408
|
+
const linked = lstatSync3(resolved);
|
|
409
|
+
if (linked.isDirectory()) {
|
|
410
|
+
symlinkSync(candidate, destination, process.platform === "win32" ? "junction" : "dir");
|
|
411
|
+
} else if (linked.isFile()) {
|
|
412
|
+
const file = within(realpathSync(dependencies), resolved) ? resolved : candidate;
|
|
413
|
+
cpSync(file, destination, { mode: constants3.COPYFILE_FICLONE });
|
|
414
|
+
} else
|
|
415
|
+
throw new Error("dependency-path-unsafe");
|
|
416
|
+
} else if (entry.isDirectory()) {
|
|
417
|
+
mkdirSync2(destination, { recursive: true });
|
|
418
|
+
for (const name of readdirSync2(source)) {
|
|
419
|
+
if (source === dependencies && CACHES.includes(name)) {
|
|
420
|
+
mkdirSync2(path3.join(destination, name));
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
copy(path3.join(source, name), path3.join(destination, name));
|
|
424
|
+
}
|
|
425
|
+
} else if (entry.isFile())
|
|
426
|
+
cpSync(source, destination, { mode: constants3.COPYFILE_FICLONE });
|
|
427
|
+
else
|
|
428
|
+
throw new Error("dependency-path-unsafe");
|
|
429
|
+
};
|
|
430
|
+
removeEntry(target, checkCancelled);
|
|
431
|
+
copy(dependencies, target);
|
|
432
|
+
}
|
|
353
433
|
const sources = new Map;
|
|
354
434
|
const bindings = runnerBindings.map((binding) => {
|
|
435
|
+
if (native) {
|
|
436
|
+
const translate2 = (value) => path3.isAbsolute(value) && within(dependencyOwner, value) ? path3.join(root, path3.relative(dependencyOwner, value)) : value;
|
|
437
|
+
return {
|
|
438
|
+
...binding,
|
|
439
|
+
executablePath: translate2(binding.executablePath),
|
|
440
|
+
...binding.fixedArgs ? { fixedArgs: binding.fixedArgs.map(translate2) } : {}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
355
443
|
if (local || !binding.mountSource)
|
|
356
444
|
return { ...binding };
|
|
357
|
-
const source =
|
|
358
|
-
const stat2 =
|
|
445
|
+
const source = path3.resolve(binding.mountSource);
|
|
446
|
+
const stat2 = lstatSync3(source);
|
|
359
447
|
const executable = realpathSync(binding.executablePath);
|
|
360
448
|
if (!stat2.isDirectory() || stat2.isSymbolicLink() || !within(source, executable)) {
|
|
361
449
|
throw new Error("runner-mount-unavailable");
|
|
@@ -366,40 +454,46 @@ function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings,
|
|
|
366
454
|
sources.set(source, target);
|
|
367
455
|
mounts.push({ source, target });
|
|
368
456
|
}
|
|
369
|
-
const translate = (file) =>
|
|
457
|
+
const translate = (file) => path3.posix.join(target, ...path3.relative(source, file).split(path3.sep));
|
|
370
458
|
return {
|
|
371
459
|
...binding,
|
|
372
|
-
executablePath: translate(executable),
|
|
460
|
+
executablePath: translate(lstatSync3(binding.executablePath).isSymbolicLink() && !path3.isAbsolute(readlinkSync(binding.executablePath)) ? binding.executablePath : executable),
|
|
373
461
|
...binding.fixedArgs ? {
|
|
374
|
-
fixedArgs: binding.fixedArgs.map((arg) =>
|
|
462
|
+
fixedArgs: binding.fixedArgs.map((arg) => path3.isAbsolute(arg) && within(source, arg) ? translate(arg) : arg)
|
|
375
463
|
} : {}
|
|
376
464
|
};
|
|
377
465
|
});
|
|
378
466
|
checkCancelled();
|
|
379
467
|
return {
|
|
380
468
|
privateRoot,
|
|
381
|
-
mounts,
|
|
469
|
+
mounts: native ? [] : mounts,
|
|
382
470
|
bindings,
|
|
383
471
|
reportRoot: local ? privateRoot : "/cadence",
|
|
384
472
|
environment: {
|
|
385
473
|
...local ? Object.fromEntries(profile.inheritEnvironment.flatMap((name) => environment[name] === undefined ? [] : [[name, environment[name] ?? ""]])) : {},
|
|
474
|
+
...native ? Object.fromEntries(Object.entries(environment).filter(([key]) => ["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(key.toUpperCase()))) : {},
|
|
475
|
+
...native ? {
|
|
476
|
+
USERPROFILE: path3.join(privateRoot, "home"),
|
|
477
|
+
APPDATA: path3.join(privateRoot, "home", "AppData", "Roaming"),
|
|
478
|
+
LOCALAPPDATA: path3.join(privateRoot, "home", "AppData", "Local"),
|
|
479
|
+
TEMP: path3.join(privateRoot, "tmp"),
|
|
480
|
+
TMP: path3.join(privateRoot, "tmp")
|
|
481
|
+
} : {},
|
|
386
482
|
CI: "1",
|
|
387
|
-
HOME: local ?
|
|
388
|
-
XDG_CACHE_HOME: local ?
|
|
389
|
-
TMPDIR: local ?
|
|
390
|
-
npm_config_cache: local ?
|
|
391
|
-
npm_config_userconfig: local ?
|
|
392
|
-
npm_config_globalconfig: local ?
|
|
483
|
+
HOME: local ? path3.join(privateRoot, "home") : "/cadence/home",
|
|
484
|
+
XDG_CACHE_HOME: local ? path3.join(privateRoot, "cache") : "/cadence/cache",
|
|
485
|
+
TMPDIR: local ? path3.join(privateRoot, "tmp") : "/cadence/tmp",
|
|
486
|
+
npm_config_cache: local ? path3.join(privateRoot, "cache/npm") : "/cadence/cache/npm",
|
|
487
|
+
npm_config_userconfig: local ? path3.join(privateRoot, "home/user.npmrc") : "/cadence/home/user.npmrc",
|
|
488
|
+
npm_config_globalconfig: local ? path3.join(privateRoot, "home/global.npmrc") : "/cadence/home/global.npmrc",
|
|
393
489
|
PATH: [
|
|
394
490
|
...new Set([
|
|
395
491
|
...bindings.filter((binding) => binding.mountSource),
|
|
396
492
|
...bindings.filter((binding) => !binding.mountSource)
|
|
397
|
-
].map((binding) =>
|
|
398
|
-
...local ? (environment.PATH ?? "").split(
|
|
399
|
-
"/usr/local/bin",
|
|
400
|
-
|
|
401
|
-
"/bin"
|
|
402
|
-
].join(":")
|
|
493
|
+
].map((binding) => (local ? path3 : path3.posix).dirname(binding.executablePath))),
|
|
494
|
+
...local ? (environment.PATH ?? "").split(path3.delimiter) : [],
|
|
495
|
+
...native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]
|
|
496
|
+
].join(local ? path3.delimiter : ":")
|
|
403
497
|
}
|
|
404
498
|
};
|
|
405
499
|
} catch (error) {
|
|
@@ -412,13 +506,16 @@ function canonicalRoot(root) {
|
|
|
412
506
|
}
|
|
413
507
|
function removeEntry(file, checkCancelled) {
|
|
414
508
|
checkCancelled();
|
|
415
|
-
const stat =
|
|
509
|
+
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
416
510
|
if (!stat)
|
|
417
511
|
return;
|
|
418
|
-
if (stat.isDirectory())
|
|
419
|
-
for (const name of
|
|
420
|
-
removeEntry(
|
|
421
|
-
|
|
512
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
513
|
+
for (const name of readdirSync2(file))
|
|
514
|
+
removeEntry(path3.join(file, name), checkCancelled);
|
|
515
|
+
removePathSync(file, {
|
|
516
|
+
recursive: stat.isDirectory() && !stat.isSymbolicLink(),
|
|
517
|
+
force: true
|
|
518
|
+
});
|
|
422
519
|
}
|
|
423
520
|
|
|
424
521
|
// src/verification-identity.ts
|
|
@@ -427,13 +524,13 @@ import {
|
|
|
427
524
|
closeSync as closeSync3,
|
|
428
525
|
constants as constants4,
|
|
429
526
|
fstatSync as fstatSync2,
|
|
430
|
-
lstatSync as
|
|
527
|
+
lstatSync as lstatSync4,
|
|
431
528
|
openSync as openSync3,
|
|
432
|
-
readdirSync as
|
|
433
|
-
readlinkSync,
|
|
529
|
+
readdirSync as readdirSync3,
|
|
530
|
+
readlinkSync as readlinkSync2,
|
|
434
531
|
readSync
|
|
435
532
|
} from "node:fs";
|
|
436
|
-
import
|
|
533
|
+
import path4 from "node:path";
|
|
437
534
|
function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
438
535
|
const digest2 = createHash2("sha256");
|
|
439
536
|
let entries = 0;
|
|
@@ -444,22 +541,22 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
444
541
|
checkCancelled();
|
|
445
542
|
if (++entries > 500000 || Date.now() - started > 60000)
|
|
446
543
|
throw new Error("verification-environment-limit");
|
|
447
|
-
const stat =
|
|
544
|
+
const stat = lstatSync4(file, { throwIfNoEntry: false });
|
|
448
545
|
digest2.update(JSON.stringify([relative, stat ? stat.mode & 511 : null]));
|
|
449
546
|
if (!stat) {
|
|
450
547
|
digest2.update("absent");
|
|
451
548
|
return;
|
|
452
549
|
}
|
|
453
550
|
if (stat.isSymbolicLink()) {
|
|
454
|
-
digest2.update(JSON.stringify(["link",
|
|
551
|
+
digest2.update(JSON.stringify(["link", readlinkSync2(file)]));
|
|
455
552
|
return;
|
|
456
553
|
}
|
|
457
554
|
if (stat.isDirectory()) {
|
|
458
555
|
digest2.update("directory");
|
|
459
|
-
for (const name of
|
|
556
|
+
for (const name of readdirSync3(file).sort()) {
|
|
460
557
|
if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
|
|
461
558
|
continue;
|
|
462
|
-
visit(
|
|
559
|
+
visit(path4.join(file, name), `${relative}/${name}`, false);
|
|
463
560
|
}
|
|
464
561
|
return;
|
|
465
562
|
}
|
|
@@ -490,7 +587,7 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
490
587
|
}
|
|
491
588
|
};
|
|
492
589
|
for (const root of [...new Set(roots)].sort())
|
|
493
|
-
visit(root, root,
|
|
590
|
+
visit(root, root, path4.basename(root) === "node_modules");
|
|
494
591
|
return digest2.digest("hex");
|
|
495
592
|
}
|
|
496
593
|
|
|
@@ -502,15 +599,15 @@ import {
|
|
|
502
599
|
closeSync as closeSync4,
|
|
503
600
|
constants as constants5,
|
|
504
601
|
fsyncSync as fsyncSync3,
|
|
505
|
-
lstatSync as
|
|
602
|
+
lstatSync as lstatSync6,
|
|
506
603
|
mkdirSync as mkdirSync3,
|
|
507
604
|
openSync as openSync4,
|
|
508
605
|
readFileSync as readFileSync2,
|
|
509
606
|
renameSync as renameSync2,
|
|
510
|
-
unlinkSync as
|
|
607
|
+
unlinkSync as unlinkSync3,
|
|
511
608
|
writeFileSync as writeFileSync3
|
|
512
609
|
} from "node:fs";
|
|
513
|
-
import
|
|
610
|
+
import path6 from "node:path";
|
|
514
611
|
import { promisify } from "node:util";
|
|
515
612
|
|
|
516
613
|
// src/canonical.ts
|
|
@@ -629,15 +726,15 @@ function isValidRelativePath(p) {
|
|
|
629
726
|
}
|
|
630
727
|
|
|
631
728
|
// src/safe-path.ts
|
|
632
|
-
import { lstatSync as
|
|
633
|
-
import
|
|
729
|
+
import { lstatSync as lstatSync5 } from "node:fs";
|
|
730
|
+
import path5 from "node:path";
|
|
634
731
|
function observeSafePath(root, relative) {
|
|
635
732
|
if (!isValidRelativePath(relative)) {
|
|
636
733
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
637
734
|
}
|
|
638
735
|
try {
|
|
639
|
-
const resolvedRoot =
|
|
640
|
-
const rootStat =
|
|
736
|
+
const resolvedRoot = path5.resolve(root);
|
|
737
|
+
const rootStat = lstatSync5(resolvedRoot, { throwIfNoEntry: false });
|
|
641
738
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
642
739
|
return { kind: "unsafe", reason: "root-unavailable" };
|
|
643
740
|
}
|
|
@@ -646,11 +743,11 @@ function observeSafePath(root, relative) {
|
|
|
646
743
|
let current = resolvedRoot;
|
|
647
744
|
const parts = relative.split("/");
|
|
648
745
|
for (const [index, part] of parts.entries()) {
|
|
649
|
-
current =
|
|
650
|
-
if (!current.startsWith(`${resolvedRoot}${
|
|
746
|
+
current = path5.join(current, part);
|
|
747
|
+
if (!current.startsWith(`${resolvedRoot}${path5.sep}`)) {
|
|
651
748
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
652
749
|
}
|
|
653
|
-
const stat =
|
|
750
|
+
const stat = lstatSync5(current, { throwIfNoEntry: false });
|
|
654
751
|
if (!stat)
|
|
655
752
|
return { kind: "absent" };
|
|
656
753
|
if (stat.isSymbolicLink()) {
|
|
@@ -753,7 +850,7 @@ function revisionId(parentRevisionId, manifest) {
|
|
|
753
850
|
return hash("cadence-workspace-revision", parentRevisionId ?? "", manifest);
|
|
754
851
|
}
|
|
755
852
|
function ensureDirectory2(directory) {
|
|
756
|
-
const stat =
|
|
853
|
+
const stat = lstatSync6(directory, { throwIfNoEntry: false });
|
|
757
854
|
if (stat) {
|
|
758
855
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
759
856
|
throw new Error("unsafe-workspace-path");
|
|
@@ -761,28 +858,28 @@ function ensureDirectory2(directory) {
|
|
|
761
858
|
chmodSync2(directory, 448);
|
|
762
859
|
return;
|
|
763
860
|
}
|
|
764
|
-
let cursor =
|
|
765
|
-
while (cursor !==
|
|
766
|
-
const ancestor =
|
|
861
|
+
let cursor = path6.dirname(directory);
|
|
862
|
+
while (cursor !== path6.dirname(cursor)) {
|
|
863
|
+
const ancestor = lstatSync6(cursor, { throwIfNoEntry: false });
|
|
767
864
|
if (ancestor) {
|
|
768
865
|
if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
|
|
769
866
|
throw new Error("unsafe-workspace-path");
|
|
770
867
|
}
|
|
771
868
|
break;
|
|
772
869
|
}
|
|
773
|
-
cursor =
|
|
870
|
+
cursor = path6.dirname(cursor);
|
|
774
871
|
}
|
|
775
872
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
776
|
-
const created =
|
|
873
|
+
const created = lstatSync6(directory);
|
|
777
874
|
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
778
875
|
throw new Error("unsafe-workspace-path");
|
|
779
876
|
}
|
|
780
877
|
chmodSync2(directory, 448);
|
|
781
878
|
}
|
|
782
879
|
function writeAtomic2(target, bytes) {
|
|
783
|
-
const directory =
|
|
880
|
+
const directory = path6.dirname(target);
|
|
784
881
|
ensureDirectory2(directory);
|
|
785
|
-
const temporary =
|
|
882
|
+
const temporary = path6.join(directory, `.${path6.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
786
883
|
const descriptor = openSync4(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
|
|
787
884
|
try {
|
|
788
885
|
writeFileSync3(descriptor, bytes);
|
|
@@ -796,7 +893,7 @@ function writeAtomic2(target, bytes) {
|
|
|
796
893
|
syncDirectory(directory);
|
|
797
894
|
} catch (error) {
|
|
798
895
|
try {
|
|
799
|
-
|
|
896
|
+
unlinkSync3(temporary);
|
|
800
897
|
} catch {}
|
|
801
898
|
throw error;
|
|
802
899
|
}
|
|
@@ -866,10 +963,10 @@ class WorkspaceStore {
|
|
|
866
963
|
constructor(root, artifacts, options = {}) {
|
|
867
964
|
this.#checkCancelled = options.checkCancelled ?? (() => {});
|
|
868
965
|
this.#onMetrics = options.onMetrics;
|
|
869
|
-
if (!
|
|
966
|
+
if (!path6.isAbsolute(root))
|
|
870
967
|
throw new Error("workspace-store-root-invalid");
|
|
871
|
-
this.root =
|
|
872
|
-
this.#revisions =
|
|
968
|
+
this.root = path6.resolve(root);
|
|
969
|
+
this.#revisions = path6.join(this.root, "revisions");
|
|
873
970
|
this.#artifacts = artifacts;
|
|
874
971
|
ensureDirectory2(this.root);
|
|
875
972
|
ensureDirectory2(this.#revisions);
|
|
@@ -877,7 +974,7 @@ class WorkspaceStore {
|
|
|
877
974
|
#revisionPath(id) {
|
|
878
975
|
if (!SHA2562.test(id))
|
|
879
976
|
throw new Error("workspace-revision-id-invalid");
|
|
880
|
-
return
|
|
977
|
+
return path6.join(this.#revisions, id.slice(0, 2), `${id}.json`);
|
|
881
978
|
}
|
|
882
979
|
#storeRevision(parentRevisionId, rawEntries) {
|
|
883
980
|
const entries = stableEntries(rawEntries);
|
|
@@ -890,7 +987,7 @@ class WorkspaceStore {
|
|
|
890
987
|
entries
|
|
891
988
|
};
|
|
892
989
|
const target = this.#revisionPath(id);
|
|
893
|
-
const existing =
|
|
990
|
+
const existing = lstatSync6(target, { throwIfNoEntry: false });
|
|
894
991
|
if (existing) {
|
|
895
992
|
const stored = this.getRevision(id);
|
|
896
993
|
if (JSON.stringify(stored) !== JSON.stringify(revision)) {
|
|
@@ -922,8 +1019,8 @@ class WorkspaceStore {
|
|
|
922
1019
|
return false;
|
|
923
1020
|
}
|
|
924
1021
|
captureBaseline(input) {
|
|
925
|
-
const consumerRoot =
|
|
926
|
-
const rootStat =
|
|
1022
|
+
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
1023
|
+
const rootStat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
|
|
927
1024
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
928
1025
|
throw new Error("unsafe-workspace-path");
|
|
929
1026
|
}
|
|
@@ -936,7 +1033,7 @@ class WorkspaceStore {
|
|
|
936
1033
|
const timeout = input.gitTimeoutMs ?? 30000;
|
|
937
1034
|
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
|
|
938
1035
|
throw new Error("workspace-git-timeout-invalid");
|
|
939
|
-
const consumerRoot =
|
|
1036
|
+
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
940
1037
|
let tracked;
|
|
941
1038
|
try {
|
|
942
1039
|
tracked = parseNullSeparated(execFileSync("git", ["ls-files", "-z", "--cached"], {
|
|
@@ -970,10 +1067,10 @@ class WorkspaceStore {
|
|
|
970
1067
|
}
|
|
971
1068
|
if (observation.kind !== "file")
|
|
972
1069
|
throw new Error("unsafe-workspace-path");
|
|
973
|
-
const absolute =
|
|
1070
|
+
const absolute = path6.join(consumerRoot, ...relative.split("/"));
|
|
974
1071
|
const bytes = readFileSync2(absolute);
|
|
975
1072
|
const artifact = this.#artifacts.put(bytes);
|
|
976
|
-
const stat =
|
|
1073
|
+
const stat = lstatSync6(absolute);
|
|
977
1074
|
entries[relative] = {
|
|
978
1075
|
kind: "file",
|
|
979
1076
|
hash: artifact.hash,
|
|
@@ -994,7 +1091,7 @@ class WorkspaceStore {
|
|
|
994
1091
|
}
|
|
995
1092
|
getRevision(id) {
|
|
996
1093
|
const target = this.#revisionPath(id);
|
|
997
|
-
const stat =
|
|
1094
|
+
const stat = lstatSync6(target, { throwIfNoEntry: false });
|
|
998
1095
|
if (!stat?.isFile() || stat.isSymbolicLink()) {
|
|
999
1096
|
throw new Error("workspace-revision-unavailable");
|
|
1000
1097
|
}
|
|
@@ -1073,7 +1170,7 @@ class WorkspaceStore {
|
|
|
1073
1170
|
}
|
|
1074
1171
|
materialize(revisionIdValue, destination) {
|
|
1075
1172
|
const revision = this.getRevision(revisionIdValue);
|
|
1076
|
-
const root =
|
|
1173
|
+
const root = path6.resolve(destination);
|
|
1077
1174
|
ensureDirectory2(root);
|
|
1078
1175
|
for (const [relative, entry] of Object.entries(revision.entries)) {
|
|
1079
1176
|
this.#checkCancelled();
|
|
@@ -1092,10 +1189,10 @@ class WorkspaceStore {
|
|
|
1092
1189
|
const segments = relative.split("/");
|
|
1093
1190
|
let parent = root;
|
|
1094
1191
|
for (const segment of segments.slice(0, -1)) {
|
|
1095
|
-
parent =
|
|
1192
|
+
parent = path6.join(parent, segment);
|
|
1096
1193
|
ensureDirectory2(parent);
|
|
1097
1194
|
}
|
|
1098
|
-
const target =
|
|
1195
|
+
const target = path6.join(root, ...segments);
|
|
1099
1196
|
if (observation.kind !== "absent") {
|
|
1100
1197
|
throw new Error("workspace-materialize-conflict");
|
|
1101
1198
|
}
|
|
@@ -1107,8 +1204,8 @@ class WorkspaceStore {
|
|
|
1107
1204
|
}
|
|
1108
1205
|
async captureBaselineWithGit(input, signal) {
|
|
1109
1206
|
signal?.throwIfAborted();
|
|
1110
|
-
const consumerRoot =
|
|
1111
|
-
const stat =
|
|
1207
|
+
const consumerRoot = path6.resolve(input.consumerRoot);
|
|
1208
|
+
const stat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
|
|
1112
1209
|
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
1113
1210
|
throw new Error("unsafe-workspace-path");
|
|
1114
1211
|
const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
|
|
@@ -1173,7 +1270,7 @@ try {
|
|
|
1173
1270
|
const result = prepareVerificationEnvironmentIo(root, owner, bindings, profile, environment, checkCancelled, scratch);
|
|
1174
1271
|
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
1175
1272
|
} else if (workerData.operation === "cleanupVerification") {
|
|
1176
|
-
|
|
1273
|
+
removePathSync(workerData.args[0], { recursive: true, force: true });
|
|
1177
1274
|
parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
|
|
1178
1275
|
} else if (workerData.operation === "verificationIdentity") {
|
|
1179
1276
|
const result = verificationEnvironmentDigest(workerData.args[0], checkCancelled);
|