@rightkit/release 0.6.15 → 0.6.16
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/deterministic-scans.mjs +9 -1
- package/npm-license-notices.mjs +49 -1
- package/ownership-lockfiles.mjs +13 -1
- package/package.json +1 -1
- package/rightkit-versions.json +1 -1
package/deterministic-scans.mjs
CHANGED
|
@@ -2500,7 +2500,15 @@ export async function runScans(plan, options = {}) {
|
|
|
2500
2500
|
const spawnArgs = shellcheckIdentity ? ["-shellcheck", shellcheckIdentity.path, ...finalArgs] : finalArgs;
|
|
2501
2501
|
const trustedDirs = trusts.map((tool) => identities.find((i) => i.tool === tool)).filter(Boolean).map((i) => path.dirname(i.path));
|
|
2502
2502
|
const pathKey = Object.keys({ ...deps.env, ...s.env }).find((k) => k.toLowerCase() === "path") ?? "PATH";
|
|
2503
|
-
|
|
2503
|
+
// The gate's cargo wrapper stays first on PATH, ahead of the trusted directories: an enrolled analyzer's directory may also hold
|
|
2504
|
+
// a real cargo (cargo-deny in ~/.cargo/bin beside rustup's cargo), which would otherwise answer the scan's `cargo` unbound and,
|
|
2505
|
+
// with several workspaces, write into the sentinel (rightkit#1327).
|
|
2506
|
+
const scanPath = ({ ...deps.env, ...s.env })[pathKey];
|
|
2507
|
+
const pathSep = deps.platform === "win32" ? ";" : ":";
|
|
2508
|
+
const scanCargo = ({ ...deps.env, ...s.env }).CARGO;
|
|
2509
|
+
const wrapperBin = (cargoLedger || options.cargoSentinel) && typeof scanCargo === "string" && typeof scanPath === "string" ? (deps.platform === "win32" ? path.win32 : path.posix).dirname(scanCargo) : null;
|
|
2510
|
+
const wrapperFirst = wrapperBin !== null && scanPath.split(pathSep)[0] === wrapperBin;
|
|
2511
|
+
const trustEnv = trustedDirs.length ? { [pathKey]: (wrapperFirst ? [wrapperBin, ...trustedDirs, scanPath.slice(wrapperBin.length + 1)] : [...trustedDirs, scanPath]).filter(Boolean).join(pathSep) } : {};
|
|
2504
2512
|
try { r = await deps.spawn(p.command, spawnArgs, { cwd: path.join(rootDir(s.root), s.cwd ?? ""), env: { ...deps.env, ...sentinelEnv, ...readTargetEnv, ...s.env, ...(extraEnv ?? {}), ...guardEnv, ...ledgerEnv, ...(carrier ? carrier.made.env : {}), ...(carrier ? scanOperationEnv({ ...deps.env, ...s.env }, limitMs) : {}), ...trustEnv }, signal: scanSignal, timeout: limitMs }); } finally { clearTimeout(clock); advisory?.cleanup?.(); }
|
|
2505
2513
|
}
|
|
2506
2514
|
if (signal?.aborted && (!r || r.error || r.status !== 0)) return { ...row, status: "not-run", code: "CANCELLED", detail: "cancelled while running" };
|
package/npm-license-notices.mjs
CHANGED
|
@@ -46,6 +46,46 @@ export function collectNpmPackages(groups, exclude = ["@rightkit/"]) {
|
|
|
46
46
|
.sort((a, b) => `${a.name}@${a.versions.join(",")}`.localeCompare(`${b.name}@${b.versions.join(",")}`));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The production closure of `pnpm list --json --depth Infinity` output: every package reachable from a project's `dependencies` or
|
|
51
|
+
* `optionalDependencies` through the dependencies, optional dependencies and resolved peer dependencies pnpm lists, as
|
|
52
|
+
* `name@version` -> { name, version, path }. pnpm 12's `licenses list --prod` no longer follows a peer edge (an optional peer of a
|
|
53
|
+
* production package that the app also installs as a dev dependency, so `@rightkit/legal` -> `@rightkit/release` -> smol-toml went
|
|
54
|
+
* missing), but the package is still installed beside the production package and loadable by it; the notices must list it.
|
|
55
|
+
*/
|
|
56
|
+
export function productionClosure(tree) {
|
|
57
|
+
if (!Array.isArray(tree)) fail("pnpm list --json did not return a project array");
|
|
58
|
+
const closure = new Map(), seen = new Set();
|
|
59
|
+
const visit = (name, node) => {
|
|
60
|
+
if (!node || typeof node !== "object" || typeof node.version !== "string" || node.version.startsWith("link:")) return;
|
|
61
|
+
const id = `${name}@${node.version}`, at = `${id}|${node.path ?? ""}`;
|
|
62
|
+
if (seen.has(at)) return;
|
|
63
|
+
seen.add(at);
|
|
64
|
+
if (!closure.has(id)) closure.set(id, { name, version: node.version, path: node.path });
|
|
65
|
+
for (const section of ["dependencies", "optionalDependencies"]) for (const [child, value] of Object.entries(node[section] ?? {})) visit(child, value);
|
|
66
|
+
};
|
|
67
|
+
for (const project of tree) for (const section of ["dependencies", "optionalDependencies"]) for (const [name, node] of Object.entries(project?.[section] ?? {})) visit(name, node);
|
|
68
|
+
return closure;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `groups` (the `--prod` listing) plus every closure package it lacks, with its license read from `allGroups` (the full listing,
|
|
73
|
+
* fetched lazily by `loadAll`) and its installed path from the tree. A missing package that the full listing does not know is an
|
|
74
|
+
* error: a notice is never silently shorter than the production closure. Excluded prefixes are skipped before the lookup.
|
|
75
|
+
*/
|
|
76
|
+
export function completeProductionGroups(groups, closure, loadAll, exclude = ["@rightkit/"]) {
|
|
77
|
+
const listed = new Set(Object.values(groups).flatMap((entries) => entries.flatMap((entry) => entry.versions.map((v) => `${entry.name}@${v}`))));
|
|
78
|
+
const missing = [...closure].filter(([id, item]) => !listed.has(id) && !exclude.some((prefix) => item.name.startsWith(prefix)));
|
|
79
|
+
if (!missing.length) return groups;
|
|
80
|
+
const all = loadAll(), completed = Object.fromEntries(Object.entries(groups).map(([license, entries]) => [license, [...entries]]));
|
|
81
|
+
for (const [id, item] of missing) {
|
|
82
|
+
const hit = Object.entries(all).find(([, entries]) => entries.some((entry) => entry.name === item.name && entry.versions.includes(item.version)));
|
|
83
|
+
if (!hit || typeof item.path !== "string") fail(`${id} is in the production closure (pnpm list) but not in pnpm licenses list: its notice cannot be produced`);
|
|
84
|
+
(completed[hit[0]] ??= []).push({ name: item.name, versions: [item.version], paths: [item.path] });
|
|
85
|
+
}
|
|
86
|
+
return completed;
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Render the appendix. `readNames(dir)` lists a package directory and `readText(file)` reads one file (both injectable).
|
|
51
91
|
* Returns `{ html, packageCount, fallbackCount }`.
|
|
@@ -106,7 +146,15 @@ export function generateNpmNotices({ appRoot, config, flags = {}, check = false,
|
|
|
106
146
|
});
|
|
107
147
|
if (result.error) throw result.error;
|
|
108
148
|
if (result.status !== 0) fail(result.stderr || "pnpm licenses list failed");
|
|
109
|
-
const
|
|
149
|
+
const run = (args) => {
|
|
150
|
+
const out = spawnSyncFn("pnpm", args, { cwd: appRoot, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, shell: process.platform === "win32" });
|
|
151
|
+
if (out.error) throw out.error;
|
|
152
|
+
if (out.status !== 0) fail(out.stderr || `pnpm ${args.join(" ")} failed`);
|
|
153
|
+
return JSON.parse(out.stdout);
|
|
154
|
+
};
|
|
155
|
+
const closure = productionClosure(run(["list", "--json", "--depth", "Infinity"]));
|
|
156
|
+
const groups = completeProductionGroups(JSON.parse(result.stdout), closure, () => run(["licenses", "list", "--json"]), exclude);
|
|
157
|
+
const packages = collectNpmPackages(groups, exclude);
|
|
110
158
|
const lockHash = createHash("sha256").update(readFileSync(path.join(appRoot, "pnpm-lock.yaml"))).digest("hex");
|
|
111
159
|
const { html, packageCount, fallbackCount } = renderNpmNotices({ productName, version, lockHash, packages });
|
|
112
160
|
const target = path.join(appRoot, output);
|
package/ownership-lockfiles.mjs
CHANGED
|
@@ -244,8 +244,20 @@ function packageLockGraph(text, manifests, lockDir) {
|
|
|
244
244
|
}
|
|
245
245
|
return g;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* pnpm 12 writes the lockfile as several YAML documents: leading ones carry the tooling pnpm installs for itself
|
|
249
|
+
* (configDependencies, packageManagerDependencies), and the last one is the project lock. Only the project's document is the
|
|
250
|
+
* dependency graph; pnpm 6/9/11 write a single document, which is returned unchanged.
|
|
251
|
+
*/
|
|
252
|
+
export function pnpmProjectDocument(text) {
|
|
253
|
+
const docs = text.split(/^---[ \t]*\r?$/m).filter((doc) => doc.trim());
|
|
254
|
+
if (docs.length <= 1) return text;
|
|
255
|
+
const last = docs.at(-1);
|
|
256
|
+
if (!/^lockfileVersion:/m.test(last)) fail("pnpm multi-document lockfile does not end with the project document");
|
|
257
|
+
return last;
|
|
258
|
+
}
|
|
247
259
|
function pnpmGraph(text, manifests, lockDir, workspaceBytes) {
|
|
248
|
-
const lock = parsePnpmYaml(text), version = String(lock.lockfileVersion), g = graph();
|
|
260
|
+
const lock = parsePnpmYaml(pnpmProjectDocument(text)), version = String(lock.lockfileVersion), g = graph();
|
|
249
261
|
if (!/^(?:9|6)(?:\.\d+)?$/.test(version) || !object(lock.importers) || !object(lock.packages ?? {})) throw new LockError("lockfile-unsupported-format", "pnpm requires v6/v9 importers");
|
|
250
262
|
const v9 = version.startsWith("9"), packages = lock.packages ?? {}, snapshots = v9 ? lock.snapshots ?? {} : packages;
|
|
251
263
|
if (!object(snapshots)) fail("invalid pnpm snapshots");
|
package/package.json
CHANGED
package/rightkit-versions.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@rightkit/qa": "0.6.2",
|
|
33
33
|
"@rightkit/qa-chrome": "0.1.12",
|
|
34
34
|
"@rightkit/react-shell": "0.7.2",
|
|
35
|
-
"@rightkit/release": "0.6.
|
|
35
|
+
"@rightkit/release": "0.6.16",
|
|
36
36
|
"@rightkit/settings-ui": "0.7.0",
|
|
37
37
|
"@rightkit/tauri": "0.3.0",
|
|
38
38
|
"@rightkit/tauri-shell": "0.1.3",
|