@rightkit/release 0.6.23 → 0.6.25
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 +11 -35
- package/admission-host.mjs +5 -2
- package/android-dev.mjs +2 -1
- package/app-check.mjs +1 -2
- package/app-closure.mjs +3 -2
- package/assembled-app.mjs +11 -3
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +6 -0
- package/bin/rightkit-gate.mjs +83 -26
- package/build-helper-trace.mjs +264 -0
- package/build-helpers-command.mjs +184 -0
- package/build-launch.mjs +11 -75
- package/build-release.mjs +10 -11
- package/cargo-profile-contract.mjs +19 -29
- package/cargo-target-shim.mjs +31 -0
- package/cli/right-release.mjs +5 -7
- package/create-mac-updater.mjs +4 -0
- package/cross-targets.mjs +94 -0
- package/deterministic-scans.mjs +310 -20
- package/development.mjs +8 -8
- package/freshness-census.mjs +1 -1
- package/gate-stages.mjs +1 -1
- package/install-assembled-app.mjs +1 -1
- package/install-tauri-authenticode.mjs +3 -3
- package/local-publish.mjs +27 -5
- package/mac-package-recipe.mjs +55 -2
- package/managed-roots.mjs +12 -4
- package/package.json +1 -5
- package/pipeline-admission.mjs +1 -1
- package/pipeline-receipts.mjs +302 -302
- package/provenance-verify.mjs +3 -1
- package/qa-build-features.mjs +11 -2
- package/release-qa-audit.mjs +9 -27
- package/release-qa-command.mjs +23 -64
- package/release-qa-fixtures.mjs +9 -5
- package/release-qa-floor.json +0 -1
- package/release-qa-policy.mjs +9 -10
- package/release-qa-receipt.mjs +6 -44
- package/release-source-identity.mjs +1 -12
- package/release-switch-registry.json +4 -1
- package/rightkit-registry-resolver.mjs +35 -3
- package/rightkit-versions.json +2 -2
- package/sign-windows.mjs +1 -1
- package/snapshot-broker.mjs +33 -8
- package/snapshot-metadata.mjs +32 -0
- package/snapshot-request.mjs +6 -3
- package/stage-sidecar.mjs +30 -12
- package/suite-signing-policy.mjs +8 -58
- package/templates/cargo-profiles.toml +6 -25
- package/test-command.mjs +40 -140
- package/updater-shipped-key.mjs +26 -0
- package/web-build.mjs +29 -4
- package/qa-journeys-prebuilt-run.mjs +0 -18
- package/qa-journeys-prebuilt.mjs +0 -148
- package/qa-layout.mjs +0 -229
- package/qa-snapshot-run.mjs +0 -53
- package/qa-snapshot.mjs +0 -164
- package/sign-qa-bundle.mjs +0 -210
- package/suite-signing.mjs +0 -259
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// 1. build the app without bundling (`target.build`: a `tauri build ... --no-bundle`; runDevelopment runs it before this module)
|
|
6
6
|
// 2. patch the bundle marker (UNK -> NSS BEFORE signing: Tauri rewrites it during bundling, and doing that to signed bytes breaks Authenticode)
|
|
7
7
|
// 3. sign the exe and its sidecars, then verify them (signer subject, timestamp)
|
|
8
|
-
// 4. carried `tauri bundle --bundles nsis
|
|
8
|
+
// 4. carried `tauri bundle --bundles nsis` (the installer embeds the exe as signed)
|
|
9
9
|
// 5. check the payload (the exe inside the installer is byte for byte the signed one)
|
|
10
10
|
// 6. sign the installer, then verify it
|
|
11
11
|
// 7. install (the silent installer, as the unsigned kind runs it)
|
|
@@ -74,10 +74,10 @@ export function validateAuthenticodeBuild(build, label = "development.targets.wi
|
|
|
74
74
|
if (!builds) refuse("TAURI_AUTHENTICODE_BUILD", `${label} has no \`tauri build --no-bundle\` step: this install kind signs the raw exe the build leaves in its output directory`);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/** The carried bundle command (step 4): the same Tauri bundle the release makes
|
|
77
|
+
/** The carried bundle command (step 4): the same Tauri bundle the release makes (the release profile, GC-A07: no `--debug`), no Tauri-side signing (the installer is signed by step 6). */
|
|
78
78
|
export function authenticodeBundleCommand(descriptor) {
|
|
79
79
|
const override = { bundle: { createUpdaterArtifacts: false, windows: { signCommand: null } } };
|
|
80
|
-
return { cmd: "pnpm", args: ["exec", "tauri", "bundle", "--bundles", "nsis", "--
|
|
80
|
+
return { cmd: "pnpm", args: ["exec", "tauri", "bundle", "--bundles", "nsis", "--target", descriptor.targetTriple, "--config", JSON.stringify(override)], ...(descriptor.tauriCwd ? { cwd: descriptor.tauriCwd } : {}), name: "tauri bundle" };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const exeBase = (name) => String(name).replace(/\.exe$/i, "");
|
package/local-publish.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { recordPublished, stageSourceVersions, workspaceFeedOptions } from "./ri
|
|
|
31
31
|
import { prepareSwapHelper, SWAP_HELPER_CARGO_ARGS, SWAP_HELPER_PACKAGE } from "./swap-helper-bundle.mjs";
|
|
32
32
|
import { exclusionsFor, npmContentDigest } from "./pack-digest.mjs";
|
|
33
33
|
import { requireDoorRun, gateStateDir } from "./gate-stages.mjs";
|
|
34
|
+
import { crossCompileEnv, crossTargetsFor, oneOutput } from "./cross-targets.mjs";
|
|
34
35
|
|
|
35
36
|
export const LOCAL_OPERATOR = "agent (local-build grant)";
|
|
36
37
|
export const LOCAL_HOST_POLICY_REASON = "Adrian 2026-10-14 local-build grant (.rightkit-local-build.json); Windows covered by the next CI run";
|
|
@@ -341,6 +342,8 @@ const consumerDirs = (rows) => [...new Set(rows.map((row) => path.posix.dirname(
|
|
|
341
342
|
// the file on disk must be the committed file, or the publish is refused.
|
|
342
343
|
export const GATE_CONFIG_FILES = Object.freeze(["ci/gate.config.json", "ci/asset-gated.json"]);
|
|
343
344
|
|
|
345
|
+
export { CROSS_TARGETS, ZIG_TARGETS, CROSS_CC_ENV, crossTargetsFor, crossCompileEnv } from "./cross-targets.mjs";
|
|
346
|
+
|
|
344
347
|
/** The CI gate's commands for one unit (scripts/ci/gate.mjs npmGate/rustGate), mirrored exactly. */
|
|
345
348
|
export function localTestPlan({ kind, unit, dir, config, manifest, batch = false, platform = "darwin" }) {
|
|
346
349
|
if (kind === "npm") {
|
|
@@ -367,6 +370,16 @@ export function localTestPlan({ kind, unit, dir, config, manifest, batch = false
|
|
|
367
370
|
// Batch mode lints the whole batch once (batchClippyInvocations) and records it as a batch receipt.
|
|
368
371
|
// Every tested feature set is linted (-D warnings), not only the first.
|
|
369
372
|
if (!batch) for (const features of featureSets) steps.push({ command: "cargo", args: ["clippy", "-p", unit, "--message-format=json-render-diagnostics", "--no-deps", "--all-targets", ...features, "--", "-D", "warnings"], test: false });
|
|
373
|
+
// Every crate is checked for the other two OS targets too, every tested feature set, -D warnings (CROSS_TARGETS). Batch mode
|
|
374
|
+
// lints them with the batch's own clippy (batchClippyInvocations crossTargetsOf).
|
|
375
|
+
if (!batch) for (const target of crossTargetsFor(entry, platform)) {
|
|
376
|
+
for (const features of featureSets) {
|
|
377
|
+
const env = crossCompileEnv(target);
|
|
378
|
+
// GC-A14: a host without the zig C toolchain fails the check, never skips it.
|
|
379
|
+
if (!env) refuse(`RK_CROSS_CC_UNPROVISIONED: the zig C toolchain for ${target} is not provisioned; run node scripts/ci/provision-zig.mjs <managed data root>/zig (or set RIGHTKIT_CROSS_CC_DIR)`);
|
|
380
|
+
steps.push({ command: "cargo", args: ["clippy", "-p", unit, "--message-format=json-render-diagnostics", "--no-deps", "--all-targets", "--target", target, ...features, "--", "-D", "warnings"], test: false, ...(env ? { env } : {}) });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
370
383
|
for (const features of allFeatureCheckSets(entry)) steps.push({ command: "cargo", args: ["check", "-p", unit, "--message-format=json-render-diagnostics", "--all-targets", ...features], test: false });
|
|
371
384
|
// rustdoc with -D warnings, for the tested and the checked feature sets: the same rows the CI shards (scripts/ci/gate.mjs) and
|
|
372
385
|
// publish.yml's native verify run, so a broken or private intra-doc link fails this unit stage instead of a later verify.
|
|
@@ -385,7 +398,7 @@ export function localTestPlan({ kind, unit, dir, config, manifest, batch = false
|
|
|
385
398
|
* names two crates: one `-p <crate>` run per crate and feature set, in layer order. Crates with a different
|
|
386
399
|
* RIGHTKIT_REQUIRE_ASSETS (asset-gated) keep their own environment.
|
|
387
400
|
*/
|
|
388
|
-
export function batchClippyInvocations(layer, { featuresOf = () => [], featureSetsOf = (unit) => [featuresOf(unit)], assetGatedOf = () => false } = {}) {
|
|
401
|
+
export function batchClippyInvocations(layer, { featuresOf = () => [], featureSetsOf = (unit) => [featuresOf(unit)], assetGatedOf = () => false, crossTargetsOf = () => [] } = {}) {
|
|
389
402
|
const invocations = [];
|
|
390
403
|
for (const unit of layer) {
|
|
391
404
|
// Every tested feature set of a crate is linted, in order; identical sets of one crate collapse to one run.
|
|
@@ -397,6 +410,14 @@ export function batchClippyInvocations(layer, { featuresOf = () => [], featureSe
|
|
|
397
410
|
crates: [unit], assetGated: assetGatedOf(unit),
|
|
398
411
|
args: ["clippy", "-p", unit, "--message-format=json-render-diagnostics", "--no-deps", "--all-targets", ...features, "--", "-D", "warnings"],
|
|
399
412
|
});
|
|
413
|
+
for (const target of crossTargetsOf(unit)) {
|
|
414
|
+
const env = crossCompileEnv(target);
|
|
415
|
+
if (!env) throw new Error(`RK_CROSS_CC_UNPROVISIONED: the zig C toolchain for ${target} is not provisioned; run node scripts/ci/provision-zig.mjs <managed data root>/zig (or set RIGHTKIT_CROSS_CC_DIR)`);
|
|
416
|
+
invocations.push({
|
|
417
|
+
crates: [unit], assetGated: assetGatedOf(unit), ...(env ? { env } : {}),
|
|
418
|
+
args: ["clippy", "-p", unit, "--message-format=json-render-diagnostics", "--no-deps", "--all-targets", "--target", target, ...features, "--", "-D", "warnings"],
|
|
419
|
+
});
|
|
420
|
+
}
|
|
400
421
|
}
|
|
401
422
|
}
|
|
402
423
|
return invocations;
|
|
@@ -670,7 +691,7 @@ async function* publishUnitStages(parsed, deps, batch) {
|
|
|
670
691
|
// A crate's own steps run in its Cargo workspace (its own Cargo.lock and target directory, #1285); a consumer row names its
|
|
671
692
|
// separate workspace by --manifest-path relative to the repository root.
|
|
672
693
|
const stepCwd = kind === "cargo" && step.command === "cargo" && !step.args.includes("--manifest-path") ? unitPath : workspaceRoot;
|
|
673
|
-
const result = await run(step.command, step.command === "cargo" ? withCargoJobs(step.args, parsed.jobs) : step.args, { cwd: stepCwd, env: step.env ? { ...env, ...step.env } : env });
|
|
694
|
+
const result = await run(step.command, step.command === "cargo" ? oneOutput(withCargoJobs(step.args, parsed.jobs), { platform }) : step.args, { cwd: stepCwd, env: step.env ? { ...env, ...step.env } : env });
|
|
674
695
|
const row = { command: commandText, ok: false, isTest: step.test && TEST_COMMAND.test(commandText) };
|
|
675
696
|
if (result.error || result.status !== 0) {
|
|
676
697
|
steps.push(row);
|
|
@@ -890,7 +911,7 @@ async function runLocalPublishBatch(parsed, deps) {
|
|
|
890
911
|
const text = `${command} ${args.join(" ")}`;
|
|
891
912
|
log(`[right-release] batch ${name}: ${text}`);
|
|
892
913
|
for (const cwd of cwds) {
|
|
893
|
-
const result = await run(command, args, { cwd, env: runEnv });
|
|
914
|
+
const result = await run(command, command === "cargo" ? oneOutput(args, { platform }) : args, { cwd, env: runEnv });
|
|
894
915
|
if (result.error || result.status !== 0) refuse(`batch ${name} failed (${result.error?.message ?? `exit ${result.status}`}): ${text}${cwd === workspaceRoot ? "" : ` (in ${path.relative(workspaceRoot, cwd)})`}\n${tail(`${result.stdout ?? ""}\n${result.stderr ?? ""}`)}`);
|
|
895
916
|
}
|
|
896
917
|
checks.push({ name, command: text, ok: true, ...stamp, ...(crateNames ? { crates: crateNames } : {}) });
|
|
@@ -906,8 +927,9 @@ async function runLocalPublishBatch(parsed, deps) {
|
|
|
906
927
|
commands.push(scanText);
|
|
907
928
|
const featureSetsOf = (unit) => gateConfig?.rust?.[unit]?.featureSets ?? [[]];
|
|
908
929
|
for (const layer of layers) {
|
|
909
|
-
for (const invocation of batchClippyInvocations(layer, { featureSetsOf, assetGatedOf: isAssetGated })) {
|
|
910
|
-
|
|
930
|
+
for (const invocation of batchClippyInvocations(layer, { featureSetsOf, assetGatedOf: isAssetGated, crossTargetsOf: (unit) => crossTargetsFor(gateConfig?.rust?.[unit], platform) })) {
|
|
931
|
+
// A cross-target invocation carries the zig C toolchain environment for its target (crossCompileEnv).
|
|
932
|
+
await batchCheck("clippy", "cargo", invocation.args, invocation.crates, { ...testEnv(deps, invocation.assetGated), ...(invocation.env ?? {}) }, invocation.crates.map((unit) => path.join(workspaceRoot, "crates", unit)));
|
|
911
933
|
}
|
|
912
934
|
}
|
|
913
935
|
const receipt = { id: `batch-${head.slice(0, 12)}-${sha256(JSON.stringify(layers)).slice(0, 8)}`, ...stamp, crates, checks };
|
package/mac-package-recipe.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { notarizeMacosArtifact } from "./macos-notarization.mjs";
|
|
|
23
23
|
import { resolveMacosDeveloperIdIdentity } from "./macos-signing-identity.mjs";
|
|
24
24
|
import { runMacCodesignCommands, signMacUpdaterFiles } from "./macos-signing-session.mjs";
|
|
25
25
|
import { discoverNestedCode } from "./swift-mac-app.mjs";
|
|
26
|
+
import { assertUpdaterMatchesShippedKey } from "./updater-shipped-key.mjs";
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Typed error. Codes: MAC_PACKAGE_CONFIG_INVALID, MAC_PACKAGE_HOST_UNSUPPORTED, MAC_PACKAGE_INPUT_MISSING,
|
|
@@ -35,7 +36,7 @@ export class MacPackageRecipeError extends Error {
|
|
|
35
36
|
|
|
36
37
|
export const MAC_PACKAGE_STEPS = Object.freeze(["universal", "sign-nested", "sign-app", "dmg", "sign-dmg", "notarize", "updater"]);
|
|
37
38
|
const UNIVERSAL_ARCHS = ["x86_64", "arm64"];
|
|
38
|
-
const MACPACKAGE_KEYS = ["universal", "dmg", "notarize", "updater", "nested", "entitlements"];
|
|
39
|
+
const MACPACKAGE_KEYS = ["universal", "dmg", "notarize", "updater", "nested", "entitlements", "requiredEntitlements"];
|
|
39
40
|
const DMG_KEYS = ["name", "background", "windowSize", "iconPositions"];
|
|
40
41
|
const ICON_KEYS = ["app", "applications"];
|
|
41
42
|
|
|
@@ -98,7 +99,20 @@ export function validateMacPackageConfig(macPackage) {
|
|
|
98
99
|
if (new Set(nested).size !== nested.length) throw invalid("nested globs must be unique");
|
|
99
100
|
}
|
|
100
101
|
if (macPackage.entitlements !== undefined && (typeof macPackage.entitlements !== "string" || !macPackage.entitlements.trim())) throw invalid("entitlements must be a path string");
|
|
101
|
-
|
|
102
|
+
// rightkit#1443 (hrmac: R2 checked the production APNs entitlement in the signed app): `requiredEntitlements` maps a bundle-relative
|
|
103
|
+
// executable to entitlement keys and the exact value each must carry after signing (a string, or true for a boolean entitlement).
|
|
104
|
+
let requiredEntitlements = null;
|
|
105
|
+
if (macPackage.requiredEntitlements !== undefined) {
|
|
106
|
+
const re = macPackage.requiredEntitlements;
|
|
107
|
+
if (!re || typeof re !== "object" || Array.isArray(re) || !Object.keys(re).length) throw invalid("requiredEntitlements must map bundle-relative executables to {entitlement: value}");
|
|
108
|
+
for (const [file, keys] of Object.entries(re)) {
|
|
109
|
+
validateGlob(file, `requiredEntitlements key ${file}`);
|
|
110
|
+
if (!keys || typeof keys !== "object" || Array.isArray(keys) || !Object.keys(keys).length) throw invalid(`requiredEntitlements.${file} must be a non-empty {entitlement: value} object`);
|
|
111
|
+
for (const [k, v] of Object.entries(keys)) if (!(typeof v === "string" || v === true)) throw invalid(`requiredEntitlements.${file}.${k} must be a string or true`);
|
|
112
|
+
}
|
|
113
|
+
requiredEntitlements = re;
|
|
114
|
+
}
|
|
115
|
+
return { universal, dmg: normalized, notarize, updater, nested, ...(macPackage.entitlements ? { entitlements: macPackage.entitlements } : {}), ...(requiredEntitlements ? { requiredEntitlements } : {}) };
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
/**
|
|
@@ -160,6 +174,35 @@ const defaultInspect = (cmd, args) => {
|
|
|
160
174
|
* listFiles(dir) -> bundle-relative posix paths, buildDmg(options), notarize(options), run(cmd, args, opts),
|
|
161
175
|
* signUpdater(files, options), receiptPath, log(event).
|
|
162
176
|
*/
|
|
177
|
+
/** Every required entitlement is present, with its exact value, in the signed executable (`codesign -d --entitlements :-`, an XML plist). */
|
|
178
|
+
export function macRequiredEntitlementsCheck({ app, required, run = spawnSync, env = process.env }) {
|
|
179
|
+
const esc = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
180
|
+
for (const [file, keys] of Object.entries(required)) {
|
|
181
|
+
const target = path.join(app, ...file.split("/"));
|
|
182
|
+
const r = run("codesign", ["-d", "--entitlements", ":-", target], { env, encoding: "utf8" });
|
|
183
|
+
if (!r || r.error || r.status !== 0) throw new Error(`codesign -d --entitlements on ${file} failed (${r?.error?.message ?? `exit ${r?.status}`})`);
|
|
184
|
+
const plist = String(r.stdout ?? "");
|
|
185
|
+
for (const [k, v] of Object.entries(keys)) {
|
|
186
|
+
const want = v === true ? "<true\\s*/>" : `<string>${esc(String(v))}</string>`;
|
|
187
|
+
if (!new RegExp(`<key>${esc(k)}</key>\\s*${want}`).test(plist)) throw new Error(`${file} is signed without the required entitlement ${k} = ${JSON.stringify(v)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Gatekeeper's verdict on the shipped app and DMG, as a user's Mac reaches it: staple validation and spctl assessment. Throws on any refusal. */
|
|
193
|
+
export function macGatekeeperCheck({ app, dmg, run = spawnSync, env = process.env }) {
|
|
194
|
+
const checks = [
|
|
195
|
+
["xcrun", ["stapler", "validate", app]],
|
|
196
|
+
["xcrun", ["stapler", "validate", dmg]],
|
|
197
|
+
["spctl", ["--assess", "--type", "execute", "-v", app]],
|
|
198
|
+
["spctl", ["--assess", "--type", "open", "--context", "context:primary-signature", "-v", dmg]],
|
|
199
|
+
];
|
|
200
|
+
for (const [cmd, args] of checks) {
|
|
201
|
+
const r = run(cmd, args, { env, encoding: "utf8" });
|
|
202
|
+
if (!r || r.error || r.status !== 0) throw new Error(`${cmd} ${args.join(" ")} refused (${r?.error?.message ?? `exit ${r?.status}`}): ${String(r?.stderr ?? r?.stdout ?? "").trim().split(/\r?\n/).slice(-2).join(" | ")}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
163
206
|
export function runMacPackageRecipe({ appRoot, config = {}, target, deps = {} }) {
|
|
164
207
|
let artifactPaths = null;
|
|
165
208
|
const { recipe, app: declaredApp, dmgFile } = validateMacPackageTarget(target);
|
|
@@ -254,6 +297,10 @@ export function runMacPackageRecipe({ appRoot, config = {}, target, deps = {} })
|
|
|
254
297
|
}
|
|
255
298
|
try { signing.push(...signMacosEntries([{ file: app, hardenedRuntime: true, options: [], ...(entitlements ? { entitlements } : {}) }], { identity, ...signOptions })); }
|
|
256
299
|
catch (error) { throw wrap("MAC_PACKAGE_SIGN_FAILED", "app signing", error); }
|
|
300
|
+
if (recipe.requiredEntitlements) {
|
|
301
|
+
try { (deps.entitlementsCheck ?? macRequiredEntitlementsCheck)({ app, required: recipe.requiredEntitlements, run, env }); }
|
|
302
|
+
catch (error) { throw wrap("MAC_PACKAGE_SIGN_FAILED", "required entitlements", error); }
|
|
303
|
+
}
|
|
257
304
|
record("sign-app");
|
|
258
305
|
|
|
259
306
|
// 4) branded DMG from the signed app.
|
|
@@ -280,6 +327,10 @@ export function runMacPackageRecipe({ appRoot, config = {}, target, deps = {} })
|
|
|
280
327
|
try { receipt = notarize({ archive: dmg, app, receiptPath, env, run }); }
|
|
281
328
|
catch (error) { throw wrap("MAC_PACKAGE_NOTARIZE_FAILED", "notarization", error); }
|
|
282
329
|
if (!receipt || receipt.notarization?.status !== "Accepted") throw new MacPackageRecipeError("MAC_PACKAGE_NOTARIZE_FAILED", `macPackage notarization did not report Accepted for ${dmg}`);
|
|
330
|
+
// rightkit#1443 (hrmac: R2's verify-macos-release-artifacts.mjs did this; the gate had dropped it): what a user's Mac will check, checked
|
|
331
|
+
// here: the staple of the app and the DMG validates, and Gatekeeper accepts the app (execute) and the DMG (open, primary signature).
|
|
332
|
+
try { (deps.gatekeeper ?? macGatekeeperCheck)({ app, dmg, run, env }); }
|
|
333
|
+
catch (error) { throw wrap("MAC_PACKAGE_NOTARIZE_FAILED", "Gatekeeper and staple validation", error); }
|
|
283
334
|
record("notarize");
|
|
284
335
|
}
|
|
285
336
|
|
|
@@ -296,6 +347,8 @@ export function runMacPackageRecipe({ appRoot, config = {}, target, deps = {} })
|
|
|
296
347
|
try { signUpdater([updater], { cwd: appRoot, env }); }
|
|
297
348
|
catch (error) { throw wrap("MAC_PACKAGE_UPDATER_FAILED", "updater signing", error); }
|
|
298
349
|
for (const file of updaterFiles) if (!fs.existsSync(file)) throw missing("updater artifact", file);
|
|
350
|
+
try { (deps.verifyUpdater ?? assertUpdaterMatchesShippedKey)({ appRoot, file: updater }); }
|
|
351
|
+
catch (error) { throw wrap("MAC_PACKAGE_UPDATER_FAILED", "updater signature against the shipped key", error); }
|
|
299
352
|
record("updater");
|
|
300
353
|
}
|
|
301
354
|
} catch (error) {
|
package/managed-roots.mjs
CHANGED
|
@@ -27,17 +27,23 @@ export const envNamed = (env, name) => {
|
|
|
27
27
|
* way (rustright 07829c05, pinned in the shared fixtures as "linux no-env defaults to the account state directory"). The broker's answering roots, not either
|
|
28
28
|
* resolver's guess, say where a running broker keeps its state.
|
|
29
29
|
*/
|
|
30
|
-
export function resolveManagedRoots({ env = process.env, platform = process.platform, home = homedir(), controlRoot, dataRoot, runtimeRoot } = {}) {
|
|
30
|
+
export function resolveManagedRoots({ env = process.env, platform = process.platform, home = homedir(), controlRoot, dataRoot, runtimeRoot, windowsControlDefault = 'localappdata' } = {}) {
|
|
31
31
|
const paths = platform === 'win32' ? win32 : posix;
|
|
32
32
|
const legacyRoot = platform === 'win32' ? 'D:\\.rightkit-managed' : '/Volumes/D/.rightkit-managed';
|
|
33
33
|
const managedPlatform = platform === 'darwin' || platform === 'win32';
|
|
34
34
|
dataRoot ??= envNamed(env, 'DATA_ROOT') || envNamed(env, 'MANAGED_ROOT') || envNamed(env, 'EXTERNAL_ROOT') || (managedPlatform ? legacyRoot : paths.join(home, '.local', 'state', 'rightkit'));
|
|
35
|
+
// rustright#54 (Adrian, 2026-11-02: "all apps are installed in internal drives ... doesn't rr belong there? the dev workspace sits on d"): the
|
|
36
|
+
// Windows control root (config, logs, policy, agent-bin, attestation) is %LOCALAPPDATA%\RightKit\build-control, as the Mac's is under Application
|
|
37
|
+
// Support; build data (ws, sccache, cargo home, snapshots) stays on the data root. Flipped together with rustright-config WindowsControlDefault.
|
|
38
|
+
const localAppData = () => env.LOCALAPPDATA || paths.join(home, 'AppData', 'Local');
|
|
35
39
|
controlRoot ??= envNamed(env, 'CONTROL_ROOT') || (platform === 'darwin'
|
|
36
|
-
? paths.join(home, 'Library', 'Application Support', 'RightKit', 'build-control')
|
|
37
|
-
|
|
40
|
+
? paths.join(home, 'Library', 'Application Support', 'RightKit', 'build-control')
|
|
41
|
+
: platform === 'win32' && windowsControlDefault === 'localappdata' ? paths.join(localAppData(), 'RightKit', 'build-control') : dataRoot);
|
|
42
|
+
runtimeRoot ??= envNamed(env, 'RUNTIME_ROOT') || (platform === 'darwin' ? paths.join(home, 'Library', 'Application Support', 'RustRight') : platform === 'win32' ? (windowsControlDefault === 'localappdata' ? paths.join(localAppData(), 'RustRight') : 'D:\\.rustright') : controlRoot);
|
|
38
43
|
return Object.freeze({ controlRoot, dataRoot, legacyRoot, runtimeCurrent: paths.join(controlRoot, 'runtime'), runtimeRoot, liveRuntime: paths.join(runtimeRoot, 'runtime'),
|
|
39
44
|
configPath: paths.join(controlRoot, 'config.json'), socket: platform === 'win32' ? null : paths.join(controlRoot, 'broker.sock'),
|
|
40
|
-
|
|
45
|
+
// rustright 303c6c6: on Windows agent-bin stays on the data root (agents' PATH and broker job PATH use D:\\.rightkit-managed\\agent-bin).
|
|
46
|
+
logs: paths.join(controlRoot, 'logs'), agentBin: platform === 'win32' ? paths.join(dataRoot, 'agent-bin') : paths.join(controlRoot, 'agent-bin'),
|
|
41
47
|
agentPolicy: paths.join(controlRoot, 'agent-policy'), ownershipInstall: paths.join(controlRoot, 'ownership-install'),
|
|
42
48
|
// The Mac signing agent already lives on the internal disk; moving it would force a re-provision.
|
|
43
49
|
signingAgent: platform === 'darwin' ? paths.join(home, 'Library', 'Application Support', 'Orthic Labs', 'shared', 'signing-agent')
|
|
@@ -144,6 +150,8 @@ export function managedRuntimeClient(env = process.env, platform = process.platf
|
|
|
144
150
|
const roots = managedControlCandidates({ env, platform, home });
|
|
145
151
|
const configured = resolveManagedRoots({ env, platform, home });
|
|
146
152
|
const ordered = [paths.join(configured.liveRuntime, exe)];
|
|
153
|
+
// rustright#54: until a Windows host's cutover moves the runtime to %LOCALAPPDATA%\\RustRight, its live runtime is still the legacy one.
|
|
154
|
+
if (platform === 'win32') ordered.push(paths.join(resolveManagedRoots({ env, platform, home, windowsControlDefault: 'legacy' }).liveRuntime, exe));
|
|
147
155
|
for (const root of roots) ordered.push(paths.join(root, 'runtime', exe));
|
|
148
156
|
for (const root of roots) ordered.push(paths.join(root, 'runtime-current', exe)); // the pre-cutover link: last, never preferred over the live runtime
|
|
149
157
|
if (platform === 'darwin') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.25",
|
|
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",
|
|
@@ -100,12 +100,8 @@
|
|
|
100
100
|
"smol-toml": "1.9.1"
|
|
101
101
|
},
|
|
102
102
|
"scripts": {
|
|
103
|
-
"test": "node test-support/run-test-shards.mjs --class=unit",
|
|
104
|
-
"test:broker": "node test-support/run-test-shards.mjs --class=broker",
|
|
105
|
-
"test:all": "node test-support/run-test-shards.mjs --class=all",
|
|
106
103
|
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
|
107
104
|
"test:signer:windows": "node sign-release-manifest.integration.mjs",
|
|
108
|
-
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
109
105
|
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
110
106
|
}
|
|
111
107
|
}
|
package/pipeline-admission.mjs
CHANGED
|
@@ -40,7 +40,7 @@ export const hasShellMeta = (text) => SHELL_META.test(String(text));
|
|
|
40
40
|
/** right-release subcommands a config may invoke (the CLI's own commands; unknown names are refused). */
|
|
41
41
|
export const OWNER_SUBCOMMANDS = Object.freeze(new Set([
|
|
42
42
|
"release", "build", "package", "publish", "upload", "deps", "publish-update", "hardening", "harden", "hardeningscan", "sign-windows",
|
|
43
|
-
"sign-updater", "sign-dev-artifact", "sign-
|
|
43
|
+
"sign-updater", "sign-dev-artifact", "sign-candidate", "engine-locate", "stage-sidecar", "init", "pe", "icons", "recipe-build",
|
|
44
44
|
"signing-agent", "create-mac-updater", "mirror-root-artifact", "test", "web-build", "license-notices", "toolchain", "github",
|
|
45
45
|
"cargo-target", "qa", "ownership", "ownership-approval", "ownership-report", "ownership-mandatory", "adoption-evidence", "swift-package", "bundle-scan",
|
|
46
46
|
"check-release-dependencies", "dist-scan", "identity-scan", "apple-profile-create", "ios-credentials", "dev", "doctor", "hooks",
|