@rightkit/release 0.6.13 → 0.6.15
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 +49 -1
- package/adapter-env.mjs +165 -0
- package/android-ndk.mjs +5 -1
- package/android-release.mjs +73 -8
- package/app-check.mjs +22 -8
- package/app-closure.mjs +214 -0
- package/assemble-app-command.mjs +24 -7
- package/atomic-write.mjs +57 -0
- package/bin/rightkit-gate.mjs +1193 -337
- package/broker-client.mjs +106 -0
- package/broker-exception.mjs +211 -0
- package/broker-handshake.mjs +160 -0
- package/broker-pins.mjs +27 -8
- package/broker-wire.mjs +262 -0
- package/build-launch.mjs +364 -12
- package/build-ownership.mjs +117 -49
- package/build-release.mjs +3 -2
- package/cache-command.mjs +7 -2
- package/candidate-build-receipt.mjs +96 -0
- package/candidate-receipt-command.mjs +29 -0
- package/cargo-contract.mjs +4 -3
- package/cargo-ledger.mjs +1 -1
- package/cargo-license-notices.mjs +2 -2
- package/cargo-shapes.mjs +332 -0
- package/cargo-target-shim.mjs +44 -5
- package/cargo-target.mjs +5 -0
- package/carried-adapter-run.mjs +31 -0
- package/carried-cargo-run.mjs +9 -0
- package/carried-cargo.mjs +237 -0
- package/carrier-census.mjs +124 -0
- package/class-a-launch-guard.mjs +266 -33
- package/cli/right-release.mjs +2 -0
- package/clippy-route.mjs +23 -11
- package/compile-inputs.mjs +9 -12
- package/control-plane.mjs +18 -0
- package/deps-cargo-lock.mjs +208 -0
- package/deps.mjs +5 -0
- package/deterministic-scans.mjs +273 -44
- package/development-signing.mjs +14 -1
- package/development.mjs +112 -26
- package/direct-origin.mjs +107 -0
- package/disk-command.mjs +1 -0
- package/disk-policy.mjs +18 -3
- package/freshness-census.mjs +206 -58
- package/gate-executor.mjs +30 -12
- package/gate-stages.mjs +93 -16
- package/github-release.mjs +2 -1
- package/github-upload-gate.mjs +2 -1
- package/install-tauri-authenticode.mjs +346 -0
- package/ios-dev.mjs +77 -23
- package/ios-package-recipe.mjs +100 -41
- package/linux-artifacts.mjs +17 -2
- package/linux-qualifier.mjs +83 -0
- package/local-publish.mjs +36 -14
- package/local-runtime.mjs +2 -2
- package/lock-identity.mjs +138 -0
- package/lsclean.sh +0 -0
- package/managed-cargo.mjs +161 -44
- package/managed-roots.mjs +130 -12
- package/media-sidecar-catalog.mjs +1 -1
- package/native-adapters.mjs +411 -0
- package/native-helpers.mjs +26 -18
- package/native-tool-planning.mjs +94 -0
- package/ownership-mark.mjs +182 -0
- package/pack-digest.mjs +122 -0
- package/package.json +12 -12
- package/pipeline-receipts.mjs +302 -302
- package/platform-dir.mjs +14 -0
- package/pnpm-adapter-grammar.mjs +226 -0
- package/progress-control.mjs +7 -2
- package/provenance-real-receipt.pins.json +7 -0
- package/provenance-verify.mjs +262 -1
- package/publication-outcomes.mjs +21 -0
- package/publish-update.mjs +2 -1
- package/qa-journeys-prebuilt-run.mjs +18 -0
- package/qa-journeys-prebuilt.mjs +148 -0
- package/qa-snapshot-run.mjs +53 -0
- package/qa-snapshot.mjs +164 -0
- package/qualification-verify.mjs +144 -0
- package/qualify-reuse.mjs +38 -0
- package/recipe-build.mjs +16 -3
- package/release-qa-audit.mjs +2 -2
- package/release-qa-command.mjs +2 -1
- package/release-qa-receipt.mjs +3 -2
- package/release-state.mjs +3 -2
- package/release.mjs +14 -3
- package/rightkit-drift.mjs +27 -5
- package/rightkit-versions.json +15 -11
- package/rustright-names.mjs +32 -0
- package/scan-tool-enrolment.mjs +167 -0
- package/sign-windows.mjs +212 -112
- package/signing-files.mjs +35 -0
- package/signing-identity.mjs +7 -0
- package/snapshot-broker.mjs +528 -57
- package/snapshot-check.mjs +95 -11
- package/snapshot-metadata.mjs +123 -0
- package/snapshot-request.mjs +389 -96
- package/stage-binary.mjs +22 -4
- package/stage-commands.mjs +56 -13
- package/stage-sidecar.mjs +93 -21
- package/suite-signing-policy.mjs +657 -0
- package/suite-signing.mjs +223 -0
- package/swift-scratch.mjs +25 -0
- package/tauri-locked.mjs +25 -0
- package/test-command.mjs +164 -29
- package/unit-gate.mjs +201 -31
- package/upload-large.mjs +0 -0
- package/web-build.mjs +2 -2
- package/win-package-recipe.mjs +26 -2
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +0 -6
package/stage-binary.mjs
CHANGED
|
@@ -32,8 +32,26 @@ export function stageDevelopmentBinary({ root, source, name = path.basename(sour
|
|
|
32
32
|
const profileDirectory = (profile) => (profile === "dev" || profile === "test" ? "debug" : profile === "bench" ? "release" : profile);
|
|
33
33
|
|
|
34
34
|
/** `{ source }` or `{ manifestPath, bin, targetTriple?, profile? }` (resolved through Cargo's own target directory, broker or not). */
|
|
35
|
-
export function resolveStageSource(entry, { root, resolveTarget = resolveTargetRoot, platform = process.platform } = {}) {
|
|
36
|
-
if (entry.source)
|
|
35
|
+
export function resolveStageSource(entry, { root, resolveTarget = resolveTargetRoot, platform = process.platform, managedOutputDirs = [] } = {}) {
|
|
36
|
+
if (entry.source) {
|
|
37
|
+
const from = path.resolve(root, entry.source);
|
|
38
|
+
// GB-60: on a receipt-managed build (managedOutputDirs: the output directories signed receipts of this run named) a source path under a `target`
|
|
39
|
+
// directory is the Cargo target directory's, which the broker keeps elsewhere: it is migrated onto the receipt's directory when exactly one is known,
|
|
40
|
+
// and refused when the receipts name several (the entry must then say which: manifestPath and bin). A source path outside any target directory is a
|
|
41
|
+
// source-tree file and is used as written.
|
|
42
|
+
const unique = [...new Set(managedOutputDirs)];
|
|
43
|
+
const segments = path.relative(path.resolve(root), from).split(path.sep).filter(Boolean);
|
|
44
|
+
const at = segments.indexOf("target");
|
|
45
|
+
if (unique.length && at >= 0 && !segments.includes("..")) {
|
|
46
|
+
if (unique.length > 1) {
|
|
47
|
+
const error = new Error(`development stage source ${JSON.stringify(entry.source)} is under a Cargo target directory, but this build's receipts name ${unique.length} output directories; name the workspace with manifestPath and bin instead`);
|
|
48
|
+
error.code = "DEV_OUTPUT_AMBIGUOUS";
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
return path.join(unique[0], ...segments.slice(at + 1));
|
|
52
|
+
}
|
|
53
|
+
return from;
|
|
54
|
+
}
|
|
37
55
|
if (!entry.manifestPath || !entry.bin) throw new Error("development stage entry needs source, or manifestPath and bin");
|
|
38
56
|
const target = resolveTarget(path.resolve(root, entry.manifestPath));
|
|
39
57
|
const file = platform === "win32" ? `${entry.bin}.exe` : entry.bin;
|
|
@@ -51,9 +69,9 @@ export function validateStageEntries(entries, label) {
|
|
|
51
69
|
return entries;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
export function stageDevelopmentBinaries(entries, { root, platform = process.platform, resolveTarget } = {}) {
|
|
72
|
+
export function stageDevelopmentBinaries(entries, { root, platform = process.platform, resolveTarget, managedOutputDirs = [] } = {}) {
|
|
55
73
|
return entries.map((entry) => {
|
|
56
|
-
const source = resolveStageSource(entry, { root, platform, resolveTarget });
|
|
74
|
+
const source = resolveStageSource(entry, { root, platform, resolveTarget, managedOutputDirs });
|
|
57
75
|
const name = entry.name ?? path.basename(source);
|
|
58
76
|
return stageDevelopmentBinary({ root, source, name, triple: entry.targetTriple ?? null });
|
|
59
77
|
});
|
package/stage-commands.mjs
CHANGED
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
// localTestPlan and the pack argv builders), so the gate (which chooses the receipts to carry) and the hosted admission (which checks
|
|
3
3
|
// the signed requestedArgv of each) share one definition and cannot drift from the stages. EVERY command of a stage must be covered by
|
|
4
4
|
// a signed receipt of its own job; a missing command is a refusal that names it.
|
|
5
|
+
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { CARGO_METADATA_ARGS, cargoPackageArgs, localTestPlan, npmPackArgs, qualifyFmtArgs } from "./local-publish.mjs";
|
|
7
8
|
import { SWAP_HELPER_CARGO_ARGS, SWAP_HELPER_METADATA_ARGS, SWAP_HELPER_PACKAGE } from "./swap-helper-bundle.mjs";
|
|
9
|
+
import { carriedRequestOf } from "./snapshot-request.mjs";
|
|
8
10
|
|
|
9
11
|
/** Stands for the one argument that is a fresh absolute scratch path (`pnpm pack --pack-destination <mkdtemp>`). */
|
|
10
12
|
export const ANY_ABSOLUTE_PATH = Object.freeze({ anyAbsolutePath: true });
|
|
11
13
|
const PNPM = (...argvs) => ({ kind: "adapter", adapter: "rightkit-pnpm", argvs });
|
|
12
|
-
|
|
14
|
+
/**
|
|
15
|
+
* A cargo command as a broker host receives it. Every Cargo call of a snapshot stage is carried (carried-cargo.mjs): `--manifest-path` is
|
|
16
|
+
* not sent (the call runs from the manifest's directory) and a build or run is sent with `--locked`, so the signed `requestedArgv` is
|
|
17
|
+
* that argv and the signed `workspace` is that directory of the checkout, never the spelling the stage's code wrote. `cwd` is the
|
|
18
|
+
* directory the stage runs the command from, relative to the repository root; the row keeps the directory the call runs in.
|
|
19
|
+
*/
|
|
20
|
+
const CARGO = (cwd, ...argvs) => {
|
|
21
|
+
const carried = argvs.map((argv) => carriedRequestOf(argv, cwd) ?? { argv, cwd });
|
|
22
|
+
if (new Set(carried.map((c) => c.cwd)).size !== 1) throw new Error(`the spellings of one cargo command run in different directories: ${argvs.map((a) => a.join(" ")).join(" | ")}`);
|
|
23
|
+
return { kind: "cargo", argvs: carried.map((c) => c.argv), cwd: carried[0].cwd };
|
|
24
|
+
};
|
|
13
25
|
|
|
14
26
|
/** Readable name of an expected command (scratch paths shown as <scratch>). */
|
|
15
27
|
export const describeCommand = (row) => `${row.kind === "adapter" ? "pnpm" : "cargo"} ${row.argvs[0].map((a) => (typeof a === "string" ? a : "<scratch>")).join(" ")}`;
|
|
@@ -22,16 +34,18 @@ export const describeCommand = (row) => `${row.kind === "adapter" ? "pnpm" : "ca
|
|
|
22
34
|
export function expectedStageCommands({ kind, unit, config, manifest, platform }) {
|
|
23
35
|
const dir = kind === "npm" ? `packages/${unit.slice("@rightkit/".length)}` : `crates/${unit}`;
|
|
24
36
|
const plan = localTestPlan({ kind, unit, dir, config, manifest, batch: false, platform });
|
|
25
|
-
|
|
37
|
+
// The stage's own working directories (local-publish.mjs): a crate's cargo steps run in its workspace; a consumer row's (named by
|
|
38
|
+
// --manifest-path from the repository root) and the swap helper's run from the repository root.
|
|
39
|
+
const crateDir = `crates/${unit}`;
|
|
40
|
+
const compile = plan.steps.map((step) => (step.command === "pnpm" ? PNPM(step.args) : CARGO(step.args.includes("--manifest-path") ? "." : crateDir, step.args)));
|
|
26
41
|
const pack = [];
|
|
27
42
|
if (kind === "npm") {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} else pack.push(PNPM(npmPackArgs(dir, ANY_ABSOLUTE_PATH)));
|
|
43
|
+
// The swap helper builds and signs a binary into its own directory of the snapshot (a declared stage output), then packs from there like any unit.
|
|
44
|
+
if (unit === SWAP_HELPER_PACKAGE) pack.push(CARGO(".", SWAP_HELPER_CARGO_ARGS), CARGO(".", SWAP_HELPER_METADATA_ARGS));
|
|
45
|
+
pack.push(PNPM(npmPackArgs(dir, ANY_ABSOLUTE_PATH)));
|
|
32
46
|
} else {
|
|
33
|
-
compile.unshift(CARGO(qualifyFmtArgs(unit)));
|
|
34
|
-
pack.push(CARGO(cargoPackageArgs(unit, true)), CARGO([...CARGO_METADATA_ARGS], CARGO_METADATA_ARGS.filter((arg) => arg !== "--offline")));
|
|
47
|
+
compile.unshift(CARGO(crateDir, qualifyFmtArgs(unit)));
|
|
48
|
+
pack.push(CARGO(crateDir, cargoPackageArgs(unit, true)), CARGO(crateDir, [...CARGO_METADATA_ARGS], CARGO_METADATA_ARGS.filter((arg) => arg !== "--offline")));
|
|
35
49
|
}
|
|
36
50
|
return { compile, package: pack };
|
|
37
51
|
}
|
|
@@ -39,21 +53,50 @@ export function expectedStageCommands({ kind, unit, config, manifest, platform }
|
|
|
39
53
|
const argvMatches = (expected, actual) => Array.isArray(actual) && expected.length === actual.length
|
|
40
54
|
&& expected.every((want, i) => (want === ANY_ABSOLUTE_PATH ? typeof actual[i] === "string" && (path.posix.isAbsolute(actual[i]) || /^[A-Za-z]:[\\/]/.test(actual[i])) : want === actual[i]));
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
const posixOf = (p) => String(p ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
|
|
57
|
+
const realOf = (p) => { try { return fs.realpathSync.native(p); } catch { return path.resolve(p); } };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The repository root a job ran under, given the directory its row runs in: the signed `workspace` of a carried cargo call is that
|
|
61
|
+
* directory of the checkout (`crates/<x>`), so the root is the workspace with the row's `cwd` removed. Null when the workspace is not that
|
|
62
|
+
* directory. A row with no `cwd` (a pnpm job) ran at the root itself.
|
|
63
|
+
*/
|
|
64
|
+
export function rootOfWorkspace(workspace, cwd) {
|
|
65
|
+
const where = posixOf(workspace);
|
|
66
|
+
if (cwd === undefined || cwd === ".") return where;
|
|
67
|
+
return where.endsWith(`/${cwd}`) ? where.slice(0, -(cwd.length + 1)) : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Whether the signed workspace is the directory the row runs in. With `checkout` (the gate's own primary checkout) it must be exactly that directory of it. */
|
|
71
|
+
function workspaceIs(workspace, row, checkout) {
|
|
72
|
+
if (row.cwd === undefined) return true;
|
|
73
|
+
if (typeof workspace !== "string" || !workspace) return false;
|
|
74
|
+
if (checkout) {
|
|
75
|
+
const want = path.join(checkout, ...(row.cwd === "." ? [] : row.cwd.split("/")));
|
|
76
|
+
return posixOf(workspace) === posixOf(want) || posixOf(realOf(workspace)) === posixOf(realOf(want));
|
|
77
|
+
}
|
|
78
|
+
return rootOfWorkspace(workspace, row.cwd) !== null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether a signed payload's request is this command: request.kind, adapter (when any), requestedArgv, and for a cargo command the
|
|
83
|
+
* directory it ran in (`row.cwd`: a different crate's call cannot satisfy it). `checkout` pins that directory exactly (the gate knows its
|
|
84
|
+
* checkout; the hosted admission does not and checks the suffix).
|
|
85
|
+
*/
|
|
86
|
+
export function requestIs(payload, row, { checkout = null } = {}) {
|
|
44
87
|
const request = payload?.request;
|
|
45
|
-
return Boolean(request && typeof request === "object" && request.kind === row.kind && (row.adapter === undefined || request.adapter === row.adapter) && row.argvs.some((argv) => argvMatches(argv, request.requestedArgv)));
|
|
88
|
+
return Boolean(request && typeof request === "object" && request.kind === row.kind && (row.adapter === undefined || request.adapter === row.adapter) && row.argvs.some((argv) => argvMatches(argv, request.requestedArgv)) && workspaceIs(request.workspace, row, checkout));
|
|
46
89
|
}
|
|
47
90
|
|
|
48
91
|
/**
|
|
49
92
|
* Assigns each job (`{ id, payload }`) to a distinct command of the stage. Returns { assigned: Map(id -> row), unmatched: [id...],
|
|
50
93
|
* missing: [row...] }: a job that is none of the commands is unmatched; a command no job covers is missing.
|
|
51
94
|
*/
|
|
52
|
-
export function assignJobs(rows, jobs) {
|
|
95
|
+
export function assignJobs(rows, jobs, options = {}) {
|
|
53
96
|
const free = rows.map((row) => ({ row, taken: false }));
|
|
54
97
|
const assigned = new Map(), unmatched = [];
|
|
55
98
|
for (const job of jobs) {
|
|
56
|
-
const slot = free.find((s) => !s.taken && requestIs(job.payload, s.row));
|
|
99
|
+
const slot = free.find((s) => !s.taken && requestIs(job.payload, s.row, options));
|
|
57
100
|
if (slot) { slot.taken = true; assigned.set(job.id, slot.row); } else unmatched.push(job.id);
|
|
58
101
|
}
|
|
59
102
|
return { assigned, unmatched, missing: free.filter((s) => !s.taken).map((s) => s.row) };
|
package/stage-sidecar.mjs
CHANGED
|
@@ -38,7 +38,9 @@ import os from "node:os";
|
|
|
38
38
|
import path from "node:path";
|
|
39
39
|
import { spawnSync as nodeSpawnSync } from "./background-process.mjs";
|
|
40
40
|
import { resolveCommand } from "./development.mjs";
|
|
41
|
-
import {
|
|
41
|
+
import { carriedCargoMapOf } from "./carried-cargo.mjs";
|
|
42
|
+
import { BUILD_LAUNCH_REFUSED, buildLaunch } from "./build-launch.mjs";
|
|
43
|
+
import { directHost, managedHost, runCargoSync } from "./managed-cargo.mjs";
|
|
42
44
|
import { APPROVAL_MACHO_STRIP_LINKEDIT_V1, APPROVAL_RAW_SHA256, approvalDigest } from "./macho-approval-hash.mjs";
|
|
43
45
|
import {
|
|
44
46
|
bundledFileName, planMediaSidecar, sourceOfferText, stagedFileName, validateMediaSidecarConfig, verifyStagedSidecars, writeMediaSidecarNotices,
|
|
@@ -583,6 +585,9 @@ const tail = (text, lines = 12) => String(text ?? "").trim().split("\n").slice(-
|
|
|
583
585
|
/** Default process runner: `rustright` resolves through the installed runtime client (its Windows shim is rustright.cmd). */
|
|
584
586
|
export function defaultRun(command, args, { cwd, env = process.env } = {}) {
|
|
585
587
|
if (command === "rightkit") throw new Error("`rightkit` is the retired name of the RustRight client (rustright#91); use rustright (stage: { cmd: \"rustright\" })");
|
|
588
|
+
// Every `rustright cargo` of this module is a right-release Cargo call: the one carried seam (managed-cargo.mjs runCargoSync) takes it,
|
|
589
|
+
// so a snapshot stage's call is carried and verified, and any other host runs it as before.
|
|
590
|
+
if (command === "rustright" && args[0] === "cargo") return runCargoSync(args.slice(1), { cwd, env, encoding: "utf8", maxBuffer: 512 * 1024 * 1024, shell: false, windowsHide: true, stdio: ["inherit", "pipe", "inherit"] });
|
|
586
591
|
const launch = command === "rustright" ? resolveCommand(command, args, { root: cwd, env }) : { command, args };
|
|
587
592
|
return nodeSpawnSync(launch.command, launch.args, {
|
|
588
593
|
cwd, env: { ...env, ...(launch.env ?? {}) }, encoding: "utf8", maxBuffer: 512 * 1024 * 1024, shell: false, windowsHide: true, stdio: ["inherit", "pipe", "inherit"],
|
|
@@ -668,7 +673,7 @@ function resolveCatalogFile(spec, ctx, kind) {
|
|
|
668
673
|
if (spec.via === "absolute") return requireSnapshotSource(ctx, spec.file, `the ${label} catalog`);
|
|
669
674
|
if (spec.via === "path") return requireSnapshotSource(ctx, path.resolve(ctx.appRoot, spec.file), `the ${label} catalog`);
|
|
670
675
|
if (spec.via === "crate") {
|
|
671
|
-
const meta = ctx.run("rustright", ["cargo", "metadata", "--format-version", "1"], { cwd: ctx.appRoot, env: ctx.env });
|
|
676
|
+
const meta = ctx.run("rustright", ["cargo", "metadata", "--frozen", "--format-version", "1"], { cwd: ctx.appRoot, env: ctx.env });
|
|
672
677
|
let packages;
|
|
673
678
|
try { packages = JSON.parse(meta.stdout).packages; } catch { /* refused below */ }
|
|
674
679
|
if (meta?.status !== 0 || !Array.isArray(packages)) fail("STAGE_SIDECAR_CATALOG_UNREADABLE", `the ${label} catalog crate ${spec.crate} cannot be located: the workspace metadata query failed`, { crate: spec.crate });
|
|
@@ -760,12 +765,16 @@ function cleanDest(ctx, entry, dest, { directory = false } = {}) {
|
|
|
760
765
|
function carryMtime(ctx, dest, sources) {
|
|
761
766
|
const { fs } = ctx;
|
|
762
767
|
if (typeof fs.utimesSync !== "function") return;
|
|
768
|
+
// Millisecond precision (a Date), FLOORED: Node's `stat().mtime` rounds the file's sub-millisecond part to the nearest millisecond, so a source whose mtime is x.6 ms would be carried
|
|
769
|
+
// as x+1 ms, newer than the source it was copied from; floored, the carried mtime is never newer than the source and less than a millisecond older. `mtimeMs` carries the
|
|
770
|
+
// fraction; a file system double that gives only a `mtime` Date is carried as that Date.
|
|
763
771
|
let newest = null;
|
|
764
772
|
for (const source of sources) {
|
|
765
773
|
let st; try { st = fs.statSync(source); } catch { continue; }
|
|
766
|
-
|
|
774
|
+
const ms = Number.isFinite(st?.mtimeMs) ? Math.floor(st.mtimeMs) : st?.mtime instanceof Date ? st.mtime.getTime() : null;
|
|
775
|
+
if (ms !== null && (newest === null || ms > newest)) newest = ms;
|
|
767
776
|
}
|
|
768
|
-
if (newest) { try { fs.utimesSync(dest,
|
|
777
|
+
if (newest !== null) { const carried = new Date(newest); try { fs.utimesSync(dest, carried, carried); } catch { /* the mtime is a cache hint, never a binding */ } }
|
|
769
778
|
}
|
|
770
779
|
|
|
771
780
|
function copyInto(ctx, source, dest, { executable }) {
|
|
@@ -799,6 +808,38 @@ function requireSnapshotSource(ctx, file, label) {
|
|
|
799
808
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) fail("STAGE_SIDECAR_SOURCE_OUTSIDE_SNAPSHOT", `${label} resolves to ${file}, outside the snapshot's app root ${ctx.appRoot}; a split run reads sources only from the snapshot (checkout bytes enter only as digest-pinned cache entries)`, { file });
|
|
800
809
|
return file;
|
|
801
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* The Cargo output directory of the workspace a split run builds in, as the checkout's absolute path, or `{ error }`. Resolved from the SNAPSHOT: `cargo
|
|
813
|
+
* metadata --offline --no-deps` runs in the snapshot's app root (with `--manifest-path` the snapshot's manifest when the entry names one), so the
|
|
814
|
+
* committed manifests and Cargo configuration decide, and a path it reports under the snapshot is mapped lexically to the same relative path under the
|
|
815
|
+
* checkout (the output directory is the checkout's, GATE-CONTRACT "Output directory & cache"; cargoOutputPin does the same for the stage's own calls). The
|
|
816
|
+
* checkout is never asked: a newer HEAD that deleted the crate's directory, a dirty manifest or another build.target-dir there changes nothing (audit GB-16).
|
|
817
|
+
* A path outside the snapshot (an absolute CARGO_TARGET_DIR, a target-dir set in the user's Cargo home) stands as reported.
|
|
818
|
+
*/
|
|
819
|
+
function snapshotTargetDirectory(ctx, env, manifest) {
|
|
820
|
+
const resolved = ctx.run("rustright", ["cargo", "metadata", "--offline", "--format-version", "1", "--no-deps", ...(manifest ? ["--manifest-path", manifest] : [])], { cwd: ctx.appRoot, env });
|
|
821
|
+
let dir;
|
|
822
|
+
try { dir = JSON.parse(resolved?.stdout).target_directory; } catch { /* reported below */ }
|
|
823
|
+
if (resolved?.error || resolved?.status !== 0 || typeof dir !== "string" || !path.isAbsolute(dir)) return { error: resolved?.error?.message ?? `exit ${resolved?.status}` };
|
|
824
|
+
const roots = splitRoots(ctx);
|
|
825
|
+
for (const snapshot of roots.snapshots) {
|
|
826
|
+
const rel = path.relative(snapshot, dir);
|
|
827
|
+
if (rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel))) return { dir: path.join(roots.checkout, rel) };
|
|
828
|
+
}
|
|
829
|
+
return { dir: path.resolve(dir) };
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/** The snapshot's and the checkout's roots of a split run: `appRoot` and `cacheRoot` are the same app directory in each, so what their paths do not share is the root. */
|
|
833
|
+
function splitRoots(ctx) {
|
|
834
|
+
const a = path.resolve(ctx.appRoot).split(path.sep); const c = path.resolve(ctx.cacheRoot).split(path.sep);
|
|
835
|
+
let n = 0;
|
|
836
|
+
while (n < a.length - 1 && n < c.length - 1 && a[a.length - 1 - n] === c[c.length - 1 - n]) n += 1;
|
|
837
|
+
const snapshot = a.slice(0, a.length - n).join(path.sep) || path.sep;
|
|
838
|
+
const snapshots = [snapshot];
|
|
839
|
+
try { snapshots.push(nodeFs.realpathSync.native(snapshot)); } catch { /* the path as given */ }
|
|
840
|
+
return { snapshots, checkout: c.slice(0, c.length - n).join(path.sep) || path.sep };
|
|
841
|
+
}
|
|
842
|
+
|
|
802
843
|
/**
|
|
803
844
|
* The environment a split run hands to the commands it starts (a `command` entry, an ffmpeg `stage` command): Cargo's output directory is
|
|
804
845
|
* the checkout's, resolved there once and passed absolute, so a command that reaches Cargo (`rustright cargo run -p ...`) never writes
|
|
@@ -806,15 +847,40 @@ function requireSnapshotSource(ctx, file, label) {
|
|
|
806
847
|
*/
|
|
807
848
|
function splitCommandEnv(ctx) {
|
|
808
849
|
if (!ctx.split) return ctx.env;
|
|
850
|
+
// A carried stage (carried-cargo.mjs): the broker pins every Cargo output, so nothing is pinned here and the commands inherit the carrier.
|
|
851
|
+
if (carriedCargoMapOf(ctx.env)?.map) return ctx.env;
|
|
809
852
|
if (ctx.splitEnv === undefined) {
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
try { dir = JSON.parse(resolved?.stdout).target_directory; } catch { /* no workspace in the checkout: nothing to pin */ }
|
|
813
|
-
ctx.splitEnv = !resolved?.error && resolved?.status === 0 && typeof dir === "string" && path.isAbsolute(dir) ? { ...ctx.env, CARGO_TARGET_DIR: path.resolve(dir) } : ctx.env;
|
|
853
|
+
const pinned = snapshotTargetDirectory(ctx, ctx.env, null); // no Cargo workspace in the snapshot: nothing to pin
|
|
854
|
+
ctx.splitEnv = pinned.dir ? { ...ctx.env, CARGO_TARGET_DIR: pinned.dir } : ctx.env;
|
|
814
855
|
}
|
|
815
856
|
return ctx.splitEnv;
|
|
816
857
|
}
|
|
817
858
|
|
|
859
|
+
/**
|
|
860
|
+
* One step command of a config (`stage.cmd`, a `command` entry's `cmd`, the cmake tool). In a snapshot stage with a carrier (the gate's
|
|
861
|
+
* carrier map in the environment) every compile is carried or nested under a carried adapter job, so such a step goes through the door
|
|
862
|
+
* (build-launch.mjs): `pnpm`, `node` and `swift` become carried adapter jobs, `rightkit-qa` runs direct (it compiles nothing), `rustright cargo` keeps the carried seam
|
|
863
|
+
* (defaultRun), this package's own `right-release` runs as it is, and anything else (`cmake`, a bare tool, `rustright <not cargo>`) is
|
|
864
|
+
* refused with STAGE_SIDECAR_STEP_REFUSED, naming the step: it would otherwise run uncarried around the broker. Without a carrier the
|
|
865
|
+
* step runs exactly as before. Utility commands that compile nothing (`tar`, `codesign`) never come through here.
|
|
866
|
+
*/
|
|
867
|
+
export function runStep(ctx, command, args, options = {}) {
|
|
868
|
+
const env = options.env ?? ctx.env;
|
|
869
|
+
const carried = carriedCargoMapOf(env);
|
|
870
|
+
if (!carried || (command === "rustright" && String(args[0]) === "cargo")) return ctx.run(command, args, options);
|
|
871
|
+
const describe = `${command} ${args.join(" ")}`.trim();
|
|
872
|
+
if (carried.error) fail("STAGE_SIDECAR_STEP_REFUSED", `step \`${describe}\`: ${carried.error}; a snapshot stage's step is never run around its carrier`, { command });
|
|
873
|
+
const own = command === "right-release" || (command === "pnpm" && ((args[0] === "exec" && args[1] === "right-release") || args[0] === "right-release"));
|
|
874
|
+
let launch;
|
|
875
|
+
try {
|
|
876
|
+
launch = own ? resolveCommand(command, args, { root: options.cwd, env }) : buildLaunch(command, args, { env, cwd: options.cwd, exists: ctx.fs?.existsSync });
|
|
877
|
+
} catch (error) {
|
|
878
|
+
if (error?.code === BUILD_LAUNCH_REFUSED) fail("STAGE_SIDECAR_STEP_REFUSED", `step \`${describe}\` of a snapshot stage is refused: ${error.message}`, { command });
|
|
879
|
+
throw error;
|
|
880
|
+
}
|
|
881
|
+
return ctx.run(launch.command, launch.args, { ...options, env: launch.env ? { ...env, ...launch.env } : env });
|
|
882
|
+
}
|
|
883
|
+
|
|
818
884
|
/** A cache directory the config names (`cacheDir`): the checkout's `.right-release` under a split, the app root otherwise. */
|
|
819
885
|
const cacheDirFor = (ctx, dir) => (ctx.split ? path.resolve(ctx.cacheRoot, ".right-release", dir) : path.resolve(ctx.appRoot, dir));
|
|
820
886
|
/** The per-arch slice directory of a universal staging: absolute under the checkout's cache root on a split, app-relative otherwise. */
|
|
@@ -907,18 +973,24 @@ function stageCargoBuild(entry, ctx, triple) {
|
|
|
907
973
|
// resolved in the checkout instead and passed as an absolute CARGO_TARGET_DIR, so the warm outputs stay the checkout's and the
|
|
908
974
|
// snapshot holds no target/ (docs/GATE-CONTRACT.md "Output directory & cache").
|
|
909
975
|
let buildEnv = env;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if (
|
|
916
|
-
buildEnv = { ...env, CARGO_TARGET_DIR:
|
|
976
|
+
// A carried stage (carried-cargo.mjs, a broker host): the broker pins the output directory and the call passes no CARGO_TARGET_DIR;
|
|
977
|
+
// the directory the binary is read from is the signed receipt's `outputDir`, never a carrier-less `cargo metadata`.
|
|
978
|
+
const carried = Boolean(carriedCargoMapOf(env)?.map);
|
|
979
|
+
if (ctx.split && !carried) {
|
|
980
|
+
const pinned = snapshotTargetDirectory(ctx, env, entry.manifestPath ? path.resolve(appRoot, entry.manifestPath) : null);
|
|
981
|
+
if (!pinned.dir) fail("STAGE_SIDECAR_BUILD_FAILED", `the cargo output directory for ${entry.package} cannot be resolved from the snapshot (${pinned.error}); nothing was built`, { package: entry.package });
|
|
982
|
+
buildEnv = { ...env, CARGO_TARGET_DIR: pinned.dir };
|
|
917
983
|
}
|
|
918
984
|
const built = run("rustright", args, { cwd: appRoot, env: buildEnv });
|
|
919
985
|
if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `rustright cargo build of ${entry.package} failed (${built?.error?.message ?? `exit ${built?.status}`})`, { package: entry.package, stderr: tail(built?.stderr) });
|
|
920
986
|
let source = artifactFromStream(built.stdout, entry.bin);
|
|
921
|
-
if (!source || !fs.existsSync(source)) {
|
|
987
|
+
if (carried && (!source || !fs.existsSync(source))) {
|
|
988
|
+
const targetDir = typeof built.outputDir === "string" && path.isAbsolute(built.outputDir) ? built.outputDir : undefined;
|
|
989
|
+
if (!targetDir) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `cargo reported no ${entry.bin} executable and the receipt names no output directory`, { bin: entry.bin });
|
|
990
|
+
const candidates = builtBinaryCandidates({ targetDir, targetDirs: [], triple, profile: entry.profile, bin: entry.bin, windows: ctx.windows });
|
|
991
|
+
source = candidates.find((file) => fs.existsSync(file));
|
|
992
|
+
if (!source) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `the build did not produce ${entry.bin} (looked in ${candidates.join(", ")})`, { bin: entry.bin, candidates });
|
|
993
|
+
} else if (!source || !fs.existsSync(source)) {
|
|
922
994
|
const meta = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...(entry.manifestPath ? ["--manifest-path", path.resolve(appRoot, entry.manifestPath)] : [])], { cwd: appRoot, env: buildEnv });
|
|
923
995
|
let targetDir;
|
|
924
996
|
try { targetDir = JSON.parse(meta.stdout).target_directory; } catch { /* refused below */ }
|
|
@@ -1029,7 +1101,7 @@ async function stageFfmpegSlice(entry, ctx, resolved, { config, t, writeNotices,
|
|
|
1029
1101
|
// A universal entry stages once per slice: `{arch}`, `{triple}` and `{stageDir}` in the stage command's arguments name the slice.
|
|
1030
1102
|
const args = (config.stage.args ?? []).map((arg) => (universal ? String(arg).replaceAll("{arch}", t.arch).replaceAll("{triple}", t.triple).replaceAll("{stageDir}", sliceRoot === appRoot ? config.stageDir : path.resolve(sliceRoot, config.stageDir)) : arg));
|
|
1031
1103
|
ctx.log(`stage-sidecar: ${config.stage.cmd} ${args.join(" ")}`);
|
|
1032
|
-
const staged =
|
|
1104
|
+
const staged = runStep(ctx, config.stage.cmd, args, { cwd: appRoot, env: splitCommandEnv(ctx) });
|
|
1033
1105
|
if (staged?.error || staged?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `ffmpeg stage command failed (${staged?.error?.message ?? `exit ${staged?.status}`})`);
|
|
1034
1106
|
}
|
|
1035
1107
|
const noticeFiles = writeNotices ? writeMediaSidecarNotices(plan, { exists: fs.existsSync, mkdir: fs.mkdirSync, copy: fs.copyFileSync, write: fs.writeFileSync }) : [];
|
|
@@ -1519,7 +1591,7 @@ function stageCommand(entry, ctx) {
|
|
|
1519
1591
|
const cwd = entry.cwd ? path.resolve(appRoot, entry.cwd) : appRoot;
|
|
1520
1592
|
cleanDest(ctx, entry, destFor(entry, ctx));
|
|
1521
1593
|
ctx.log(`stage-sidecar: ${entry.cmd} ${entry.args.join(" ")}`);
|
|
1522
|
-
const built =
|
|
1594
|
+
const built = runStep(ctx, entry.cmd, entry.args, { cwd, env: splitCommandEnv(ctx) });
|
|
1523
1595
|
if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `command ${entry.name} (${entry.cmd}) failed (${built?.error?.message ?? `exit ${built?.status}`})`, { name: entry.name, stderr: tail(built?.stderr) });
|
|
1524
1596
|
const source = path.resolve(appRoot, entry.output);
|
|
1525
1597
|
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `command ${entry.name} did not produce ${entry.output}`, { name: entry.name, output: entry.output });
|
|
@@ -1681,7 +1753,7 @@ function stageCmake(entry, ctx) {
|
|
|
1681
1753
|
const configure = ["-S", srcDir, "-B", buildDir, "-DCMAKE_BUILD_TYPE=Release", ...(generator ? ["-G", generator] : []), ...(entry.architecture ? ["-A", entry.architecture] : []), ...(archFlag ? [archFlag] : []), ...(entry.platformArgs[target.platform] ?? []), ...entry.cmakeArgs];
|
|
1682
1754
|
const step = (args, label) => {
|
|
1683
1755
|
ctx.log(`stage-sidecar: ${cmake} ${args.join(" ")}`);
|
|
1684
|
-
const result =
|
|
1756
|
+
const result = runStep(ctx, cmake, args, { cwd: appRoot, env });
|
|
1685
1757
|
if (result?.error || result?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `cmake ${label} of ${entry.name} failed (${result?.error?.message ?? `exit ${result?.status}`})`, { name: entry.name, stderr: tail(result?.stderr) });
|
|
1686
1758
|
};
|
|
1687
1759
|
step(configure, "configure");
|
|
@@ -1808,8 +1880,8 @@ function wipeStaleStageDir(ctx, plan, produced) {
|
|
|
1808
1880
|
*/
|
|
1809
1881
|
export function isNestedBrokerContext(env = process.env, platform = process.platform) {
|
|
1810
1882
|
// A direct (Linux) host has no broker, so no outer broker build to nest in, whatever flag or socket the env carries (heavy-command sets
|
|
1811
|
-
// RIGHTSUITE_HEAVY_WORK_OWNER on every platform).
|
|
1812
|
-
if (
|
|
1883
|
+
// RIGHTSUITE_HEAVY_WORK_OWNER on every platform). A Linux host that declared the broker route (managed-cargo linuxBrokerDeclared) is a broker host.
|
|
1884
|
+
if (directHost(platform, env ?? {})) return false;
|
|
1813
1885
|
if (env?.RIGHTKIT_STAGE_SIDECAR_ALLOW_NESTED === "1") return false;
|
|
1814
1886
|
const insideTauri = Boolean(env?.TAURI_ENV_PLATFORM || env?.TAURI_ENV_TARGET_TRIPLE);
|
|
1815
1887
|
return insideTauri && (managedHost(env ?? {}, platform) || env?.RIGHTSUITE_HEAVY_WORK_OWNER === "1");
|