@edgehero/pi-dispatch 4.3.0 → 5.0.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/.env.example +94 -4
- package/README.md +20 -7
- package/deploy/console.service +52 -0
- package/deploy/docker-compose.yml +3 -1
- package/deploy/pi-dispatch-valkey.container +4 -2
- package/deploy/sandbox-gateway.service +23 -0
- package/deploy/worker-env-wrapper.cmd +6 -0
- package/deploy/worker-env-wrapper.sh +10 -1
- package/package.json +117 -19
- package/src/agent-snapshot.mjs +49 -0
- package/src/answerer-runtime.mjs +194 -0
- package/src/answerer.mjs +178 -0
- package/src/approval-policy.mjs +76 -0
- package/src/approval-runtime.mjs +188 -0
- package/src/approval-session.mjs +137 -0
- package/src/approval-state.mjs +170 -0
- package/src/auth-cli.mjs +86 -0
- package/src/auth-connections.mjs +145 -0
- package/src/auth-policy.mjs +32 -0
- package/src/auth-store.mjs +97 -0
- package/src/azure-host.mjs +66 -4
- package/src/azure-prompt.mjs +5 -4
- package/src/backend-conformance.mjs +34 -2
- package/src/backend-local.mjs +103 -34
- package/src/backend-openshell.mjs +1540 -0
- package/src/backend-podman.mjs +63 -2
- package/src/backend-registry.mjs +61 -4
- package/src/backends.mjs +395 -6
- package/src/bullmq-internals.mjs +208 -0
- package/src/canary-cli.mjs +28 -0
- package/src/canary-lock.mjs +41 -0
- package/src/canary-runtime.mjs +237 -0
- package/src/canary.mjs +53 -0
- package/src/cancel-cli.mjs +47 -34
- package/src/cancel-state.mjs +213 -15
- package/src/capabilities.mjs +36 -1
- package/src/capacity-cli.mjs +28 -8
- package/src/capacity-records.mjs +75 -16
- package/src/capacity.mjs +65 -0
- package/src/chat-cli.mjs +288 -0
- package/src/chat-fork.mjs +547 -0
- package/src/chat-mode.mjs +156 -0
- package/src/chat-view.mjs +720 -0
- package/src/checkpoint-paths.mjs +113 -0
- package/src/cli.mjs +128 -41
- package/src/comment-tables.mjs +322 -0
- package/src/community-auth-adapters.mjs +87 -0
- package/src/community-auth-helper.mjs +76 -0
- package/src/community-auth-metadata.mjs +6 -0
- package/src/community-auth-output-pins.mjs +38 -0
- package/src/community-auth-patches.mjs +168 -0
- package/src/community-auth-pins.mjs +38 -0
- package/src/config.mjs +199 -30
- package/src/connection.mjs +83 -7
- package/src/console-answer.mjs +113 -0
- package/src/console-audit.mjs +604 -0
- package/src/console-bootstrap.mjs +159 -0
- package/src/console-client.mjs +848 -0
- package/src/console-probe.mjs +106 -0
- package/src/console-tokens.mjs +459 -0
- package/src/container-names.mjs +51 -0
- package/src/container-spec.mjs +20 -1
- package/src/cpu-reserve.mjs +4 -1
- package/src/defer-reasons.mjs +200 -0
- package/src/defer-write.mjs +208 -0
- package/src/deployment-venue.mjs +23 -6
- package/src/docker-run.mjs +11 -0
- package/src/doctor-bundles.mjs +130 -0
- package/src/doctor-model-proposals.mjs +109 -0
- package/src/doctor-openshell.mjs +238 -0
- package/src/doctor-results.mjs +602 -0
- package/src/doctor-spawner.mjs +215 -0
- package/src/doctor.mjs +1663 -198
- package/src/dollar-budget.mjs +212 -3
- package/src/endpoint-status.mjs +100 -0
- package/src/env-allowlist.mjs +103 -9
- package/src/env-file.mjs +6 -4
- package/src/file-expect.mjs +47 -0
- package/src/forgejo-auth.mjs +4 -0
- package/src/forgejo-host.mjs +127 -31
- package/src/forgejo-prompt.mjs +5 -4
- package/src/forges.mjs +73 -15
- package/src/fork-job.mjs +58 -0
- package/src/fork-policy.mjs +23 -0
- package/src/fork-store.mjs +338 -0
- package/src/get-token.mjs +1 -1
- package/src/git-dirty.mjs +119 -14
- package/src/git-hardening.mjs +85 -0
- package/src/github-host.mjs +31 -6
- package/src/github-prompt.mjs +7 -16
- package/src/gitlab-host.mjs +42 -3
- package/src/gitlab-prompt.mjs +5 -4
- package/src/held-document.mjs +23 -0
- package/src/held-replies.mjs +325 -0
- package/src/held-settle.mjs +266 -0
- package/src/hook-runs.mjs +188 -0
- package/src/host-budget.mjs +8 -5
- package/src/host-clone.mjs +72 -0
- package/src/host-loads.mjs +557 -0
- package/src/host-registry.mjs +66 -6
- package/src/host-telemetry.mjs +92 -0
- package/src/http-body.mjs +78 -0
- package/src/image-preflight.mjs +156 -13
- package/src/import-pi.mjs +60 -172
- package/src/inbox-relay.mjs +482 -0
- package/src/inbox-settle.mjs +196 -0
- package/src/index.mjs +740 -129
- package/src/init.mjs +35 -5
- package/src/job-data-version.mjs +75 -0
- package/src/job-id.mjs +30 -15
- package/src/job-layout.mjs +22 -0
- package/src/job-size.mjs +16 -0
- package/src/live-jobs.mjs +14 -6
- package/src/live-probes.mjs +6 -2
- package/src/live-transcript.mjs +872 -0
- package/src/loaded-record.mjs +609 -0
- package/src/mcp-config.mjs +1 -1
- package/src/mcp-preflight.mjs +36 -5
- package/src/model-attempts.mjs +84 -0
- package/src/model-catalog.mjs +83 -0
- package/src/model-cooldowns.mjs +22 -0
- package/src/model-failover-protocol.mjs +11 -0
- package/src/model-failover.mjs +134 -0
- package/src/model-observation.mjs +50 -0
- package/src/model-proposal-protocol.mjs +21 -0
- package/src/model-proposals.mjs +75 -0
- package/src/model-ref.mjs +67 -10
- package/src/model-roles-file.mjs +24 -0
- package/src/model-roles.mjs +73 -0
- package/src/model-status.mjs +34 -0
- package/src/models-json.mjs +4 -1
- package/src/money.mjs +1 -1
- package/src/oauth-openai.mjs +121 -0
- package/src/older-state.mjs +249 -0
- package/src/on-failure.mjs +17 -13
- package/src/open-browser.mjs +31 -2
- package/src/opener-cli.mjs +135 -0
- package/src/opener-protocol.mjs +27 -0
- package/src/opener-terminal.mjs +128 -0
- package/src/openshell-cli.mjs +155 -0
- package/src/openshell-policy.mjs +163 -0
- package/src/outbox.mjs +14 -0
- package/src/output-cap.mjs +1 -0
- package/src/package-canary-record.mjs +26 -0
- package/src/package-canary.mjs +135 -0
- package/src/package-names.mjs +9 -0
- package/src/package-stage.mjs +418 -0
- package/src/packages.mjs +24 -16
- package/src/parked.mjs +2 -1
- package/src/pause-windows.mjs +136 -17
- package/src/podman-stack.mjs +40 -14
- package/src/portfolio-snapshot.mjs +26 -7
- package/src/prepare-github.mjs +41 -77
- package/src/prepare-local.mjs +32 -2
- package/src/prepare.mjs +45 -11
- package/src/private-store-boundary.mjs +70 -0
- package/src/processor.mjs +1554 -167
- package/src/projects.mjs +23 -0
- package/src/prompt-fence.mjs +18 -0
- package/src/provider-key.mjs +7 -0
- package/src/provider-steering.mjs +1 -0
- package/src/question-context.mjs +35 -0
- package/src/question-group-runtime.mjs +91 -0
- package/src/question-groups.mjs +171 -0
- package/src/queue.mjs +296 -55
- package/src/reason-catalog.mjs +448 -0
- package/src/receiver-status.mjs +230 -0
- package/src/record-reason.mjs +37 -0
- package/src/recovery-queue.mjs +566 -0
- package/src/redact-secrets.mjs +219 -0
- package/src/reserve-estimate.mjs +101 -0
- package/src/reserved-env.mjs +36 -1
- package/src/retention.mjs +67 -0
- package/src/run-budget-ledger.mjs +283 -0
- package/src/run-checkpoint.mjs +627 -0
- package/src/run-container.mjs +749 -33
- package/src/run-history.mjs +791 -45
- package/src/run-mirror.mjs +75 -7
- package/src/run-progress.mjs +751 -0
- package/src/runtime-observations.mjs +3 -1
- package/src/runtime-settings.mjs +272 -37
- package/src/sandbox-cli.mjs +78 -2
- package/src/sandbox-deployment.mjs +51 -0
- package/src/sandbox-gateway-protocol.mjs +378 -0
- package/src/sandbox-hosts.mjs +100 -0
- package/src/sandbox-inventory.mjs +83 -0
- package/src/sandbox-policy.mjs +225 -0
- package/src/sandbox-prepare-requests.mjs +145 -0
- package/src/sandbox-prepare.mjs +285 -0
- package/src/sandbox-refs.mjs +70 -0
- package/src/sandbox-session-monitor.mjs +4 -0
- package/src/sandbox-session-watch.mjs +124 -0
- package/src/sandbox-sessions.mjs +161 -0
- package/src/sandbox-store.mjs +63 -6
- package/src/sandbox-workspace-guard.mjs +39 -0
- package/src/sandbox.mjs +27 -200
- package/src/scheduler-stall-guard.mjs +14 -8
- package/src/schedules.mjs +19 -6
- package/src/secret-bundles.mjs +245 -0
- package/src/secret-profiles.mjs +103 -3
- package/src/secrets.mjs +19 -9
- package/src/service-env.mjs +38 -5
- package/src/service.mjs +638 -106
- package/src/session-key.mjs +60 -0
- package/src/session-store.mjs +168 -10
- package/src/setup-contract.mjs +40 -0
- package/src/setup-egress.mjs +113 -0
- package/src/setup-host-entry.mjs +33 -0
- package/src/setup-host.mjs +242 -0
- package/src/setup-lock.mjs +41 -0
- package/src/size-records.mjs +15 -3
- package/src/start.mjs +1353 -135
- package/src/status-comment.mjs +415 -0
- package/src/stderr-transcript.mjs +318 -0
- package/src/steer-ack.mjs +122 -0
- package/src/steer-cli.mjs +213 -0
- package/src/steer-framing.mjs +185 -0
- package/src/steer-inbox.mjs +693 -0
- package/src/store-version.mjs +139 -0
- package/src/subscription-config.mjs +56 -0
- package/src/subscription-limits.mjs +3 -0
- package/src/subscription-quota.mjs +259 -0
- package/src/subscription-runtime.mjs +123 -0
- package/src/subscription-status.mjs +42 -0
- package/src/subscription-wait.mjs +11 -0
- package/src/subscriptions.mjs +7 -1
- package/src/thinking-level.mjs +19 -0
- package/src/touch-outbox.mjs +61 -0
- package/src/touch-state.mjs +133 -0
- package/src/triggers-file.mjs +299 -39
- package/src/triggers.mjs +782 -40
- package/src/trust-paths.mjs +171 -0
- package/src/up.mjs +398 -51
- package/src/updates-cli.mjs +61 -0
- package/src/updates-look.mjs +127 -0
- package/src/updates-reconcile.mjs +182 -0
- package/src/updates-subscription-adapters.mjs +8 -0
- package/src/updates.mjs +252 -0
- package/src/valkey-auth.mjs +32 -9
- package/src/valkey-endpoint.mjs +14 -4
- package/src/wait-for.mjs +1 -1
- package/src/wait-state.mjs +62 -3
- package/src/watch-board.mjs +38 -0
- package/src/watch-closer.mjs +1 -1
- package/src/watch-contract.mjs +102 -0
- package/src/watch-decision.mjs +78 -0
- package/src/watch-drainer.mjs +14 -0
- package/src/watch-issue-adapter.mjs +44 -0
- package/src/watch-issue.mjs +111 -0
- package/src/watch-record.mjs +10 -0
- package/src/watch-runtime.mjs +167 -0
- package/src/watch-store.mjs +183 -0
- package/src/watch-trace.mjs +34 -0
- package/src/workspace-checkpoint.mjs +1750 -0
- package/src/zero-touch-fixes.mjs +99 -0
- package/src/zero-touch.mjs +225 -0
- package/src/repeat-slot.mjs +0 -13
package/src/import-pi.mjs
CHANGED
|
@@ -29,7 +29,8 @@ import { execFile } from "node:child_process";
|
|
|
29
29
|
import { randomBytes } from "node:crypto";
|
|
30
30
|
import { join, resolve } from "node:path";
|
|
31
31
|
import { promisify } from "node:util";
|
|
32
|
-
import { FROM_HOST, PACKAGES_SUBDIR,
|
|
32
|
+
import { FROM_HOST, PACKAGES_SUBDIR, mergeHostPackages, parsePackagesFile } from "./packages.mjs";
|
|
33
|
+
import { newStagingDir, preparePackage, pruneStagedTrees, stagedTreeName, withStageLock, writeStageManifest } from "./package-stage.mjs";
|
|
33
34
|
import { agentDirFrom, readHostPi } from "./host-pi.mjs";
|
|
34
35
|
import { ENTRY_NAME_RE, copyDirContents, copySkillTree } from "./copy-tree.mjs";
|
|
35
36
|
import { MCP_CONFIG_FILE_NAME, MCP_CONFIG_MAX_BYTES, mcpServerWhere, overlayMcpText, parseOverlayMcp } from "./mcp-config.mjs";
|
|
@@ -489,8 +490,6 @@ async function stagePackages({ fs, exec, out, packagesFile, to, platform = proce
|
|
|
489
490
|
return { error: error.message };
|
|
490
491
|
}
|
|
491
492
|
}
|
|
492
|
-
const { entries, overrides, dropped } = mergeHostPackages(declared, discovered);
|
|
493
|
-
|
|
494
493
|
const packagesRoot = join(to, PACKAGES_SUBDIR);
|
|
495
494
|
const rootExisted = fs.existsSync(packagesRoot);
|
|
496
495
|
fs.mkdirSync(packagesRoot, { recursive: true });
|
|
@@ -498,183 +497,72 @@ async function stagePackages({ fs, exec, out, packagesFile, to, platform = proce
|
|
|
498
497
|
const npmBin = platform === "win32" ? "npm.cmd" : "npm";
|
|
499
498
|
const stagingDirs = [];
|
|
500
499
|
const prepared = [];
|
|
501
|
-
const
|
|
500
|
+
const moved = [];
|
|
502
501
|
const warnings = [];
|
|
503
502
|
const softDropped = [];
|
|
504
503
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
504
|
+
// One stage at a time on this overlay, also from another host on a shared home. Staging names are unique and nothing already
|
|
505
|
+
// staged is touched, so a failure below cannot damage what jobs are loading: the manifest still names the old trees.
|
|
506
|
+
let overrides = [], dropped = [];
|
|
507
|
+
const staged = await withStageLock({ fs, packagesRoot, holder: "import-pi" }, async ({ stillHeld }) => {
|
|
508
|
+
try {
|
|
509
|
+
// Read again under the lock: a person's Adopt on this overlay may have just pinned a version in this file, and staging what the file said a moment ago would undo it.
|
|
510
|
+
const current = haveFile ? parsePackagesFile(fs.readFileSync(packagesFile, "utf8"), packagesFile) : declared;
|
|
511
|
+
const merged = mergeHostPackages(current, discovered);
|
|
512
|
+
const entries = merged.entries;
|
|
513
|
+
({ overrides, dropped } = merged);
|
|
514
|
+
for (const entry of entries) {
|
|
515
|
+
const staging = newStagingDir(packagesRoot);
|
|
516
|
+
stagingDirs.push(staging);
|
|
517
|
+
let source;
|
|
518
|
+
try {
|
|
519
|
+
source = await preparePackage({ fs, exec, out, entry, staging, npmBin, platform, warnings });
|
|
520
|
+
} catch (error) {
|
|
521
|
+
// The declared/discovered split: an entry the operator pinned is fatal, one we inferred from
|
|
522
|
+
// their pi setup is dropped by name so the rest of the stage still lands.
|
|
523
|
+
if (entry.from === FROM_HOST) {
|
|
524
|
+
softDropped.push({ name: entry.name, reason: error.message });
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
throw error;
|
|
519
528
|
}
|
|
520
|
-
|
|
529
|
+
prepared.push({ entry, source });
|
|
521
530
|
}
|
|
522
|
-
prepared.push({ entry, source });
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Renames happen only after EVERY package has passed, so a failure on the last one cannot leave the
|
|
526
|
-
// earlier ones swapped in beside a stale manifest.
|
|
527
|
-
for (const { entry, source } of prepared) {
|
|
528
|
-
const dest = join(packagesRoot, entry.dir);
|
|
529
|
-
fs.rmSync(dest, { recursive: true, force: true }); // replace a previous stage of the same package
|
|
530
|
-
// renameSync, never copyTree: copyTree's symlink guard uses statSync, which FOLLOWS links, so it
|
|
531
|
-
// would copy the target of every node_modules/.bin symlink instead of skipping it.
|
|
532
|
-
fs.renameSync(source, dest);
|
|
533
|
-
renamed.push(dest);
|
|
534
|
-
}
|
|
535
|
-
} catch (error) {
|
|
536
|
-
for (const dest of renamed) fs.rmSync(dest, { recursive: true, force: true });
|
|
537
|
-
for (const staging of stagingDirs) fs.rmSync(staging, { recursive: true, force: true });
|
|
538
|
-
if (!rootExisted) fs.rmSync(packagesRoot, { recursive: true, force: true });
|
|
539
|
-
return { error: `${error.message}\n Nothing was staged -- fix ${packagesFile} (or the package) and re-run with --with-packages.` };
|
|
540
|
-
}
|
|
541
531
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
// `name@version` token already validated against NPM_NAME_RE + EXACT_VERSION_RE; that is the property
|
|
570
|
-
// npmExecOptions relies on below.
|
|
571
|
-
//
|
|
572
|
-
// --ignore-scripts is load-bearing: without it the lifecycle scripts of this package AND of every
|
|
573
|
-
// transitive dependency would run as the operator, on the operator's host, at stage time.
|
|
574
|
-
// --omit=peer because pi aliases its own packages for extensions at load time, so a staged peer
|
|
575
|
-
// copy is ignored dead weight -- and a floating pi version at that (CONST-PI-VERSION-PINNED).
|
|
576
|
-
// --install-strategy=nested asks npm to keep every dependency inside the package dir; step 4 below
|
|
577
|
-
// ASSERTS the result rather than trusting the flag, whose name and default have moved across npm
|
|
578
|
-
// versions.
|
|
579
|
-
const args = [
|
|
580
|
-
"install",
|
|
581
|
-
`${entry.name}@${entry.version}`,
|
|
582
|
-
"--omit=dev",
|
|
583
|
-
"--omit=peer",
|
|
584
|
-
"--omit=optional",
|
|
585
|
-
"--ignore-scripts",
|
|
586
|
-
"--install-strategy=nested",
|
|
587
|
-
"--no-audit",
|
|
588
|
-
"--no-fund",
|
|
589
|
-
"--loglevel=error",
|
|
590
|
-
];
|
|
591
|
-
out(` staging ${entry.name}@${entry.version} -> packages/${entry.dir}\n`);
|
|
592
|
-
try {
|
|
593
|
-
await exec(npmBin, args, npmExecOptions(platform, staging));
|
|
594
|
-
} catch (error) {
|
|
595
|
-
const detail = String(error?.stderr ?? error?.message ?? "").trim();
|
|
596
|
-
throw new Error(`npm install failed for ${entry.name}@${entry.version}: ${detail}`);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const source = join(staging, "node_modules", entry.name);
|
|
600
|
-
let pkg;
|
|
601
|
-
try {
|
|
602
|
-
pkg = JSON.parse(fs.readFileSync(join(source, "package.json"), "utf8"));
|
|
603
|
-
} catch {
|
|
604
|
-
throw new Error(`${entry.name}@${entry.version}: npm reported success but there is no readable package.json at ${join(source, "package.json")}`);
|
|
605
|
-
}
|
|
606
|
-
if (pkg.version !== entry.version) {
|
|
607
|
-
throw new Error(`${entry.name}: npm staged version ${JSON.stringify(pkg.version)}, not the pinned ${JSON.stringify(entry.version)} (CONST-PI-VERSION-PINNED)`);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Dependency completeness -- catches hoisting whatever npm's flag defaults do this month. A
|
|
611
|
-
// hoisted dependency would only surface as an import failure inside a job, hours later.
|
|
612
|
-
for (const dep of Object.keys(pkg.dependencies ?? {})) {
|
|
613
|
-
if (!fs.existsSync(join(source, "node_modules", dep))) {
|
|
614
|
-
throw new Error(`${entry.name}: dependency "${dep}" is not inside the package dir -- npm hoisted it out, so the staged copy could not import it at run time (no network, no install)`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// A package that contributes no pi resources loads as a silent no-op; staging exists to turn that
|
|
619
|
-
// run-time nothing into a stage-time error the operator can act on.
|
|
620
|
-
const manifest = pkg.pi !== null && typeof pkg.pi === "object" ? pkg.pi : null;
|
|
621
|
-
const hasResourceDir = RESOURCE_DIRS.some((name) => fs.existsSync(join(source, name)));
|
|
622
|
-
if (!manifest && !hasResourceDir) {
|
|
623
|
-
throw new Error(`${entry.name} is not a pi package -- no "pi" manifest in package.json and none of ${RESOURCE_DIRS.join("/")}; it would load as a silent no-op`);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Containment: manifest entries are resolved relative to the package dir at job time, so one that
|
|
627
|
-
// climbs out of it would reach the rest of the read-only overlay.
|
|
628
|
-
const escaping = manifest && findEscapingEntry(manifest);
|
|
629
|
-
if (escaping) {
|
|
630
|
-
throw new Error(`${entry.name}: pi manifest entry ${JSON.stringify(escaping)} leaves the package dir (no ".." segment, no leading "/")`);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Warn, do not refuse: --ignore-scripts means a build/postinstall step did NOT run and an optional
|
|
634
|
-
// dependency was NOT fetched, so such a package is staged INCOMPLETE and may fail at run time.
|
|
635
|
-
const scriptKeys = ["install", "preinstall", "postinstall"].filter((key) => typeof pkg.scripts?.[key] === "string");
|
|
636
|
-
const hasOptional = Object.keys(pkg.optionalDependencies ?? {}).length > 0;
|
|
637
|
-
if (scriptKeys.length > 0 || hasOptional) {
|
|
638
|
-
const declares = [...scriptKeys.map((key) => `scripts.${key}`), ...(hasOptional ? ["optionalDependencies"] : [])].join(", ");
|
|
639
|
-
warnings.push({ dir: entry.dir, reason: `${entry.name} declares ${declares} -- staged with --ignore-scripts, so it is INCOMPLETE and may fail at run time` });
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return source;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* The exec options for one `npm install`: always `cwd: <staging>` (that IS the install target now that
|
|
647
|
-
* `--prefix` is gone), plus `shell: true` on win32 and nowhere else.
|
|
648
|
-
*
|
|
649
|
-
* WHY shell:true is REQUIRED on win32: npm ships there as `npm.cmd`, and since Node 18.20.2 / 20.12.2
|
|
650
|
-
* (CVE-2024-27980) spawning a `.cmd`/`.bat` WITHOUT a shell throws EINVAL outright. This package floors at
|
|
651
|
-
* Node >=22.19, so every Node it can run on has that behaviour -- without this, `--with-packages` fails on
|
|
652
|
-
* every Windows host with a misleading "spawn npm.cmd EINVAL" and the branch above is dead on arrival.
|
|
653
|
-
*
|
|
654
|
-
* WHY shell:true is SAFE HERE SPECIFICALLY, which it would NOT be in general: with `--prefix` replaced by
|
|
655
|
-
* `cwd`, argv holds no filesystem path at all -- only literal flags this file spells out, plus the single
|
|
656
|
-
* `name@version` token, and BOTH halves of that token were validated before anything was created (packages.mjs
|
|
657
|
-
* rejects any name failing NPM_NAME_RE and any version failing EXACT_VERSION_RE, neither of which admits a
|
|
658
|
-
* space, quote, or cmd metacharacter). So no operator-supplied string that could survive as shell syntax ever
|
|
659
|
-
* reaches the command line. Re-introducing a path -- or loosening either regex -- breaks that argument, so
|
|
660
|
-
* this option must be revisited together with them.
|
|
661
|
-
*/
|
|
662
|
-
function npmExecOptions(platform, staging) {
|
|
663
|
-
return platform === "win32" ? { cwd: staging, shell: true } : { cwd: staging };
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/** The first string anywhere in a `pi` manifest that leaves the package dir, or null when all are contained. */
|
|
667
|
-
function findEscapingEntry(value) {
|
|
668
|
-
if (typeof value === "string") {
|
|
669
|
-
return /^[\\/]/.test(value) || value.split(/[\\/]/).includes("..") ? value : null;
|
|
670
|
-
}
|
|
671
|
-
if (Array.isArray(value) || (value !== null && typeof value === "object")) {
|
|
672
|
-
for (const child of Object.values(value)) {
|
|
673
|
-
const hit = findEscapingEntry(child);
|
|
674
|
-
if (hit) return hit;
|
|
532
|
+
// Moves happen only after EVERY package has passed. Each goes to a new directory, so a failure on the last one
|
|
533
|
+
// removes only what this run made, and what jobs are loading was never touched.
|
|
534
|
+
const trees = new Map();
|
|
535
|
+
for (const { entry, source } of prepared) {
|
|
536
|
+
const dir = stagedTreeName(entry.dir, entry.version);
|
|
537
|
+
// renameSync, never copyTree: copyTree's symlink guard uses statSync, which FOLLOWS links, so it
|
|
538
|
+
// would copy the target of every node_modules/.bin symlink instead of skipping it.
|
|
539
|
+
fs.renameSync(source, join(packagesRoot, dir));
|
|
540
|
+
moved.push(join(packagesRoot, dir));
|
|
541
|
+
trees.set(entry, dir);
|
|
542
|
+
}
|
|
543
|
+
// A dropped entry never made it into `prepared`, so it must not appear in the receipt either.
|
|
544
|
+
const landed = entries.filter((entry) => trees.has(entry)).map((entry) => ({ ...entry, dir: trees.get(entry) }));
|
|
545
|
+
// `from` and nothing more. This receipt is bind-mounted into every job container, so it must never carry
|
|
546
|
+
// an install path off the operator's machine -- provenance is the fact doctor needs, the host path is not.
|
|
547
|
+
const stageManifest = { stagedAt: new Date().toISOString(), packages: landed.map(({ name, version, dir, from }) => ({ name, version, dir, from })) };
|
|
548
|
+
if (!stillHeld()) throw new Error("another stage took over this overlay's lock while this one ran");
|
|
549
|
+
writeStageManifest({ fs, packagesRoot, manifest: stageManifest });
|
|
550
|
+
pruneStagedTrees({ fs, packagesRoot, manifest: stageManifest });
|
|
551
|
+
// A warning names its package's base directory, which only this stage knows the tree of.
|
|
552
|
+
for (const warning of warnings) warning.dir = [...trees].find(([entry]) => entry.dir === warning.dir)?.[1] ?? warning.dir;
|
|
553
|
+
return { landed };
|
|
554
|
+
} catch (error) {
|
|
555
|
+
for (const dest of moved) fs.rmSync(dest, { recursive: true, force: true });
|
|
556
|
+
return { failed: error };
|
|
557
|
+
} finally {
|
|
558
|
+
for (const staging of stagingDirs) fs.rmSync(staging, { recursive: true, force: true });
|
|
675
559
|
}
|
|
560
|
+
}).catch((error) => ({ failed: error }));
|
|
561
|
+
if (staged.failed) {
|
|
562
|
+
if (!rootExisted && fs.readdirSync(packagesRoot).length === 0) fs.rmSync(packagesRoot, { recursive: true, force: true });
|
|
563
|
+
return { error: `${staged.failed.message}\n Nothing was staged -- fix ${packagesFile} (or the package) and re-run with --with-packages.` };
|
|
676
564
|
}
|
|
677
|
-
return
|
|
565
|
+
return { packages: staged.landed, warnings, overrides, dropped: [...dropped, ...softDropped] };
|
|
678
566
|
}
|
|
679
567
|
|
|
680
568
|
function flagValue(argv, flag) {
|