@rightkit/release 0.6.13 → 0.6.14

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.
Files changed (109) hide show
  1. package/README.md +49 -1
  2. package/adapter-env.mjs +165 -0
  3. package/android-ndk.mjs +5 -1
  4. package/android-release.mjs +73 -8
  5. package/app-check.mjs +22 -8
  6. package/app-closure.mjs +214 -0
  7. package/assemble-app-command.mjs +24 -7
  8. package/atomic-write.mjs +57 -0
  9. package/bin/darwin/rightkit-swap +0 -0
  10. package/bin/rightkit-gate.mjs +1190 -337
  11. package/broker-client.mjs +106 -0
  12. package/broker-exception.mjs +211 -0
  13. package/broker-handshake.mjs +160 -0
  14. package/broker-pins.mjs +27 -8
  15. package/broker-wire.mjs +262 -0
  16. package/build-launch.mjs +364 -12
  17. package/build-ownership.mjs +117 -49
  18. package/build-release.mjs +3 -2
  19. package/cache-command.mjs +7 -2
  20. package/candidate-build-receipt.mjs +96 -0
  21. package/candidate-receipt-command.mjs +29 -0
  22. package/cargo-contract.mjs +4 -3
  23. package/cargo-ledger.mjs +1 -1
  24. package/cargo-license-notices.mjs +2 -2
  25. package/cargo-shapes.mjs +332 -0
  26. package/cargo-target-shim.mjs +44 -5
  27. package/cargo-target.mjs +5 -0
  28. package/carried-adapter-run.mjs +31 -0
  29. package/carried-cargo-run.mjs +9 -0
  30. package/carried-cargo.mjs +237 -0
  31. package/carrier-census.mjs +124 -0
  32. package/class-a-launch-guard.mjs +266 -33
  33. package/cli/right-release.mjs +2 -0
  34. package/clippy-route.mjs +23 -11
  35. package/compile-inputs.mjs +9 -12
  36. package/control-plane.mjs +18 -0
  37. package/deps-cargo-lock.mjs +208 -0
  38. package/deps.mjs +5 -0
  39. package/deterministic-scans.mjs +273 -44
  40. package/development-signing.mjs +14 -1
  41. package/development.mjs +109 -24
  42. package/direct-origin.mjs +107 -0
  43. package/disk-command.mjs +1 -0
  44. package/disk-policy.mjs +18 -3
  45. package/freshness-census.mjs +206 -58
  46. package/gate-executor.mjs +30 -12
  47. package/gate-stages.mjs +93 -16
  48. package/github-release.mjs +2 -1
  49. package/github-upload-gate.mjs +2 -1
  50. package/install-tauri-authenticode.mjs +346 -0
  51. package/ios-dev.mjs +77 -23
  52. package/ios-package-recipe.mjs +100 -41
  53. package/linux-artifacts.mjs +17 -2
  54. package/linux-qualifier.mjs +83 -0
  55. package/local-publish.mjs +36 -14
  56. package/local-runtime.mjs +2 -2
  57. package/lock-identity.mjs +138 -0
  58. package/lsclean.sh +0 -0
  59. package/managed-cargo.mjs +151 -44
  60. package/managed-roots.mjs +130 -12
  61. package/media-sidecar-catalog.mjs +1 -1
  62. package/native-adapters.mjs +411 -0
  63. package/native-helpers.mjs +26 -18
  64. package/native-tool-planning.mjs +94 -0
  65. package/ownership-mark.mjs +182 -0
  66. package/pack-digest.mjs +122 -0
  67. package/package.json +12 -12
  68. package/platform-dir.mjs +14 -0
  69. package/pnpm-adapter-grammar.mjs +226 -0
  70. package/progress-control.mjs +7 -2
  71. package/provenance-real-receipt.pins.json +7 -0
  72. package/provenance-verify.mjs +262 -1
  73. package/publication-outcomes.mjs +21 -0
  74. package/publish-update.mjs +2 -1
  75. package/qa-journeys-prebuilt-run.mjs +18 -0
  76. package/qa-journeys-prebuilt.mjs +148 -0
  77. package/qa-snapshot-run.mjs +53 -0
  78. package/qa-snapshot.mjs +164 -0
  79. package/qualification-verify.mjs +144 -0
  80. package/qualify-reuse.mjs +38 -0
  81. package/recipe-build.mjs +16 -3
  82. package/release-qa-audit.mjs +2 -2
  83. package/release-qa-command.mjs +2 -1
  84. package/release-qa-receipt.mjs +3 -2
  85. package/release-state.mjs +3 -2
  86. package/release.mjs +14 -3
  87. package/rightkit-drift.mjs +27 -5
  88. package/rightkit-versions.json +14 -10
  89. package/rustright-names.mjs +32 -0
  90. package/scan-tool-enrolment.mjs +167 -0
  91. package/sign-windows.mjs +212 -112
  92. package/signing-files.mjs +35 -0
  93. package/signing-identity.mjs +7 -0
  94. package/snapshot-broker.mjs +528 -57
  95. package/snapshot-check.mjs +95 -11
  96. package/snapshot-metadata.mjs +123 -0
  97. package/snapshot-request.mjs +389 -96
  98. package/stage-binary.mjs +22 -4
  99. package/stage-commands.mjs +56 -13
  100. package/stage-sidecar.mjs +93 -21
  101. package/suite-signing-policy.mjs +657 -0
  102. package/suite-signing.mjs +223 -0
  103. package/swift-scratch.mjs +25 -0
  104. package/tauri-locked.mjs +25 -0
  105. package/test-command.mjs +164 -29
  106. package/unit-gate.mjs +201 -31
  107. package/upload-large.mjs +0 -0
  108. package/web-build.mjs +2 -2
  109. package/win-package-recipe.mjs +26 -2
