@rightkit/release 0.6.8 → 0.6.11
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 +30 -12
- package/admission-files.mjs +4 -1
- package/admission.mjs +2 -2
- package/advisory-exceptions.mjs +41 -5
- package/android-play-upload.mjs +16 -15
- package/app-check.mjs +105 -27
- package/assemble-app-command.mjs +151 -0
- package/assembled-app.mjs +445 -0
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +6 -0
- package/bin/rightkit-gate.mjs +1608 -345
- package/broker-pins.mjs +152 -0
- package/build-launch.mjs +88 -0
- package/build-ownership.mjs +1231 -0
- package/build-release.mjs +82 -28
- package/cargo-contract.mjs +3 -1
- package/cargo-ledger.mjs +62 -0
- package/cargo-target-shim.mjs +238 -0
- package/class-a-launch-guard.mjs +126 -0
- package/cli/right-release.mjs +31 -5
- package/clippy-equivalence.mjs +202 -0
- package/clippy-route.mjs +543 -0
- package/codesign-shim.mjs +11 -1
- package/committed-source.mjs +39 -5
- package/crate-workspaces.mjs +87 -0
- package/create-mac-updater.mjs +14 -2
- package/deterministic-scans.mjs +997 -71
- package/development-signing.mjs +4 -1
- package/development.mjs +120 -21
- package/direct-receipt.mjs +135 -0
- package/door-policy.mjs +71 -6
- package/door.mjs +6 -6
- package/freshness-census.mjs +257 -0
- package/gate-executor.mjs +14 -2
- package/gate-stages.mjs +91 -15
- package/heavy-command.mjs +3 -2
- package/hook-retirement.mjs +65 -0
- package/init-command.mjs +1 -1
- package/install-app.mjs +11 -2
- package/install-assembled-app.mjs +423 -0
- package/inventory-check.mjs +11 -8
- package/ios-dev.mjs +70 -3
- package/ios-upload.mjs +20 -15
- package/linux-artifacts.mjs +93 -0
- package/local-publish.mjs +224 -53
- package/local-qualify.mjs +5 -3
- package/local-runtime.mjs +5 -4
- package/mac-package-recipe.mjs +4 -1
- package/managed-cargo.mjs +47 -5
- package/managed-root-guard.mjs +34 -0
- package/mirror-root-artifact.mjs +18 -5
- package/native-helpers.mjs +355 -0
- package/ownership-class-rules.mjs +1 -1
- package/ownership-evidence.mjs +37 -13
- package/ownership-hooks.mjs +20 -59
- package/ownership-lockfiles.mjs +14 -1
- package/ownership-policy-bundle.mjs +3 -2
- package/ownership-staged.mjs +2 -1
- package/ownership.mjs +24 -0
- package/package-recipe-targets.mjs +84 -10
- package/package.json +4 -3
- package/pipeline-admission.mjs +4 -4
- package/pipeline-receipts.mjs +302 -302
- package/preflight.mjs +27 -4
- package/project-type.mjs +2 -2
- package/provenance-verify.mjs +115 -0
- package/publish-cargo.mjs +12 -9
- package/qa-admission-policy.json +10 -1
- package/recipe-build.mjs +106 -14
- package/release-artifact-path.mjs +341 -0
- package/release-config.mjs +1 -1
- package/release-invocation.mjs +8 -1
- package/release-qa-command.mjs +25 -9
- package/release-source-brand.mjs +17 -0
- package/release-source-identity.mjs +171 -0
- package/release.mjs +59 -22
- package/rightkit-versions.json +73 -47
- package/runtime-budgets.mjs +6 -2
- package/scan-digest.mjs +71 -5
- package/sign-updater.mjs +6 -3
- package/snapshot-broker.mjs +172 -0
- package/snapshot-check.mjs +312 -0
- package/snapshot-git.mjs +166 -0
- package/snapshot-request.mjs +363 -0
- package/snapshot-root-config.mjs +76 -0
- package/snapshot.mjs +969 -0
- package/source-split.mjs +73 -0
- package/stage-commands.mjs +60 -0
- package/stage-ledger.mjs +204 -0
- package/stage-sidecar-command.mjs +5 -1
- package/stage-sidecar.mjs +157 -27
- package/swap-helper-bundle.mjs +5 -2
- package/test-command.mjs +17 -4
- package/unit-gate.mjs +301 -0
- package/web-build.mjs +40 -6
- package/win-package-recipe.mjs +20 -7
package/committed-source.mjs
CHANGED
|
@@ -115,7 +115,7 @@ function readBatch(root, oids, totalBytes, runner) {
|
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function acquire(root, { expectedCommit, expectedTree, requiredPaths = [], runner = spawnSync, env = gitEnv() } = {}) {
|
|
118
|
+
function acquire(root, { expectedCommit, expectedTree, commit: selectedCommit, requiredPaths = [], runner = spawnSync, env = gitEnv() } = {}) {
|
|
119
119
|
const text = (args) => runGit(root, args, { encoding: "utf8", runner, env }).trim();
|
|
120
120
|
const repositoryRoot = path.resolve(text(["rev-parse", "--show-toplevel"]));
|
|
121
121
|
const pathPrefix = text(["rev-parse", "--show-prefix"]).replaceAll("\\", "/");
|
|
@@ -125,9 +125,20 @@ function acquire(root, { expectedCommit, expectedTree, requiredPaths = [], runne
|
|
|
125
125
|
const origin = originResult?.status === 0 ? String(originResult.stdout).trim() : null;
|
|
126
126
|
const objectFormat = text(["rev-parse", "--show-object-format"]);
|
|
127
127
|
if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new Error("committed ownership: unsupported Git object format");
|
|
128
|
-
const commit = text(["rev-parse", "--verify", "HEAD^{commit}"]);
|
|
129
128
|
const digestLength = objectFormat === "sha256" ? 64 : 40;
|
|
130
|
-
|
|
129
|
+
// A door source split (release-source-identity.mjs, item 7 row 7.04) names the commit it builds: that commit's immutable objects are the
|
|
130
|
+
// source, never the checkout's HEAD. Only a full object id is accepted (never a ref, a revision expression or an option), and the
|
|
131
|
+
// object database must answer with that same commit. Without `commit` the source is HEAD, exactly as before.
|
|
132
|
+
let commit;
|
|
133
|
+
if (selectedCommit !== undefined) {
|
|
134
|
+
if (!new RegExp(`^[0-9a-f]{${digestLength}}$`).test(String(selectedCommit))) throw new Error("committed ownership: the selected commit is not a full object id");
|
|
135
|
+
commit = text(["rev-parse", "--verify", `${selectedCommit}^{commit}`]);
|
|
136
|
+
if (commit !== selectedCommit) throw new Error("committed ownership: the selected commit resolved to another object");
|
|
137
|
+
if (expectedCommit && commit !== expectedCommit) throw new Error("committed ownership: expected commit is not the selected commit");
|
|
138
|
+
} else {
|
|
139
|
+
commit = text(["rev-parse", "--verify", "HEAD^{commit}"]);
|
|
140
|
+
if (!new RegExp(`^[0-9a-f]{${digestLength}}$`).test(commit) || (expectedCommit && commit !== expectedCommit)) throw new Error("committed ownership: expected commit is not current HEAD");
|
|
141
|
+
}
|
|
131
142
|
const tree = text(["rev-parse", "--verify", `${commit}^{tree}`]);
|
|
132
143
|
if (!new RegExp(`^[0-9a-f]{${digestLength}}$`).test(tree) || (expectedTree && tree !== expectedTree)) throw new Error("committed ownership: expected tree does not match commit");
|
|
133
144
|
const rows = parseTree(runGit(root, ["ls-tree", "-r", "-z", "--full-tree", tree], { encoding: "buffer", maxBuffer: MAX_TREE_BYTES, timeout: COMMAND_TIMEOUT_MS, runner, env }), objectFormat);
|
|
@@ -161,9 +172,13 @@ function acquire(root, { expectedCommit, expectedTree, requiredPaths = [], runne
|
|
|
161
172
|
return Object.freeze({ repositoryRoot, pathPrefix, origin, commit, tree, objectFormat, entries, blobs, coverage });
|
|
162
173
|
}
|
|
163
174
|
|
|
175
|
+
/**
|
|
176
|
+
* The committed source of `root`'s repository. `options.commit` selects the commit by full object id (a door source split reads the
|
|
177
|
+
* selected commit's objects, never the checkout's HEAD); without it the source is HEAD. Blob-hash verification is the same either way.
|
|
178
|
+
*/
|
|
164
179
|
export function acquireCommittedSource(root, options = {}) {
|
|
165
|
-
const { expectedCommit, expectedTree, requiredPaths = [] } = options;
|
|
166
|
-
try { return createSourceHandle(acquire(path.resolve(root), { expectedCommit, expectedTree, requiredPaths })); }
|
|
180
|
+
const { expectedCommit, expectedTree, requiredPaths = [], commit } = options;
|
|
181
|
+
try { return createSourceHandle(acquire(path.resolve(root), { expectedCommit, expectedTree, requiredPaths, ...(commit !== undefined ? { commit } : {}) })); }
|
|
167
182
|
catch (error) { if (error instanceof CommittedSourceIncompleteError) throw error; throw new CommittedSourceIncompleteError(error.message, error); }
|
|
168
183
|
}
|
|
169
184
|
|
|
@@ -185,6 +200,25 @@ export function committedSourceDataForInternalUse(handle) {
|
|
|
185
200
|
return acquiredSources.get(handle) ?? null;
|
|
186
201
|
}
|
|
187
202
|
|
|
203
|
+
/**
|
|
204
|
+
* `{ commit, tree }` of `commit` read from `root`'s object database: the full object id must name a commit, and the tree is that
|
|
205
|
+
* commit's own. It reads no HEAD, no index and no status, so a dirty or moved checkout cannot change the answer. Throws on any failure.
|
|
206
|
+
*/
|
|
207
|
+
export function resolveSelectedCommit(root, commit, { runner = spawnSync } = {}) {
|
|
208
|
+
const resolvedRoot = path.resolve(root);
|
|
209
|
+
const text = (args) => runGit(resolvedRoot, args, { encoding: "utf8", timeout: COMMAND_TIMEOUT_MS, runner, env: gitEnv() }).trim();
|
|
210
|
+
const objectFormat = text(["rev-parse", "--show-object-format"]);
|
|
211
|
+
if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new Error("committed ownership: unsupported Git object format");
|
|
212
|
+
const digestLength = objectFormat === "sha256" ? 64 : 40;
|
|
213
|
+
const shape = new RegExp(`^[0-9a-f]{${digestLength}}$`);
|
|
214
|
+
if (!shape.test(String(commit))) throw new Error("committed ownership: the selected commit is not a full object id");
|
|
215
|
+
const resolved = text(["rev-parse", "--verify", `${commit}^{commit}`]);
|
|
216
|
+
if (resolved !== commit) throw new Error("committed ownership: the selected commit resolved to another object");
|
|
217
|
+
const tree = text(["rev-parse", "--verify", `${commit}^{tree}`]);
|
|
218
|
+
if (!shape.test(tree)) throw new Error("committed ownership: the selected commit has no tree");
|
|
219
|
+
return Object.freeze({ commit: resolved, tree, objectFormat });
|
|
220
|
+
}
|
|
221
|
+
|
|
188
222
|
export function assertCommittedCheckoutClean(root, { expectedCommit, expectedTree, runner = spawnSync } = {}) {
|
|
189
223
|
if (expectedCommit || expectedTree) {
|
|
190
224
|
const currentCommit = runGit(path.resolve(root), ["rev-parse", "--verify", "HEAD^{commit}"], { encoding: "utf8", timeout: COMMAND_TIMEOUT_MS, runner, env: gitEnv() }).trim();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// One Cargo workspace and lock per RightKit crate (Adrian, 2026-10-29: "I want you to have 1 cargo dot lock per component";
|
|
2
|
+
// rightkit#1285). Every `crates/<x>` is its own workspace; crates that use each other link by path dependency across
|
|
3
|
+
// workspaces. This module reads manifests only (no cargo) and answers which crates exist, which sibling crates each one
|
|
4
|
+
// path-depends on, and which crates a change reaches: the changed crate plus every crate that path-depends on it, transitively.
|
|
5
|
+
// It imports nothing outside node: CI's plan job runs it before any package install, so it reads the few manifest keys it needs
|
|
6
|
+
// (the package name and dependency `path` values) line by line instead of through a TOML library.
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const posix = (p) => p.split(path.sep).join("/");
|
|
11
|
+
const defaults = { readFile: (f) => fs.readFileSync(f, "utf8"), readdir: (d) => fs.readdirSync(d), exists: fs.existsSync };
|
|
12
|
+
|
|
13
|
+
/** [{ name, dir: "crates/<x>", manifest, lock }] for every crates/<x>/Cargo.toml, sorted by dir. */
|
|
14
|
+
export function crateWorkspaces(root, io = {}) {
|
|
15
|
+
const { readFile, readdir, exists } = { ...defaults, ...io };
|
|
16
|
+
const cratesDir = path.join(root, "crates");
|
|
17
|
+
if (!exists(cratesDir)) return [];
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const entry of [...readdir(cratesDir)].map(String).sort()) {
|
|
20
|
+
if (entry.startsWith(".")) continue;
|
|
21
|
+
const manifest = `crates/${entry}/Cargo.toml`;
|
|
22
|
+
if (!exists(path.join(root, manifest))) continue;
|
|
23
|
+
const name = manifestFacts(readFile(path.join(root, manifest))).name;
|
|
24
|
+
if (typeof name !== "string") continue;
|
|
25
|
+
out.push({ name, dir: `crates/${entry}`, manifest, lock: `crates/${entry}/Cargo.lock` });
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEPENDENCY_TABLE = /^(?:target\.(?:'[^']*'|"[^"]*"|[^.\]]+)\.)?(?:dev-|build-)?dependencies$/;
|
|
31
|
+
const unquote = (v) => v.replace(/^(["'])(.*)\1$/, "$2");
|
|
32
|
+
|
|
33
|
+
/** { name, paths }: the [package] name and every dependency `path` value (inline tables and `[dependencies.<x>]` sections). */
|
|
34
|
+
export function manifestFacts(text) {
|
|
35
|
+
let section = ""; let name; const paths = [];
|
|
36
|
+
for (const raw of String(text).split(/\r?\n/)) {
|
|
37
|
+
const line = raw.replace(/\s+#.*$/, "").trim();
|
|
38
|
+
if (!line || line.startsWith("#")) continue;
|
|
39
|
+
const header = line.match(/^\[\[?([^\[\]]+)\]\]?$/);
|
|
40
|
+
if (header) { section = header[1].trim(); continue; }
|
|
41
|
+
if (section === "package") { const m = line.match(/^name\s*=\s*("[^"]*"|'[^']*')/); if (m) name = unquote(m[1]); continue; }
|
|
42
|
+
if (DEPENDENCY_TABLE.test(section)) {
|
|
43
|
+
const m = line.match(/^[A-Za-z0-9_.-]+\s*=\s*\{(.*)\}$/);
|
|
44
|
+
const p = m && m[1].match(/(?:^|,)\s*path\s*=\s*("[^"]*"|'[^']*')/);
|
|
45
|
+
if (p) paths.push(unquote(p[1]));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const dotted = section.match(/^(.*)\.([^.]+)$/);
|
|
49
|
+
if (dotted && DEPENDENCY_TABLE.test(dotted[1])) { const m = line.match(/^path\s*=\s*("[^"]*"|'[^']*')/); if (m) paths.push(unquote(m[1])); }
|
|
50
|
+
}
|
|
51
|
+
return { name, paths };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Map<name, Set<name>>: each crate's direct path dependencies on sibling crates (normal, dev, build, any target). */
|
|
55
|
+
export function pathDependencies(root, io = {}, workspaces = crateWorkspaces(root, io)) {
|
|
56
|
+
const { readFile } = { ...defaults, ...io };
|
|
57
|
+
const byDir = new Map(workspaces.map((w) => [w.dir, w.name]));
|
|
58
|
+
const deps = new Map(workspaces.map((w) => [w.name, new Set()]));
|
|
59
|
+
for (const w of workspaces) {
|
|
60
|
+
for (const dep of manifestFacts(readFile(path.join(root, w.manifest))).paths) {
|
|
61
|
+
const target = byDir.get(posix(path.posix.normalize(path.posix.join(w.dir, posix(dep)))));
|
|
62
|
+
if (target && target !== w.name) deps.get(w.name).add(target);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return deps;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Set<name>: every crate that path-depends on any of `names`, transitively; `names` themselves are excluded. */
|
|
69
|
+
export function reverseDependents(names, deps) {
|
|
70
|
+
const reverse = new Map();
|
|
71
|
+
for (const [from, tos] of deps) for (const to of tos) (reverse.get(to) ?? reverse.set(to, new Set()).get(to)).add(from);
|
|
72
|
+
const seen = new Set(); const queue = [...names];
|
|
73
|
+
while (queue.length) {
|
|
74
|
+
for (const dependent of reverse.get(queue.shift()) ?? []) {
|
|
75
|
+
if (!seen.has(dependent)) { seen.add(dependent); queue.push(dependent); }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const n of names) seen.delete(n);
|
|
79
|
+
return seen;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The crate whose workspace holds `file` (repository-relative, posix), or null. */
|
|
83
|
+
export function crateOfFile(file, workspaces) {
|
|
84
|
+
let best = null;
|
|
85
|
+
for (const w of workspaces) if (file.startsWith(`${w.dir}/`) && (!best || w.dir.length > best.dir.length)) best = w;
|
|
86
|
+
return best?.name ?? null;
|
|
87
|
+
}
|
package/create-mac-updater.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { spawnSync } from "./background-process.mjs";
|
|
|
3
3
|
import { existsSync, rmSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { signMacUpdaterFiles } from "./macos-signing-session.mjs";
|
|
6
|
+
import { ArtifactPathError, loadSplitArtifactContext, resolveArtifactPath, resolveArtifactWritePath } from "./release-artifact-path.mjs";
|
|
6
7
|
|
|
7
8
|
const args = process.argv.slice(2);
|
|
8
9
|
let app;
|
|
@@ -18,8 +19,19 @@ for (let i = 0; i < args.length; i++) {
|
|
|
18
19
|
}
|
|
19
20
|
if (!app || !output) fail("--app and --output are required");
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// A door split (row 7.04c, R-T2): a target-prefixed `--app` is the bound Cargo target's, an absolute one must lie inside it, and the tarball is
|
|
23
|
+
// written under the snapshot app root (a generated, ignored ledger entry). Outside a split both are resolved against the cwd as always.
|
|
24
|
+
let appPath;
|
|
25
|
+
let outputPath;
|
|
26
|
+
try {
|
|
27
|
+
const artifactPaths = await loadSplitArtifactContext({ cwd });
|
|
28
|
+
const split = artifactPaths.mode === "split";
|
|
29
|
+
appPath = split ? resolveArtifactPath(app, artifactPaths) : path.resolve(cwd, app);
|
|
30
|
+
outputPath = split ? resolveArtifactWritePath(output, artifactPaths) : path.resolve(cwd, output);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof ArtifactPathError) fail(error.message);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
23
35
|
if (!dryRun && !existsSync(appPath)) fail(`signed app not found: ${appPath}`);
|
|
24
36
|
|
|
25
37
|
const env = { ...process.env };
|