@rightkit/release 0.6.3 → 0.6.5
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 +67 -5
- package/admission-files.mjs +279 -0
- package/admission-host.mjs +403 -0
- package/admission.mjs +146 -0
- package/app-check.mjs +869 -0
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/rightkit-gate.mjs +68 -29
- package/build-release.mjs +81 -21
- package/cli/right-release.mjs +39 -5
- package/deterministic-scans.mjs +42 -15
- package/development.mjs +3 -2
- package/hosted-workflow-policy.mjs +111 -0
- package/inventory-check.mjs +12 -1
- package/ios-dev.mjs +2 -2
- package/ios-host.mjs +7 -7
- package/ios-package-recipe.mjs +2 -2
- package/local-publish.mjs +1 -1
- package/local-qualify.mjs +3 -3
- package/local-runtime.mjs +1 -1
- package/ownership-dependency-allowlist.json +2 -0
- package/package.json +5 -3
- package/pipeline-admission.mjs +5 -4
- package/pipeline-receipts.mjs +302 -302
- package/pipeline-scan.mjs +3 -3
- package/pipeline-tools.mjs +175 -171
- package/prebuild-gates.mjs +191 -0
- package/progress-control.mjs +1 -1
- package/publish-update.mjs +4 -3
- package/qa-admission-policy.json +5 -0
- package/release-config.mjs +353 -330
- package/release-qa-audit.mjs +37 -11
- package/release-qa-command.mjs +46 -5
- package/release-qa-fixtures.mjs +127 -0
- package/release-qa-floor.json +6 -1
- package/release-qa-hardening.mjs +173 -0
- package/release-qa-policy.mjs +168 -0
- package/release-qa-receipt.mjs +21 -1
- package/release-switch-scan.mjs +137 -6
- package/release.mjs +333 -231
- package/rightapps-register.mjs +3 -1
- package/rightkit-drift.mjs +1 -1
- package/rightkit-ownership.json +15 -5
- package/rightkit-versions.json +50 -37
- package/stage-sidecar.mjs +17 -15
- package/suite-api-literal.mjs +109 -0
- package/suite-api.generated.mjs +78 -0
- package/swap-helper-bundle.mjs +2 -2
- package/win-package-recipe.mjs +7 -4
package/development.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export function developmentEnvironment(env = process.env) {
|
|
|
50
50
|
result[pathKey] = [agentBin, result[pathKey]].filter(Boolean).join(path.delimiter);
|
|
51
51
|
}
|
|
52
52
|
// Same default as the agent-bin `rightkit` shim: children that run the managed client directly
|
|
53
|
-
// (resolveCommand("
|
|
53
|
+
// (resolveCommand("rustright"), build scripts) must not lose the broker endpoint.
|
|
54
54
|
const socket = discoverBrokerSocket(result);
|
|
55
55
|
if (!result.RIGHTKIT_BUILD_BROKER_SOCKET && socket && existsSync(socket)) result.RIGHTKIT_BUILD_BROKER_SOCKET = socket;
|
|
56
56
|
}
|
|
@@ -298,7 +298,8 @@ function execute(command, { root, env, dryRun, run, label }) {
|
|
|
298
298
|
export function resolveCommand(command, args, { root, env = process.env } = {}) {
|
|
299
299
|
if (command === "node") return { command: process.execPath, args };
|
|
300
300
|
if (command === "pnpm") return { command: process.execPath, args: [resolvePnpmCli({ root, env }), ...args] };
|
|
301
|
-
if (command === "rightkit")
|
|
301
|
+
if (command === "rightkit") throw new Error("`rightkit` is the retired name of the RustRight client (rustright#91); use rustright (cmd: \"rustright\")");
|
|
302
|
+
if (command === "rustright") {
|
|
302
303
|
const client = managedRuntimeClient(env);
|
|
303
304
|
if (!client) throw new Error("installed RightKit runtime client is unavailable for development command");
|
|
304
305
|
const socket = discoverBrokerSocket(env);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// hostedWorkflows: { policy: "declared-unrelated", files: { "<file>.yml": "<reason>" | { reason, idToken } } }
|
|
2
|
+
//
|
|
3
|
+
// For a repository that ships more than the released app (a monorepo whose web or server pipeline is a hand-written
|
|
4
|
+
// GitHub workflow). The released app still builds, signs and uploads only through right-release on its native host;
|
|
5
|
+
// every hosted workflow must be declared with the reason it exists, and none may be able to build, sign or upload a
|
|
6
|
+
// release. What makes that hold is credentials: an admitted workflow reaches no secret but GITHUB_TOKEN (no secrets
|
|
7
|
+
// context, no `secrets: inherit`, no reusable workflow, no escape that could spell one), mints no OIDC token unless the
|
|
8
|
+
// file declares why (`idToken`), and runs only commit-pinned actions. Without an Apple, Azure or store credential it
|
|
9
|
+
// cannot sign or upload as Orthic Labs. The command and artifact patterns are a denylist backstop, not the guarantee:
|
|
10
|
+
// a script file the workflow runs is not read.
|
|
11
|
+
export const DECLARED_UNRELATED_POLICY = "declared-unrelated";
|
|
12
|
+
|
|
13
|
+
const FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,60}\.ya?ml$/;
|
|
14
|
+
const MIN_REASON = 12;
|
|
15
|
+
|
|
16
|
+
// Release machinery a declared-unrelated workflow must never run. Matched on the whole file, comments included: a comment cannot hide a command.
|
|
17
|
+
const RELEASE_MACHINERY = [
|
|
18
|
+
[/\bright-release\b/, "right-release"],
|
|
19
|
+
[/\bxcodebuild\b[^\n]*(?:\barchive\b|-exportArchive\b|-exportOptionsPlist\b)/, "xcodebuild archive/export"],
|
|
20
|
+
[/\bcodesign\b/, "codesign"],
|
|
21
|
+
[/\bnotarytool\b|\bstapler\b/, "notarization"],
|
|
22
|
+
[/\baltool\b|\biTMSTransporter\b|\bupload-testflight\b|\bapp-store-connect\b/i, "App Store upload"],
|
|
23
|
+
[/\bfastlane\b/, "fastlane"],
|
|
24
|
+
[/\bsecurity\s+import\b|\bapple-actions\//, "Apple signing keychain import"],
|
|
25
|
+
[/\bproductbuild\b|\bpkgbuild\b/, "installer package build"],
|
|
26
|
+
[/\bsigntool\b|\bAzureSignTool\b|\bazure\/(?:trusted|artifact)-signing/i, "Windows code signing"],
|
|
27
|
+
[/\btauri(?:-apps\/tauri-action|\s+build\b)/, "Tauri app build"],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// A `uses:` must be a local action (./...), a docker image by digest, or owner/repo[/path]@<40-hex commit>.
|
|
31
|
+
const PINNED_USES = /^(?:\.\/[^\s]*|docker:\/\/[^\s@]+@sha256:[0-9a-f]{64}|[A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+@[0-9a-f]{40})$/;
|
|
32
|
+
|
|
33
|
+
const normalize = (source) => source.replace(/\r\n?/g, "\n");
|
|
34
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
35
|
+
const entryReason = (entry) => (isObject(entry) ? entry.reason : entry);
|
|
36
|
+
|
|
37
|
+
/** Shape problems of a declared-unrelated hostedWorkflows value (an empty list when it is well formed). */
|
|
38
|
+
export function declaredUnrelatedShapeProblems(value) {
|
|
39
|
+
if (!isObject(value)) return ["hostedWorkflows must be a policy string or an object"];
|
|
40
|
+
const problems = [];
|
|
41
|
+
for (const key of Object.keys(value)) if (key !== "policy" && key !== "files") problems.push(`hostedWorkflows.${key} is not a known field (policy, files)`);
|
|
42
|
+
if (value.policy !== DECLARED_UNRELATED_POLICY) problems.push(`hostedWorkflows.policy must be "${DECLARED_UNRELATED_POLICY}"`);
|
|
43
|
+
const files = value.files;
|
|
44
|
+
if (!isObject(files) || Object.keys(files).length === 0) {
|
|
45
|
+
problems.push("hostedWorkflows.files must map each workflow file name to the reason it exists");
|
|
46
|
+
return problems;
|
|
47
|
+
}
|
|
48
|
+
for (const [file, entry] of Object.entries(files)) {
|
|
49
|
+
if (!FILE_NAME.test(file)) problems.push(`hostedWorkflows.files has an invalid workflow file name ${JSON.stringify(file)}`);
|
|
50
|
+
if (isObject(entry)) {
|
|
51
|
+
for (const key of Object.keys(entry)) if (key !== "reason" && key !== "idToken") problems.push(`hostedWorkflows.files["${file}"].${key} is not a known field (reason, idToken)`);
|
|
52
|
+
if (entry.idToken !== undefined && (typeof entry.idToken !== "string" || entry.idToken.trim().length < MIN_REASON)) problems.push(`hostedWorkflows.files["${file}"].idToken must say why the workflow mints an OIDC token (at least ${MIN_REASON} characters)`);
|
|
53
|
+
}
|
|
54
|
+
const reason = entryReason(entry);
|
|
55
|
+
if (typeof reason !== "string" || reason.trim().length < MIN_REASON) problems.push(`hostedWorkflows.files["${file}"] must say why the workflow exists (at least ${MIN_REASON} characters)`);
|
|
56
|
+
}
|
|
57
|
+
return problems;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function credentialRefusal(file, source, entry) {
|
|
61
|
+
// GitHub decodes YAML escapes before it evaluates expressions, so an escape could spell `secrets` or a command.
|
|
62
|
+
if (/\\[xuU]/.test(source)) return `${file} contains a \\x, \\u or \\U escape; a declared-unrelated workflow must spell every name literally`;
|
|
63
|
+
// The only secret is GITHUB_TOKEN; any other use of the word (secrets.X, secrets['X'], secrets: inherit, toJSON(secrets),
|
|
64
|
+
// join(secrets)) is refused. A path segment such as /run/secrets/ is not the context.
|
|
65
|
+
const rest = source.replace(/\bsecrets\.GITHUB_TOKEN\b/g, "");
|
|
66
|
+
if (/(?<![\w/-])secrets\b(?![\w/-])/i.test(rest)) return `${file} references secrets other than GITHUB_TOKEN (or passes the secrets context on); a declared-unrelated workflow can hold no signing or store credential`;
|
|
67
|
+
for (const [, ref] of source.matchAll(/^\s*(?:-\s+)?uses:\s*["']?([^\s"'#]+)/gm)) {
|
|
68
|
+
if (/\.github\/workflows\//.test(ref)) return `${file} calls the reusable workflow ${ref}; a called workflow is not checked`;
|
|
69
|
+
if (!PINNED_USES.test(ref)) return `${file} uses ${ref}, which is not pinned to a full commit sha (or a local action, or a docker image by digest)`;
|
|
70
|
+
}
|
|
71
|
+
if (/^\s*id-token\s*:\s*write\b/m.test(source) && !(isObject(entry) && entry.idToken)) return `${file} mints an OIDC token (id-token: write) without declaring why; set hostedWorkflows.files["${file}"] to { reason, idToken: "<why>" }`;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Why a declared-unrelated workflow set is refused, or null when it is admitted.
|
|
77
|
+
* workflows: the file names present in .github/workflows; read(file): its text; artifacts: release artifact paths
|
|
78
|
+
* from the config's targets (a workflow that names one could produce or ship it).
|
|
79
|
+
*/
|
|
80
|
+
export function declaredUnrelatedRefusal({ declaration, workflows, read, artifacts = [] }) {
|
|
81
|
+
const shape = declaredUnrelatedShapeProblems(declaration);
|
|
82
|
+
if (shape.length) return shape.join("; ");
|
|
83
|
+
const declared = Object.keys(declaration.files).sort();
|
|
84
|
+
const present = [...workflows].sort();
|
|
85
|
+
const undeclared = present.filter((file) => !declared.includes(file));
|
|
86
|
+
if (undeclared.length) return `undeclared hosted workflow file(s): ${undeclared.join(", ")} (declare each in hostedWorkflows.files with its reason, or remove it)`;
|
|
87
|
+
const missing = declared.filter((file) => !present.includes(file));
|
|
88
|
+
if (missing.length) return `hostedWorkflows.files declares workflow file(s) that do not exist: ${missing.join(", ")}`;
|
|
89
|
+
for (const file of present) {
|
|
90
|
+
const source = normalize(read(file));
|
|
91
|
+
const credential = credentialRefusal(file, source, declaration.files[file]);
|
|
92
|
+
if (credential) return credential;
|
|
93
|
+
for (const [pattern, label] of RELEASE_MACHINERY) {
|
|
94
|
+
if (pattern.test(source)) return `${file} runs release machinery (${label}); the released app builds, signs and uploads only through right-release on its native host`;
|
|
95
|
+
}
|
|
96
|
+
for (const artifact of artifacts) {
|
|
97
|
+
if (artifact && source.includes(artifact)) return `${file} names the release artifact ${artifact}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Release artifact paths a config's targets declare (artifacts, iosPackage archive and ipa). */
|
|
104
|
+
export function releaseArtifactPaths(config) {
|
|
105
|
+
const paths = new Set();
|
|
106
|
+
for (const target of Object.values(config?.targets ?? {})) {
|
|
107
|
+
for (const artifact of target?.artifacts ?? []) if (typeof artifact === "string") paths.add(artifact);
|
|
108
|
+
for (const key of ["archive", "ipa"]) if (typeof target?.iosPackage?.[key] === "string") paths.add(target.iosPackage[key]);
|
|
109
|
+
}
|
|
110
|
+
return [...paths];
|
|
111
|
+
}
|
package/inventory-check.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
generateAdoption,
|
|
17
17
|
generateLocal,
|
|
18
18
|
} from "../../scripts/generate-index.mjs";
|
|
19
|
+
import { PORT_MATRIX_OUTPUTS, generatePortMatrix } from "../../scripts/generate-port-matrix.mjs";
|
|
19
20
|
|
|
20
21
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
export const DEFAULT_REPO_ROOT = resolve(HERE, "..", "..");
|
|
@@ -781,6 +782,16 @@ function checkGenerated({ repoRoot, appsRoot, apps, findings }) {
|
|
|
781
782
|
for (const rel of driftOf(repoRoot, adoption)) {
|
|
782
783
|
findings.push(issue("generated", rel, "generated-doc-drift", "committed adoption report differs from the app repos; run generate-index.mjs"));
|
|
783
784
|
}
|
|
785
|
+
// The port matrix reads the app repos like the adoption report. Its stamp, app directories and HEADs, and per-app check counts
|
|
786
|
+
// are carried over from the committed docs/port-matrix.json, so only a change in the data itself reads as drift.
|
|
787
|
+
try {
|
|
788
|
+
const matrix = generatePortMatrix({ root: repoRoot, workspace: appsRoot, carry: true });
|
|
789
|
+
for (const rel of driftOf(repoRoot, matrix.outputs)) {
|
|
790
|
+
findings.push(issue("generated", rel, "generated-doc-drift", "committed port matrix differs from the app repos; run generate-index.mjs"));
|
|
791
|
+
}
|
|
792
|
+
} catch (error) {
|
|
793
|
+
findings.push(issue("generated", PORT_MATRIX_OUTPUTS[2], "generated-doc-drift", `the port matrix could not be regenerated: ${error.message}`));
|
|
794
|
+
}
|
|
784
795
|
return { local, adoption: Object.values(adoption)[0] };
|
|
785
796
|
}
|
|
786
797
|
|
|
@@ -842,7 +853,7 @@ export async function runInventoryCheck(options = {}) {
|
|
|
842
853
|
account: { crates: account.crates, npm: account.npm },
|
|
843
854
|
thirdParty: { declarations: thirdParty.declarations, uniquePackages: thirdParty.uniquePackages, lockDuplicates: thirdParty.lockDuplicates },
|
|
844
855
|
apps: { rows: appRows },
|
|
845
|
-
generated: { checked: ["INDEX.md", "docs/CAPABILITY-MAP.md", "docs/ADOPTION.md"] },
|
|
856
|
+
generated: { checked: ["INDEX.md", "docs/CAPABILITY-MAP.md", "docs/ADOPTION.md", ...PORT_MATRIX_OUTPUTS] },
|
|
846
857
|
hygiene: { checked: records.length },
|
|
847
858
|
},
|
|
848
859
|
};
|
package/ios-dev.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// | { kind: "device", device: "<devicectl identifier>", bundleId, productName?, launch?: true, launchArgs?: [] },
|
|
14
14
|
// }
|
|
15
15
|
// The build commands are generated from the config (a hand-written `build` is refused). The device is chosen by id only, never
|
|
16
|
-
// "the only device": `install.device`, `--serial` or RIGHTKIT_IOS_DEVICE. DerivedData comes from `
|
|
16
|
+
// "the only device": `install.device`, `--serial` or RIGHTKIT_IOS_DEVICE. DerivedData comes from `rustright storage xcode-args`.
|
|
17
17
|
import * as nodeFs from "node:fs";
|
|
18
18
|
import os from "node:os";
|
|
19
19
|
import path from "node:path";
|
|
@@ -102,7 +102,7 @@ export function validateIosDevelopment(target, { root, env = process.env } = {})
|
|
|
102
102
|
install = { ...descriptor, productName: descriptor.productName ?? target.scheme, launchArgs: [...(descriptor.launchArgs ?? [])] };
|
|
103
103
|
}
|
|
104
104
|
const normalized = { project, projectSpec, scheme: target.scheme, configuration, teamId: target.teamId, extraBuildSettings, prepare, install };
|
|
105
|
-
return { ...normalized, build: iosDevelopmentBuildCommands(normalized, { root, storage: { derivedData: "<
|
|
105
|
+
return { ...normalized, build: iosDevelopmentBuildCommands(normalized, { root, storage: { derivedData: "<rustright storage xcode-args -derivedDataPath>" }, destinationId: "<device or simulator id>" }) };
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const destinationFor = (install, id) => (install?.kind === "device" ? `id=${id}` : `platform=iOS Simulator,id=${id}`);
|
package/ios-host.mjs
CHANGED
|
@@ -34,29 +34,29 @@ export function assertGuiSigningSession({ operation, env = process.env, run = sp
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* DerivedData and result-bundle paths. With RIGHTKIT_RUN_ROOT set they come from `
|
|
37
|
+
* DerivedData and result-bundle paths. With RIGHTKIT_RUN_ROOT set they come from `rustright storage xcode-args --run-root <root>`
|
|
38
38
|
* (a JSON argv list holding -derivedDataPath and -resultBundlePath), the same source CodeRight and HeardRight use. Without a
|
|
39
39
|
* managed run the fallback is a directory under the right-release state root, never an in-repo DerivedData.
|
|
40
40
|
*/
|
|
41
41
|
export function resolveXcodeStorage({ env = process.env, run = spawnSync, cwd = process.cwd(), fallbackRoot }) {
|
|
42
42
|
const runRoot = typeof env.RIGHTKIT_RUN_ROOT === "string" ? env.RIGHTKIT_RUN_ROOT.trim() : "";
|
|
43
43
|
if (!runRoot) {
|
|
44
|
-
if (!fallbackRoot) throw new IosHostError("IOS_STORAGE_UNAVAILABLE", "no RIGHTKIT_RUN_ROOT and no fallback storage root; run via:
|
|
44
|
+
if (!fallbackRoot) throw new IosHostError("IOS_STORAGE_UNAVAILABLE", "no RIGHTKIT_RUN_ROOT and no fallback storage root; run via: rustright storage run --app <app> -- right-release ...");
|
|
45
45
|
return { derivedData: path.join(fallbackRoot, "DerivedData"), resultBundle: undefined, source: "fallback" };
|
|
46
46
|
}
|
|
47
|
-
const result = run("
|
|
48
|
-
if (result.error || result.status !== 0) throw new IosHostError("IOS_STORAGE_FAILED", `
|
|
47
|
+
const result = run("rustright", ["storage", "xcode-args", "--run-root", runRoot], { cwd, env, encoding: "utf8", windowsHide: true, shell: false, timeout: 30_000 });
|
|
48
|
+
if (result.error || result.status !== 0) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args failed (${result.status ?? "unavailable"})`);
|
|
49
49
|
let args;
|
|
50
50
|
try { args = JSON.parse(String(result.stdout ?? "")); } catch { args = null; }
|
|
51
|
-
if (!Array.isArray(args) || !args.length || args.some((arg) => typeof arg !== "string")) throw new IosHostError("IOS_STORAGE_FAILED", "
|
|
51
|
+
if (!Array.isArray(args) || !args.length || args.some((arg) => typeof arg !== "string")) throw new IosHostError("IOS_STORAGE_FAILED", "rustright storage xcode-args returned no argv list");
|
|
52
52
|
const value = (flag, required) => {
|
|
53
53
|
const index = args.indexOf(flag);
|
|
54
54
|
if (index < 0 || index + 1 >= args.length) {
|
|
55
|
-
if (required) throw new IosHostError("IOS_STORAGE_FAILED", `
|
|
55
|
+
if (required) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args is missing ${flag}`);
|
|
56
56
|
return undefined;
|
|
57
57
|
}
|
|
58
58
|
const found = args[index + 1];
|
|
59
|
-
if (!path.isAbsolute(found)) throw new IosHostError("IOS_STORAGE_FAILED", `
|
|
59
|
+
if (!path.isAbsolute(found)) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args ${flag} is not an absolute path`);
|
|
60
60
|
return found;
|
|
61
61
|
};
|
|
62
62
|
return { derivedData: value("-derivedDataPath", true), resultBundle: value("-resultBundlePath", false), source: "rightkit-storage" };
|
package/ios-package-recipe.mjs
CHANGED
|
@@ -378,7 +378,7 @@ const plannedTrust = (spec) => {
|
|
|
378
378
|
};
|
|
379
379
|
|
|
380
380
|
/** The ordered plan (pure; no secret is part of any argument). Used by --dry-run and by the run itself. */
|
|
381
|
-
export function iosPackagePlan(spec, { root, buildNumber = "<YYYYMMDDNN>", storage = { derivedData: "<
|
|
381
|
+
export function iosPackagePlan(spec, { root, buildNumber = "<YYYYMMDDNN>", storage = { derivedData: "<rustright storage xcode-args -derivedDataPath>" }, receiptDir }) {
|
|
382
382
|
const optionsFile = path.join(receiptDir, "ios-export-options.plist");
|
|
383
383
|
return {
|
|
384
384
|
steps: [
|
|
@@ -504,7 +504,7 @@ export function scanForbiddenStrings(dir, strings, fs = nodeFs) {
|
|
|
504
504
|
* scanFiles(label, files, o) release.mjs's assertNoQaControlBinaries (QA rules plus the release-switch binary layer)
|
|
505
505
|
* detectSession / route GUI signing session seams (ios-host.mjs)
|
|
506
506
|
* gitHead() the source commit; it must not change during the build
|
|
507
|
-
* storage() DerivedData and result bundle (
|
|
507
|
+
* storage() DerivedData and result bundle (rustright storage xcode-args)
|
|
508
508
|
* scanForbidden(dir, strings) forbidden-string scan
|
|
509
509
|
* counterDir where the per-app build number counter lives (the vault state root under right-release build)
|
|
510
510
|
*/
|
package/local-publish.mjs
CHANGED
|
@@ -529,7 +529,7 @@ async function publishUnit(parsed, deps, batch) {
|
|
|
529
529
|
// #258: the package ships a Developer-ID-signed bin/darwin/rightkit-swap; build, sign and verify it before packing (fails closed).
|
|
530
530
|
try { (deps.prepareSwapHelper ?? prepareSwapHelper)({ workspaceRoot, packageDir: unitPath, platform, run, env, log }); }
|
|
531
531
|
catch (error) { refuse(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
|
|
532
|
-
packCommands.push(`
|
|
532
|
+
packCommands.push(`rustright cargo ${SWAP_HELPER_CARGO_ARGS.join(" ")} && codesign --verify --strict bin/darwin/rightkit-swap`);
|
|
533
533
|
}
|
|
534
534
|
if (kind === "npm") {
|
|
535
535
|
// The feed a tarball ships must list the version it ships with (#319): stage it before packing.
|
package/local-qualify.mjs
CHANGED
|
@@ -370,16 +370,16 @@ function brokerIdentity(deps) {
|
|
|
370
370
|
return { name: "rustright", client: String(client).replaceAll("\\", "/"), sha256: sha256((deps.readFile ?? fs.readFileSync)(client)) };
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
// Cargo only through the broker: `
|
|
373
|
+
// Cargo only through the broker: `rustright cargo`, or a `cargo` on PATH that is the managed shim (scripts/local-gate.mjs).
|
|
374
374
|
function detectCargoCommand(deps) {
|
|
375
375
|
const which = (command) => {
|
|
376
376
|
const result = (deps.run ?? spawnSync)((deps.platform ?? process.platform) === "win32" ? "where" : "which", [command], { encoding: "utf8", windowsHide: true });
|
|
377
377
|
return result.status === 0 ? String(result.stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
|
|
378
378
|
};
|
|
379
|
-
if (which("
|
|
379
|
+
if (which("rustright").length) return ["rustright", "cargo"];
|
|
380
380
|
const first = which("cargo")[0];
|
|
381
381
|
if (first) { try { if (fs.readFileSync(first, "utf8").includes("cargo-guard.mjs")) return ["cargo"]; } catch { /* not the managed shim */ } }
|
|
382
|
-
return refuse("neither `
|
|
382
|
+
return refuse("neither `rustright` nor the managed cargo shim (cargo-guard.mjs) is first on PATH; plain cargo is never used");
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
const loadFromRoot = (root, file) => {
|
package/local-runtime.mjs
CHANGED
|
@@ -176,7 +176,7 @@ export async function measureRuntimeCrate(options, deps = {}) {
|
|
|
176
176
|
const run = deps.run ?? ((command, args, settings) => spawnCollect(command, args, { maxBuffer: 64 * 1024 * 1024, ...settings }));
|
|
177
177
|
const invoke = (args, settings) => run(cargoCommand[0], [...cargoCommand.slice(1), ...args], { cwd: root, windowsHide: true, ...settings });
|
|
178
178
|
const toolchainOf = async () => {
|
|
179
|
-
const rustc = await run(cargoCommand[0] === "
|
|
179
|
+
const rustc = await run(cargoCommand[0] === "rustright" ? "rustright" : "rustc", cargoCommand[0] === "rustright" ? ["rustc", "-Vv"] : ["-Vv"], { cwd: root, env: { ...baseEnv, ...brokerEnv }, windowsHide: true, timeout: 60_000 });
|
|
180
180
|
const cargo = await invoke(["-V"], { env: { ...baseEnv, ...brokerEnv }, timeout: 60_000 });
|
|
181
181
|
if (rustc.error || rustc.status !== 0 || cargo.error || cargo.status !== 0) throw new Error("cannot read the toolchain through the broker");
|
|
182
182
|
return { rustc: String(rustc.stdout).trim(), cargo: String(cargo.stdout).trim() };
|
|
@@ -399,10 +399,12 @@
|
|
|
399
399
|
"cargo:base64",
|
|
400
400
|
"cargo:block2",
|
|
401
401
|
"cargo:chrono",
|
|
402
|
+
"cargo:clap",
|
|
402
403
|
"cargo:dirs",
|
|
403
404
|
"cargo:futures",
|
|
404
405
|
"cargo:getrandom",
|
|
405
406
|
"cargo:image",
|
|
407
|
+
"cargo:libc",
|
|
406
408
|
"cargo:objc2",
|
|
407
409
|
"cargo:objc2-foundation",
|
|
408
410
|
"cargo:reqwest",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"LICENSE-MIT",
|
|
34
34
|
"LICENSE-APACHE",
|
|
35
35
|
"!*.test.mjs",
|
|
36
|
-
"!
|
|
36
|
+
"!cli/*.test.mjs",
|
|
37
|
+
"!standalone-clone-evidence.json",
|
|
38
|
+
"!refusals.json"
|
|
37
39
|
],
|
|
38
40
|
"sideEffects": false,
|
|
39
41
|
"publishConfig": {
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"smol-toml": "1.9.1"
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
52
|
-
"test": "node --import ./test-support/broker-guard.mjs --test --test-concurrency=1 --test-force-exit *.test.mjs",
|
|
54
|
+
"test": "node --import ./test-support/broker-guard.mjs --test --test-concurrency=1 --test-force-exit *.test.mjs cli/*.test.mjs",
|
|
53
55
|
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
|
54
56
|
"test:signer:windows": "node sign-release-manifest.integration.mjs",
|
|
55
57
|
"doctor:all": "node --test right-suite-contract.test.mjs",
|
package/pipeline-admission.mjs
CHANGED
|
@@ -352,11 +352,12 @@ export function admitInvocation(step, options = {}) {
|
|
|
352
352
|
if (name === "right-release") return admitRightRelease(args, label);
|
|
353
353
|
if (name === "pnpm") return admitPnpm(args, inner, label, chain);
|
|
354
354
|
if (name === "node") return admitNode(args, inner, label);
|
|
355
|
-
if (name === "rightkit") {
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
if (name === "rightkit") return refuse("R1-retired-client", `${label}: rightkit is the retired name of the RustRight client (rustright#91); use rustright ${args.slice(0, 2).join(" ")}`);
|
|
356
|
+
if (name === "rustright") {
|
|
357
|
+
if (args[0] !== "cargo" || !CARGO_SUBCOMMANDS.has(args[1])) return refuse("R1-managed-client", `${label}: only rustright cargo ${[...CARGO_SUBCOMMANDS].join("|")} is admitted`);
|
|
358
|
+
return shellSafeArgs(args, label) ?? admit(`rustright-cargo-${args[1]}`, "managed-cargo");
|
|
358
359
|
}
|
|
359
|
-
if (name === "cargo" || name === "rustc" || name === "rustdoc") return refuse("R1-bare-cargo", `${label}: run Cargo through the managed client (
|
|
360
|
+
if (name === "cargo" || name === "rustc" || name === "rustdoc") return refuse("R1-bare-cargo", `${label}: run Cargo through the managed client (rustright cargo ...), never bare ${name}`);
|
|
360
361
|
if (name === "tauri") return refuse("R1-direct-tauri", `${label}: run the Tauri CLI as pnpm exec tauri ..., or through right-release; a bare tauri is not an approved installed runtime`);
|
|
361
362
|
if (PACKAGE_MANAGERS.has(name)) return refuse("R1-package-manager", `${label}: ${name} is not allowed; the workspace uses pnpm`);
|
|
362
363
|
if (INTERPRETERS.has(name)) return refuse("R1-interpreter", `${label}: ${name} runs script text or a script file the app owns; only an admitted shared generator may do that`);
|