@@ -0,0 +1,208 @@
1
+ // `right-release deps --cargo-lock --manifest <Cargo.toml> --pin <crate>=<version> [--pin …]` (rightkit#1305): the sanctioned, non-compiling
2
+ // Cargo.lock update door. An app adopting a newly published RightKit crate refreshes its lock here, never by hand and never by a build.
3
+ //
4
+ // - On Mac and Dell (the broker) each pin is one uncarried root request, `cargo update --manifest-path <m> -p <crate> --precise <v>`,
5
+ // sent through the managed client (runCargoSync) in the PRIMARY checkout of the manifest's workspace. Cargo takes one `--precise` per
6
+ // `-p`, so several pins are several requests in sequence inside this one door run. No `--config`, `--target-dir` or `+toolchain` is ever
7
+ // passed: the broker refuses those as redirects.
8
+ // - On Linux (direct) the same argv runs as plain `cargo update` (runCargoSync routes it).
9
+ // - The door writes only the workspace's Cargo.lock. Before the first request it records `git status --porcelain` and the sha256 of every
10
+ // tracked file under the workspace; after each request it refuses (RK_LOCK_UPDATE_OUTSIDE_LOCK) if anything else changed, and restores
11
+ // the lock to its prior bytes. The broker does not restrict writes to Cargo.lock, so this check is the enforcement.
12
+ // - A gate stage's process (RK_GATE_CHECKOUT or the carried cargo map in its environment) never updates a lock: refused as a snapshot.
13
+ // - It is never a build: nothing compiles, and a failed request restores the lock too.
14
+ import { createHash } from "node:crypto";
15
+ import { existsSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
16
+ import path from "node:path";
17
+ import { spawnSync } from "./background-process.mjs";
18
+ import { runCargoSync } from "./managed-cargo.mjs";
19
+ import { CARRIED_CARGO_MAP_ENV } from "./carried-cargo.mjs";
20
+ import { CHECKOUT_ENV } from "./gate-stages.mjs";
21
+
22
+ export const LOCK_UPDATE_CODES = Object.freeze({
23
+ ARGS: "RK_LOCK_UPDATE_ARGS",
24
+ PIN: "RK_LOCK_UPDATE_PIN",
25
+ SNAPSHOT: "RK_LOCK_UPDATE_SNAPSHOT",
26
+ CHECKOUT: "RK_LOCK_UPDATE_CHECKOUT",
27
+ NO_LOCK: "RK_LOCK_UPDATE_NO_LOCK",
28
+ FAILED: "RK_LOCK_UPDATE_FAILED",
29
+ OUTSIDE_LOCK: "RK_LOCK_UPDATE_OUTSIDE_LOCK",
30
+ });
31
+
32
+ export class CargoLockUpdateError extends Error {
33
+ constructor(code, message) { super(message); this.name = "CargoLockUpdateError"; this.code = code; }
34
+ }
35
+
36
+ const USAGE = "usage: right-release deps --cargo-lock --manifest <Cargo.toml> --pin <crate>=<version> [--pin <crate>=<version>]...";
37
+ const CRATE = /^[A-Za-z][A-Za-z0-9_-]*$/;
38
+ // A semantic version (SemVer 2.0.0), the form `cargo update --precise` takes.
39
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
40
+
41
+ /** `[{ crate, version }]` from `--pin <crate>=<version>` values. Throws RK_LOCK_UPDATE_PIN on a malformed or unparsable pin. */
42
+ export function parsePins(values) {
43
+ if (!Array.isArray(values) || values.length === 0) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.ARGS, "at least one --pin <crate>=<version> is required");
44
+ const seen = new Set();
45
+ return values.map((raw) => {
46
+ const text = String(raw);
47
+ const eq = text.indexOf("=");
48
+ const crate = eq < 0 ? "" : text.slice(0, eq);
49
+ const version = eq < 0 ? "" : text.slice(eq + 1);
50
+ if (!CRATE.test(crate)) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.PIN, `--pin ${text}: ${JSON.stringify(crate)} is not a Cargo package name`);
51
+ if (!SEMVER.test(version)) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.PIN, `--pin ${text}: ${JSON.stringify(version)} is not a semantic version`);
52
+ if (seen.has(crate)) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.PIN, `--pin ${crate} is given more than once`);
53
+ seen.add(crate);
54
+ return { crate, version };
55
+ });
56
+ }
57
+
58
+ /** The argv of one pin's request. Never carries --config, --target-dir or a +toolchain. */
59
+ export function cargoUpdateArgv({ manifest, crate, version }) {
60
+ return ["update", "--manifest-path", manifest, "-p", crate, "--precise", version];
61
+ }
62
+
63
+ const real = (p) => { try { return realpathSync.native(p); } catch { return path.resolve(p); } };
64
+ /** A path with its directory resolved, so a path that no longer exists still compares equal to its live spelling. */
65
+ const canonical = (p) => path.join(real(path.dirname(p)), path.basename(p));
66
+
67
+ function snapshotRefusal(env) {
68
+ if (env?.[CHECKOUT_ENV]) return `${CHECKOUT_ENV} is set: this process runs inside a gate stage's snapshot`;
69
+ if (env?.[CARRIED_CARGO_MAP_ENV]) return `${CARRIED_CARGO_MAP_ENV} is set: this process runs inside a gate stage's snapshot`;
70
+ return null;
71
+ }
72
+
73
+ const gitRun = (args, cwd) => {
74
+ const result = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true });
75
+ return { status: result.status, stdout: String(result.stdout ?? "") };
76
+ };
77
+
78
+ function primaryCheckoutProblem(ws, git) {
79
+ const dir = git(["rev-parse", "--git-dir"], ws);
80
+ const common = git(["rev-parse", "--git-common-dir"], ws);
81
+ if (dir.status !== 0 || common.status !== 0) return `${ws} is not inside a git checkout`;
82
+ if (real(path.resolve(ws, dir.stdout.trim())) !== real(path.resolve(ws, common.stdout.trim()))) {
83
+ return `${ws} is a linked worktree; the lock is updated only in the primary checkout`;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ const digest = (file) => {
89
+ try { return createHash("sha256").update(readFileSync(file)).digest("hex"); } catch (error) { return error?.code === "ENOENT" ? "<missing>" : "<unreadable>"; }
90
+ };
91
+
92
+ /** `git status --porcelain -z` entries as `{ key, paths }`; a rename or copy carries its source path as the next token. */
93
+ function statusEntries(text) {
94
+ const tokens = text.split("\0");
95
+ const entries = [];
96
+ for (let i = 0; i < tokens.length; i++) {
97
+ const token = tokens[i];
98
+ if (token.length < 4) continue;
99
+ const entry = { key: token, paths: [token.slice(3)] };
100
+ if (/[RC]/.test(token.slice(0, 2)) && tokens[i + 1]) { entry.paths.push(tokens[++i]); }
101
+ entries.push(entry);
102
+ }
103
+ return entries;
104
+ }
105
+
106
+ /** The state the outside-lock check compares: tracked-file digests under the workspace and the porcelain status. */
107
+ function workspaceState(ws, top, git) {
108
+ const tracked = git(["ls-files", "-z", "--", "."], ws);
109
+ if (tracked.status !== 0) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.CHECKOUT, `git ls-files failed under ${ws}`);
110
+ const hashes = new Map();
111
+ for (const rel of tracked.stdout.split("\0")) if (rel) hashes.set(rel, digest(path.join(ws, rel)));
112
+ const status = git(["status", "--porcelain", "-z", "--untracked-files=all"], ws);
113
+ if (status.status !== 0) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.CHECKOUT, `git status failed under ${ws}`);
114
+ return { top, hashes, entries: statusEntries(status.stdout) };
115
+ }
116
+
117
+ /** Canonical paths that changed between the baseline and now, other than the lock. */
118
+ function outsideChanges(before, after, ws, lockPath) {
119
+ const changed = new Set();
120
+ for (const rel of new Set([...before.hashes.keys(), ...after.hashes.keys()])) {
121
+ if (before.hashes.get(rel) !== after.hashes.get(rel)) changed.add(canonical(path.join(ws, rel)));
122
+ }
123
+ const beforeKeys = new Set(before.entries.map((entry) => entry.key));
124
+ const afterKeys = new Set(after.entries.map((entry) => entry.key));
125
+ for (const entry of [...before.entries, ...after.entries]) {
126
+ if (beforeKeys.has(entry.key) !== afterKeys.has(entry.key)) for (const p of entry.paths) changed.add(canonical(path.resolve(before.top, p)));
127
+ }
128
+ changed.delete(canonical(lockPath));
129
+ return [...changed].sort();
130
+ }
131
+
132
+ function restoreLock(lock, bytes) {
133
+ if (bytes === null) rmSync(lock, { force: true });
134
+ else writeFileSync(lock, bytes);
135
+ }
136
+
137
+ /**
138
+ * Refresh the Cargo.lock of the workspace that holds `manifest`, one request per pin. Returns `{ workspace, lock, pins }`; throws
139
+ * CargoLockUpdateError with a LOCK_UPDATE_CODES code (the lock is restored first on every failure after the requests start).
140
+ * `run` is the managed cargo runner (injectable), `git` the git runner (injectable), `env` the environment of the requests.
141
+ */
142
+ export function updateCargoLock({ manifest, pins, cwd = process.cwd(), env = process.env, run = runCargoSync, git = gitRun, log = () => {} } = {}) {
143
+ const refusal = snapshotRefusal(env);
144
+ if (refusal) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.SNAPSHOT, `refused: ${refusal}`);
145
+ const parsed = parsePins(pins);
146
+ if (typeof manifest !== "string" || !manifest) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.ARGS, "--manifest <Cargo.toml> is required");
147
+ const manifestPath = path.resolve(cwd, manifest);
148
+ if (!existsSync(manifestPath) || !statSync(manifestPath).isFile()) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.ARGS, `manifest not found: ${manifestPath}`);
149
+ const ws = path.dirname(manifestPath);
150
+ const lock = path.join(ws, "Cargo.lock");
151
+ if (!existsSync(lock)) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.NO_LOCK, `${lock} does not exist; an unlocked workspace has no lock to update`);
152
+ const problem = primaryCheckoutProblem(ws, git);
153
+ if (problem) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.CHECKOUT, `refused: ${problem}`);
154
+ const top = canonical(path.resolve(ws, git(["rev-parse", "--show-toplevel"], ws).stdout.trim()));
155
+ const before = workspaceState(ws, top, git);
156
+ const lockBytes = readFileSync(lock);
157
+ try {
158
+ for (const { crate, version } of parsed) {
159
+ const argv = cargoUpdateArgv({ manifest: manifestPath, crate, version });
160
+ log(`right-deps: cargo ${argv.join(" ")} (${ws})`);
161
+ const result = run(argv, { cwd: ws, env, stdio: "inherit" });
162
+ if (result?.error) throw new CargoLockUpdateError(result.error.code ?? LOCK_UPDATE_CODES.FAILED, `cargo update -p ${crate} --precise ${version} could not run (${result.error.message})`);
163
+ if (result?.status !== 0) throw new CargoLockUpdateError(LOCK_UPDATE_CODES.FAILED, `cargo update -p ${crate} --precise ${version} exited ${result?.status ?? "without status"}`);
164
+ const outside = outsideChanges(before, workspaceState(ws, top, git), ws, lock);
165
+ if (outside.length > 0) {
166
+ throw new CargoLockUpdateError(LOCK_UPDATE_CODES.OUTSIDE_LOCK, `cargo update changed files other than ${lock}: ${outside.join(", ")}; the lock was restored (other files are left for review)`);
167
+ }
168
+ }
169
+ } catch (error) {
170
+ restoreLock(lock, lockBytes);
171
+ throw error;
172
+ }
173
+ return { workspace: ws, lock, pins: parsed };
174
+ }
175
+
176
+ /**
177
+ * The `deps --cargo-lock` command line (argv without the `--cargo-lock` word). Returns the exit code: 0 updated, 1 refused or failed,
178
+ * 2 usage. Prints the typed refusal code on stderr.
179
+ */
180
+ export function runCargoLockCommand(argv, { cwd = process.cwd(), env = process.env, run, git, log = (line) => console.log(line), fail = (line) => console.error(line) } = {}) {
181
+ let manifest;
182
+ const pins = [];
183
+ for (let i = 0; i < argv.length; i++) {
184
+ const arg = argv[i];
185
+ if (arg === "--") continue;
186
+ if (arg === "--manifest" || arg === "--pin") {
187
+ const value = argv[i + 1];
188
+ if (value === undefined || value.startsWith("--")) { fail(`right-deps: ${arg} needs a value\n${USAGE}`); return 2; }
189
+ i++;
190
+ if (arg === "--pin") pins.push(value);
191
+ else if (manifest !== undefined) { fail(`right-deps: --manifest given twice\n${USAGE}`); return 2; }
192
+ else manifest = value;
193
+ } else {
194
+ fail(`right-deps: unknown argument: ${arg}\n${USAGE}`);
195
+ return 2;
196
+ }
197
+ }
198
+ if (manifest === undefined || pins.length === 0) { fail(USAGE); return 2; }
199
+ try {
200
+ const { lock, pins: applied } = updateCargoLock({ manifest, pins, cwd, env, run, git, log });
201
+ log(`right-deps: ${lock} updated (${applied.map((pin) => `${pin.crate} ${pin.version}`).join(", ")}); no other file changed`);
202
+ return 0;
203
+ } catch (error) {
204
+ if (error instanceof CargoLockUpdateError) { fail(`right-deps: ${error.code}: ${error.message}`); return 1; }
205
+ fail(`right-deps: ${error?.message ?? error}`);
206
+ return 1;
207
+ }
208
+ }
package/deps.mjs CHANGED
@@ -4,8 +4,12 @@ import { defaultReleaseConfigFile, importReleaseConfig } from "./release-config.
4
4
  import { spawnSync } from "./background-process.mjs";
5
5
  import { existsSync } from "node:fs";
6
6
  import { cargoAuditWorkdirs, runCargoAudits } from "./deps-cargo.mjs";
7
+ import { runCargoLockCommand } from "./deps-cargo-lock.mjs";
7
8
 
8
9
  const args = process.argv.slice(2);
10
+ // `deps --cargo-lock --manifest <Cargo.toml> --pin <crate>=<version>`: the Cargo.lock update door (rightkit#1305). It needs no release
11
+ // config and compiles nothing, so it answers before the config is read.
12
+ if (args.includes("--cargo-lock")) process.exit(runCargoLockCommand(args.filter((arg) => arg !== "--cargo-lock")));
9
13
  const opts = {
10
14
  config: defaultReleaseConfigFile(),
11
15
  mode: "check",
@@ -71,6 +75,7 @@ if (failures.length > 0) {
71
75
  function usage(code, message) {
72
76
  if (message) console.error(`right-deps: ${message}\n`);
73
77
  console.error("usage: node deps.mjs [--config right-release.config.json] [--check|--audit|--update] [--latest]");
78
+ console.error(" node deps.mjs --cargo-lock --manifest <Cargo.toml> --pin <crate>=<version> [--pin <crate>=<version>]...");
74
79
  process.exit(code);
75
80
  }
76
81