@rightkit/release 0.2.54 → 0.2.55
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/build-release.mjs +16 -3
- package/build-release.test.mjs +11 -0
- package/cargo-guard.test.mjs +5 -4
- package/package.json +1 -1
- package/release-invocation.test.mjs +3 -2
- package/release.mjs +44 -2
- package/release.test.mjs +20 -6
- package/right-suite-contract.test.mjs +25 -21
- package/rightkit-versions.json +7 -4
- package/sign-windows.mjs +34 -5
- package/standalone-clone-evidence.json +32 -0
- package/target-bridge.mjs +17 -1
package/build-release.mjs
CHANGED
|
@@ -121,10 +121,17 @@ try {
|
|
|
121
121
|
if (dirtyInputs.length > 0) fail(`dirty files can change the packaged app; resolve them before release:\n${dirtyInputs.map((file) => `- ${file}`).join("\n")}`);
|
|
122
122
|
const toolVersions = collectToolVersions(config.packageManager ?? "pnpm");
|
|
123
123
|
const inputHashes = hashInputs(requiredInputs);
|
|
124
|
+
const receiptRoot = path.resolve(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(appRoot, ".right-release", "receipts"));
|
|
125
|
+
const signingIdentity = platform === "win" ? {
|
|
126
|
+
contract: target.signingContract ?? "<missing>",
|
|
127
|
+
configSha256: hashFile(configPath),
|
|
128
|
+
receiptInputs: ["raw-exe", "installer"].map((phase) => path.join(receiptRoot, `windows-${phase}.json`)),
|
|
129
|
+
} : null;
|
|
130
|
+
if (signingIdentity) inputHashes[".right-release/signing-identity.json"] = hashFileText(JSON.stringify(signingIdentity));
|
|
124
131
|
const cargoLock = requiredInputs.find((file) => /Cargo\.lock$/i.test(file));
|
|
125
132
|
const cargoToml = requiredInputs.find((file) => /Cargo\.toml$/i.test(file));
|
|
126
133
|
const cacheIdentity = resolveSharedCacheIdentity({ cargoLockPath: cargoLock, cargoTomlPath: cargoToml, rustcVerbose: toolVersions.rustc, targetTriple: target.cargoTarget ?? target.targetTriple ?? target.rustTarget ?? toolVersions.rustHost, profile: target.profile ?? "release" });
|
|
127
|
-
const cacheKey = cacheIdentity.fingerprint;
|
|
134
|
+
const cacheKey = hashFileText(JSON.stringify({ cache: cacheIdentity.fingerprint, signingIdentity })).slice(0, 16);
|
|
128
135
|
const cacheMode = process.env.RIGHT_RELEASE_CACHE_MODE ?? (platform === "mac" || platform === "win" ? "shared" : "legacy");
|
|
129
136
|
if (cacheMode !== "legacy" && cacheMode !== "shared") fail("RIGHT_RELEASE_CACHE_MODE must be legacy or shared");
|
|
130
137
|
const sharedCacheRoot = cacheMode === "shared" ? resolveSharedCacheRoot({ platform, env: process.env }) : undefined;
|
|
@@ -179,6 +186,7 @@ try {
|
|
|
179
186
|
platform,
|
|
180
187
|
cacheKey,
|
|
181
188
|
inputs: inputHashes,
|
|
189
|
+
signingIdentity,
|
|
182
190
|
tools: toolVersions,
|
|
183
191
|
createdAt: new Date().toISOString(),
|
|
184
192
|
};
|
|
@@ -246,7 +254,7 @@ try {
|
|
|
246
254
|
checkpoint(stateRoot, "hardened");
|
|
247
255
|
},
|
|
248
256
|
seal: async ({ sealedDir }) => {
|
|
249
|
-
sealRelease({ configRoot: appRoot, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey });
|
|
257
|
+
sealRelease({ configRoot: appRoot, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity });
|
|
250
258
|
verifySealedRelease(sealedDir);
|
|
251
259
|
if (cacheMode === "shared") markCacheEntrySuccessful({ layout: sharedLayout });
|
|
252
260
|
checkpoint(stateRoot, "sealed");
|
|
@@ -275,6 +283,10 @@ function hashFile(file) {
|
|
|
275
283
|
return createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
276
284
|
}
|
|
277
285
|
|
|
286
|
+
function hashFileText(text) {
|
|
287
|
+
return createHash("sha256").update(text).digest("hex");
|
|
288
|
+
}
|
|
289
|
+
|
|
278
290
|
function sqlCipherFeatures(text) {
|
|
279
291
|
return [...text.matchAll(/features\s*=\s*\[([^\]]+)\]/g)]
|
|
280
292
|
.flatMap((match) => match[1].match(/"([^"]+)"/g) ?? [])
|
|
@@ -282,7 +294,7 @@ function sqlCipherFeatures(text) {
|
|
|
282
294
|
.filter((value) => /sqlcipher|openssl/i.test(value));
|
|
283
295
|
}
|
|
284
296
|
|
|
285
|
-
function sealRelease({ configRoot, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey }) {
|
|
297
|
+
function sealRelease({ configRoot, sealedDir, releaseId, config, target, platform, commit, inputHashes, toolVersions, cacheKey, signingIdentity }) {
|
|
286
298
|
const sources = new Map();
|
|
287
299
|
const addSource = (source, role) => {
|
|
288
300
|
const current = sources.get(source) ?? new Set();
|
|
@@ -327,6 +339,7 @@ function sealRelease({ configRoot, sealedDir, releaseId, config, target, platfor
|
|
|
327
339
|
commit,
|
|
328
340
|
platform,
|
|
329
341
|
cacheKey,
|
|
342
|
+
signing: signingIdentity ? { ...signingIdentity, receipts: signingIdentity.receiptInputs.map((file) => ({ file, sha256: hashFile(file) })) } : null,
|
|
330
343
|
files,
|
|
331
344
|
routes,
|
|
332
345
|
inputs: inputHashes,
|
package/build-release.test.mjs
CHANGED
|
@@ -60,3 +60,14 @@ test("shared Cache V2 target bridge is owned by a finally-cleaned lifecycle", ()
|
|
|
60
60
|
assert.match(source, /targetBridge\.run\(.*runBuildStateMachine/s);
|
|
61
61
|
assert.match(source, /if \(cacheMode === "shared"\) targetBridge\.ensure\(\)/);
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
test("Windows seal and cache identity bind the signing contract, config, and receipts", () => {
|
|
65
|
+
assert.match(source, /signingIdentity = platform === "win"/);
|
|
66
|
+
assert.match(source, /contract: target\.signingContract/);
|
|
67
|
+
assert.match(source, /configSha256: hashFile\(configPath\)/);
|
|
68
|
+
assert.match(source, /windows-\$\{phase\}\.json/);
|
|
69
|
+
assert.match(source, /inputHashes\["\.right-release\/signing-identity\.json"\]/);
|
|
70
|
+
assert.match(source, /cache: cacheIdentity\.fingerprint, signingIdentity/);
|
|
71
|
+
assert.match(source, /receipts: signingIdentity\.receiptInputs\.map/);
|
|
72
|
+
assert.match(readFileSync(new URL("./release.mjs", import.meta.url), "utf8"), /item\.after\?\.sha256 !== currentHashes/);
|
|
73
|
+
});
|
package/cargo-guard.test.mjs
CHANGED
|
@@ -48,8 +48,8 @@ test("Cargo guard resolves real Cargo through rustup or explicit override", () =
|
|
|
48
48
|
const cargo = resolveRealCargo({ env: {}, run: (command, args) => { calls.push([command, args]); return { status: 0, stdout: "/real/cargo\n" }; } });
|
|
49
49
|
assert.equal(cargo, path.resolve("/real/cargo"));
|
|
50
50
|
assert.deepEqual(calls[0][1], ["which", "cargo"]);
|
|
51
|
-
assert.equal(resolveRealRustc({ env: { RIGHTSUITE_REAL_RUSTC: "/opt/toolchain/rustc" } }), "/opt/toolchain/rustc");
|
|
52
|
-
assert.equal(resolveRealRustc({ env: {}, run: () => ({ status: 0, stdout: "/real/rustc\n" }) }), "/real/rustc");
|
|
51
|
+
assert.equal(resolveRealRustc({ env: { RIGHTSUITE_REAL_RUSTC: "/opt/toolchain/rustc" } }), path.resolve("/opt/toolchain/rustc"));
|
|
52
|
+
assert.equal(resolveRealRustc({ env: {}, run: () => ({ status: 0, stdout: "/real/rustc\n" }) }), path.resolve("/real/rustc"));
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
function cacheEnv(overrides = {}) {
|
|
@@ -81,7 +81,7 @@ test("Cargo guard rejects cache escapes and alternate compiler wrappers", async
|
|
|
81
81
|
test("Cargo guard routes heavy and light commands without recursion", async () => {
|
|
82
82
|
const calls = [];
|
|
83
83
|
const options = {
|
|
84
|
-
env: { TEST: "1",
|
|
84
|
+
env: { TEST: "1", ...cacheEnv() },
|
|
85
85
|
policyOptions: { exists: () => false },
|
|
86
86
|
resolveCargo: () => "/real/cargo",
|
|
87
87
|
runHeavy: async (args, config) => { calls.push(["heavy", args, config.env]); return 0; },
|
|
@@ -90,7 +90,8 @@ test("Cargo guard routes heavy and light commands without recursion", async () =
|
|
|
90
90
|
assert.equal(await runCargoGuard(["test"], options), 0);
|
|
91
91
|
assert.equal(await runCargoGuard(["fmt", "--check"], options), 0);
|
|
92
92
|
assert.deepEqual(calls[0].slice(0, 2), ["heavy", ["--", "/real/cargo", "test"]]);
|
|
93
|
-
|
|
93
|
+
const cacheRoot = cacheEnv().RIGHT_RELEASE_CACHE_ROOT ?? path.join(cacheEnv().LOCALAPPDATA, "RightSuite", "Cache", "release");
|
|
94
|
+
assert.equal(calls[0][2].CARGO_TARGET_DIR.startsWith(path.join(path.resolve(cacheRoot), "dev-targets") + path.sep), true);
|
|
94
95
|
assert.equal(calls[0][2].RUSTC_WRAPPER, "sccache");
|
|
95
96
|
assert.deepEqual(calls[1], ["light", "/real/cargo", ["fmt", "--check"], options.env]);
|
|
96
97
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: signed installers, updater artifacts, patch/update routing, hardening, R2 publish, and RightApps registration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import test from "node:test";
|
|
3
4
|
import { resolvePrimaryWorktree } from "./release-invocation.mjs";
|
|
4
5
|
|
|
5
6
|
test("resolves a submodule primary worktree from core.worktree", () => {
|
|
6
7
|
assert.equal(resolvePrimaryWorktree({
|
|
7
|
-
repoRoot: "/suite/membrane",
|
|
8
|
+
repoRoot: path.resolve("/suite/membrane"),
|
|
8
9
|
worktrees: "worktree /suite/.git/modules/membrane\nHEAD abc\n",
|
|
9
10
|
gitDir: "/suite/.git/modules/membrane",
|
|
10
11
|
coreWorktree: "../../../membrane",
|
|
11
|
-
}), "/suite/membrane");
|
|
12
|
+
}), path.resolve("/suite/membrane"));
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test("preserves an ordinary primary worktree", () => {
|
package/release.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { access, readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -25,6 +26,7 @@ const RIGHTKIT_ALLOWED = buildAllowedVersions(
|
|
|
25
26
|
);
|
|
26
27
|
const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo);
|
|
27
28
|
const FORBIDDEN_RIGHTKIT_SPEC = /^(?:git|file|link|workspace):|github|github\.com/i;
|
|
29
|
+
const WINDOWS_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-nsis-v1";
|
|
28
30
|
|
|
29
31
|
const args = process.argv.slice(2);
|
|
30
32
|
const opts = {
|
|
@@ -98,6 +100,12 @@ if (target.signed !== true) {
|
|
|
98
100
|
if (opts.platform === "win" && !target.sign?.files?.length) {
|
|
99
101
|
fail(`${config.app ?? "app"} win must declare sign.files for the signed release pipeline`);
|
|
100
102
|
}
|
|
103
|
+
if (opts.platform === "win") {
|
|
104
|
+
if (target.signingContract !== WINDOWS_SIGNING_CONTRACT) fail(`${config.app ?? "app"} win must declare signingContract: ${WINDOWS_SIGNING_CONTRACT}`);
|
|
105
|
+
if (!target.prePackage?.cmd) fail(`${config.app ?? "app"} win must declare prePackage for the raw EXE build`);
|
|
106
|
+
if (target.sign?.prePackageFiles?.length !== 1) fail(`${config.app ?? "app"} win must declare exactly one sign.prePackageFiles raw EXE`);
|
|
107
|
+
if (target.sign.prePackageFiles.some((file) => target.sign.files.includes(file))) fail(`${config.app ?? "app"} win raw EXE and installer signing files must be distinct`);
|
|
108
|
+
}
|
|
101
109
|
if (opts.upload && target.publishBlocked) fail(`${config.app ?? "app"} ${opts.platform} publish blocked: ${target.publishBlocked}`);
|
|
102
110
|
/**
|
|
103
111
|
* Assemble preflight inputs from the app's own files. Kept here (not in
|
|
@@ -185,6 +193,13 @@ if (!opts.skipChecks) {
|
|
|
185
193
|
for (const script of config.checks ?? []) await runPackageScript(config.packageManager, script, workdir);
|
|
186
194
|
}
|
|
187
195
|
|
|
196
|
+
if (opts.platform === "win") {
|
|
197
|
+
const rawFiles = target.sign.prePackageFiles.map((p) => path.resolve(root, p));
|
|
198
|
+
await runCommand(target.prePackage, root);
|
|
199
|
+
for (const file of rawFiles) await mustExist(file, `missing raw EXE signing artifact: ${file}`);
|
|
200
|
+
await signWindows(rawFiles, "raw-exe", root);
|
|
201
|
+
}
|
|
202
|
+
|
|
188
203
|
await runCommand(command, root);
|
|
189
204
|
|
|
190
205
|
for (const rel of target.artifacts ?? []) {
|
|
@@ -194,13 +209,14 @@ for (const rel of target.artifacts ?? []) {
|
|
|
194
209
|
if (opts.platform === "win" && target.sign?.files?.length) {
|
|
195
210
|
const files = target.sign.files.map((p) => path.resolve(root, p));
|
|
196
211
|
for (const file of files) await mustExist(file, `missing signing artifact: ${file}`);
|
|
197
|
-
await
|
|
212
|
+
await signWindows(files, "installer", root);
|
|
213
|
+
if (target.postSign) await runCommand(target.postSign, root);
|
|
198
214
|
const updaterFiles = [...new Set((target.updater?.artifacts ?? []).map((artifact) => path.resolve(root, artifact.file)))];
|
|
199
215
|
if (!updaterFiles.length) fail(`${config.app} win must declare updater.artifacts for post-code-signing minisign`);
|
|
200
216
|
await run("node", [SIGN_UPDATER, ...(opts.dryRun ? ["--dry-run"] : []), ...updaterFiles.flatMap((file) => ["--file", file])], root);
|
|
201
217
|
}
|
|
202
218
|
|
|
203
|
-
if (target.postSign) {
|
|
219
|
+
if (target.postSign && opts.platform !== "win") {
|
|
204
220
|
await runCommand(target.postSign, root);
|
|
205
221
|
}
|
|
206
222
|
|
|
@@ -377,6 +393,32 @@ function expandEnv(value) {
|
|
|
377
393
|
return String(value).replace(/%([^%]+)%/g, (_, name) => process.env[name] ?? "");
|
|
378
394
|
}
|
|
379
395
|
|
|
396
|
+
async function signWindows(files, phase, root) {
|
|
397
|
+
const args = [SIGN_WINDOWS];
|
|
398
|
+
const receipt = path.join(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(root, ".right-release", "receipts"), `windows-${phase}.json`);
|
|
399
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
400
|
+
else {
|
|
401
|
+
if (existsSync(receipt)) unlinkSync(receipt);
|
|
402
|
+
args.push("--receipt", receipt);
|
|
403
|
+
}
|
|
404
|
+
console.log(`right-release: signing windows ${phase}`);
|
|
405
|
+
await run("node", [...args, ...files], root);
|
|
406
|
+
if (!opts.dryRun) verifyWindowsReceipt(receipt, files, phase);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function verifyWindowsReceipt(receipt, files, phase) {
|
|
410
|
+
if (!existsSync(receipt)) fail(`missing ${phase} Windows signing receipt: ${receipt}`);
|
|
411
|
+
let evidence;
|
|
412
|
+
try { evidence = JSON.parse(readFileSync(receipt, "utf8")); } catch { fail(`invalid ${phase} Windows signing receipt: ${receipt}`); }
|
|
413
|
+
if (evidence.schema !== 1 || !Array.isArray(evidence.files) || evidence.files.length !== files.length) fail(`ambiguous ${phase} Windows signing receipt: ${receipt}`);
|
|
414
|
+
const expected = files.map((file) => path.resolve(file)).sort();
|
|
415
|
+
const actual = evidence.files.map((item) => path.resolve(item.file)).sort();
|
|
416
|
+
const currentHashes = new Map(files.map((file) => [path.resolve(file), createHash("sha256").update(readFileSync(file)).digest("hex")]));
|
|
417
|
+
if (JSON.stringify(expected) !== JSON.stringify(actual) || evidence.files.some((item) => item.authenticode !== "Valid" || item.subject !== "CN=Damned Ventures LLC" || item.timestampPresent !== true || item.after?.sha256 !== currentHashes.get(path.resolve(item.file)))) {
|
|
418
|
+
fail(`invalid ${phase} Windows signing receipt evidence: ${receipt}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
380
422
|
function run(cmd, runArgs, cwd, env = {}, options = {}) {
|
|
381
423
|
const printable = `${cmd} ${runArgs.join(" ")}`.trim();
|
|
382
424
|
const releaseEnv = opts.tier ? { RIGHT_RELEASE_TIER: opts.tier } : {};
|
package/release.test.mjs
CHANGED
|
@@ -16,7 +16,7 @@ function git(cwd, ...args) {
|
|
|
16
16
|
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function fixture({ signed = true, publish = false, packageJson, cargoManifest, cargoConfig, repoCargoConfig, siblingCargoManifest, buildInputs = defaultBuildInputs, platform = "win" } = {}) {
|
|
19
|
+
function fixture({ signed = true, publish = false, signingContract = "windows-raw-exe-authenticode-before-nsis-v1", prePackageFiles = ["raw.exe"], packageJson, cargoManifest, cargoConfig, repoCargoConfig, siblingCargoManifest, buildInputs = defaultBuildInputs, platform = "win" } = {}) {
|
|
20
20
|
const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), "right-release-test-"));
|
|
21
21
|
const dir = repoCargoConfig ? path.join(fixtureRoot, "apps", "fixture") : fixtureRoot;
|
|
22
22
|
mkdirSync(dir, { recursive: true });
|
|
@@ -52,7 +52,7 @@ function fixture({ signed = true, publish = false, packageJson, cargoManifest, c
|
|
|
52
52
|
package: { cmd: "node", args: ["-e", "process.exit(0)"] },
|
|
53
53
|
...(publish ? { publish: { cmd: "node", args: ["publish-update.mjs"] } } : {}),
|
|
54
54
|
artifacts: [],
|
|
55
|
-
...(platform === "win" ? { sign: { files: ["fixture.exe"] } } : {}),
|
|
55
|
+
...(platform === "win" ? { signingContract, prePackage: { cmd: "node", args: ["-e", "process.exit(0)"] }, sign: { prePackageFiles, files: ["fixture.exe"] } } : {}),
|
|
56
56
|
updater: { artifacts: [{ file: "fixture.exe", signature: "fixture.exe.sig", platform: "windows-x86_64", key: "fixture/fixture.exe" }] },
|
|
57
57
|
hardening: [],
|
|
58
58
|
},
|
|
@@ -185,6 +185,14 @@ test("rejects targets that do not explicitly declare signed release output", ()
|
|
|
185
185
|
assert.match(result.stderr, /signed: true/);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
test("rejects Windows targets with missing or ambiguous signing configuration", () => {
|
|
189
|
+
for (const options of [{ signingContract: null }, { prePackageFiles: [] }, { prePackageFiles: ["raw.exe", "other.exe"] }]) {
|
|
190
|
+
const result = run(fixture(options), "--tier=patch");
|
|
191
|
+
assert.notEqual(result.status, 0);
|
|
192
|
+
assert.match(result.stderr, /signingContract|prePackageFiles/i);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
188
196
|
test("rejects Git, path, or link RightKit app dependencies before release work starts", () => {
|
|
189
197
|
const result = run(
|
|
190
198
|
fixture({
|
|
@@ -348,13 +356,19 @@ test("combined build and upload is rejected before the package step", () => {
|
|
|
348
356
|
assert.doesNotMatch(result.stdout, /node -e process\.exit\(0\)/);
|
|
349
357
|
});
|
|
350
358
|
|
|
351
|
-
test("Windows tier-neutral build signs
|
|
359
|
+
test("Windows tier-neutral build builds raw EXE, signs it, bundles, signs installer, then minisigns updater payload", () => {
|
|
352
360
|
const result = run(fixture({ publish: true }));
|
|
353
361
|
assert.equal(result.status, 0, result.stderr);
|
|
354
|
-
const
|
|
362
|
+
const rawBuildAt = result.stdout.indexOf("node -e");
|
|
363
|
+
const rawSignAt = result.stdout.indexOf("signing windows raw-exe");
|
|
364
|
+
const bundleAt = result.stdout.indexOf("node -e", rawSignAt);
|
|
365
|
+
const installerSignAt = result.stdout.indexOf("signing windows installer");
|
|
355
366
|
const updaterAt = result.stdout.indexOf("sign-updater.mjs");
|
|
356
|
-
assert.ok(
|
|
357
|
-
assert.ok(
|
|
367
|
+
assert.ok(rawBuildAt >= 0, result.stdout);
|
|
368
|
+
assert.ok(rawSignAt > rawBuildAt, result.stdout);
|
|
369
|
+
assert.ok(bundleAt > rawSignAt, result.stdout);
|
|
370
|
+
assert.ok(installerSignAt > bundleAt, result.stdout);
|
|
371
|
+
assert.ok(updaterAt > installerSignAt, result.stdout);
|
|
358
372
|
assert.doesNotMatch(result.stdout, /publish-update\.mjs/);
|
|
359
373
|
});
|
|
360
374
|
|
|
@@ -450,6 +450,7 @@ test("every app platform requires effective non-empty build inputs", () => {
|
|
|
450
450
|
|
|
451
451
|
test("RightKit exposes one current version manifest", () => {
|
|
452
452
|
assert.equal(existsSync(versionsPath), true, "rightkit-versions.json must be the single current-version source");
|
|
453
|
+
assert.equal(versions.schema, 2);
|
|
453
454
|
assert.match(versions.packageManager, /^pnpm@\d+\.\d+\.\d+$/);
|
|
454
455
|
assert.equal(versions.npm["@rightkit/release"], "0.2.47");
|
|
455
456
|
assert.equal(versions.npm["@rightkit/legal"], "0.2.0");
|
|
@@ -458,23 +459,30 @@ test("RightKit exposes one current version manifest", () => {
|
|
|
458
459
|
assert.equal(versions.npm["@rightkit/tauri"], "0.1.0");
|
|
459
460
|
assert.equal(versions.npm["@rightkit/updates"], "0.2.3");
|
|
460
461
|
assert.deepEqual(versions.stagedNpm, {
|
|
462
|
+
"@rightkit/ax": "0.2.0",
|
|
463
|
+
"@rightkit/git": "0.2.0",
|
|
461
464
|
"@rightkit/legal": "0.3.0",
|
|
462
|
-
"@rightkit/legal-ui": "0.1.
|
|
465
|
+
"@rightkit/legal-ui": "0.1.1",
|
|
463
466
|
"@rightkit/license": "0.1.6",
|
|
464
|
-
"@rightkit/release": "0.2.
|
|
467
|
+
"@rightkit/release": "0.2.55",
|
|
465
468
|
});
|
|
466
469
|
assert.deepEqual(versions.legacyNpm, {
|
|
467
|
-
"@rightkit/
|
|
470
|
+
"@rightkit/legal-ui": ["0.1.0"],
|
|
471
|
+
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43", "0.2.44", "0.2.45", "0.2.46", "0.2.49", "0.2.50", "0.2.51", "0.2.53", "0.2.54"],
|
|
468
472
|
});
|
|
469
473
|
assert.ok(
|
|
470
474
|
new Set([versions.npm["@rightkit/release"], ...versions.legacyNpm["@rightkit/release"]]).has("0.2.42"),
|
|
471
475
|
"previously published @rightkit/release versions must remain accepted during the 0.2.46 rollout",
|
|
472
476
|
);
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
477
|
+
for (const [name, version] of Object.entries(versions.stagedNpm)) {
|
|
478
|
+
const packageName = name.slice("@rightkit/".length);
|
|
479
|
+
const manifest = JSON.parse(readFileSync(
|
|
480
|
+
path.join(workspace, "tools/rightkit/packages", packageName, "package.json"),
|
|
481
|
+
"utf8",
|
|
482
|
+
));
|
|
483
|
+
assert.equal(manifest.name, name);
|
|
484
|
+
assert.equal(manifest.version, version, `${name} package version must equal stagedNpm`);
|
|
485
|
+
}
|
|
478
486
|
assert.equal(versions.cargo["rightkit-license"], "0.1.2");
|
|
479
487
|
assert.equal(versions.cargo["rightkit-logs"], "0.1.0");
|
|
480
488
|
assert.equal(versions.cargo["rightkit-tauri"], "0.1.0");
|
|
@@ -653,19 +661,15 @@ for (const app of macOnlyApps) {
|
|
|
653
661
|
args: ["publish-update", "--config", "right-release.config.mjs", "--platform", "mac"],
|
|
654
662
|
});
|
|
655
663
|
const updaters = config.targets.mac.updater.artifacts;
|
|
656
|
-
assert.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
);
|
|
661
|
-
const updater
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
for (const candidate of updaters) {
|
|
666
|
-
assert.equal(candidate.file, updater.file, `${app.key} updater rows must share one universal DMG`);
|
|
667
|
-
assert.equal(candidate.signature, updater.signature, `${app.key} updater rows must share one signature`);
|
|
668
|
-
assert.equal(candidate.key, updater.key, `${app.key} updater rows must share one current R2 object`);
|
|
664
|
+
assert.equal(updaters.length, 2);
|
|
665
|
+
assert.deepEqual(updaters.map((updater) => updater.platform).sort(), ["darwin-aarch64", "darwin-x86_64"]);
|
|
666
|
+
assert.equal(new Set(updaters.map((updater) => updater.file)).size, 1, `${app.key} universal updater must share one DMG`);
|
|
667
|
+
assert.equal(new Set(updaters.map((updater) => updater.signature)).size, 1, `${app.key} universal updater must share one signature`);
|
|
668
|
+
assert.equal(new Set(updaters.map((updater) => updater.key)).size, 1, `${app.key} universal updater must share one current R2 object`);
|
|
669
|
+
for (const updater of updaters) {
|
|
670
|
+
assert.match(updater.file, /\.dmg$/);
|
|
671
|
+
assert.equal(updater.signature, `${updater.file}.sig`);
|
|
672
|
+
assert.equal(updater.key, `${app.key}/updates/mac/current/ScreenRight.dmg`);
|
|
669
673
|
}
|
|
670
674
|
assertBuildInputs(config, config.targets.mac, `${app.key} mac`);
|
|
671
675
|
assertMacPackageEntry(config.targets.mac.package, pkg.scripts, app.key);
|
package/rightkit-versions.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema":
|
|
2
|
+
"schema": 2,
|
|
3
3
|
"packageManager": "pnpm@11.18.0",
|
|
4
4
|
"npm": {
|
|
5
5
|
"@rightkit/git": "0.2.0",
|
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
"@rightkit/updates": "0.2.3"
|
|
14
14
|
},
|
|
15
15
|
"stagedNpm": {
|
|
16
|
+
"@rightkit/ax": "0.2.0",
|
|
17
|
+
"@rightkit/git": "0.2.0",
|
|
16
18
|
"@rightkit/legal": "0.3.0",
|
|
17
|
-
"@rightkit/legal-ui": "0.1.
|
|
19
|
+
"@rightkit/legal-ui": "0.1.1",
|
|
18
20
|
"@rightkit/license": "0.1.6",
|
|
19
|
-
"@rightkit/release": "0.2.
|
|
21
|
+
"@rightkit/release": "0.2.55"
|
|
20
22
|
},
|
|
21
23
|
"legacyNpm": {
|
|
22
|
-
"@rightkit/
|
|
24
|
+
"@rightkit/legal-ui": ["0.1.0"],
|
|
25
|
+
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43", "0.2.44", "0.2.45", "0.2.46", "0.2.49", "0.2.50", "0.2.51", "0.2.53", "0.2.54"]
|
|
23
26
|
},
|
|
24
27
|
"cargo": {
|
|
25
28
|
"rightkit-license": "0.1.2",
|
package/sign-windows.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { spawnSync } from "node:child_process";
|
|
@@ -19,9 +20,14 @@ const userEnvCache = new Map();
|
|
|
19
20
|
const args = process.argv.slice(2);
|
|
20
21
|
const dryRun = args.includes("--dry-run");
|
|
21
22
|
const verifyOnly = args.includes("--verify-only");
|
|
22
|
-
const
|
|
23
|
+
const receiptIndex = args.indexOf("--receipt");
|
|
24
|
+
const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
|
|
25
|
+
const files = args
|
|
26
|
+
.filter((arg, index) => arg !== "--dry-run" && arg !== "--verify-only" && arg !== "--receipt" && (receiptIndex < 0 || index !== receiptIndex + 1))
|
|
27
|
+
.map((arg) => path.resolve(arg));
|
|
23
28
|
|
|
24
29
|
if (!files.length) fail("usage: node sign-windows.mjs [--dry-run|--verify-only] <file>...");
|
|
30
|
+
if (receiptIndex >= 0 && !receiptPath) fail("--receipt requires a path");
|
|
25
31
|
if (dryRun) {
|
|
26
32
|
console.log(`dry-run: ${verifyOnly ? "Authenticode verification" : "Azure Artifact Signing"} ${files.join(", ")}`);
|
|
27
33
|
process.exit(0);
|
|
@@ -33,7 +39,7 @@ const childEnv = signingEnv();
|
|
|
33
39
|
const signtool = findFirst([env("AZURE_SIGNTOOL_PATH"), env("SIGNTOOL_PATH"), ...signtoolCandidates()]);
|
|
34
40
|
if (!signtool) fail("signtool.exe not found; set AZURE_SIGNTOOL_PATH or install Windows SDK Build Tools");
|
|
35
41
|
if (verifyOnly) {
|
|
36
|
-
for (const file of files) run(signtool, ["verify", "/pa", "/v", file]);
|
|
42
|
+
for (const file of files) assertVerification(file, run(signtool, ["verify", "/pa", "/v", file]));
|
|
37
43
|
process.exit(0);
|
|
38
44
|
}
|
|
39
45
|
const dlib = findFirst([
|
|
@@ -47,14 +53,22 @@ if (!dlib) fail("Azure.CodeSigning.Dlib.dll not found; set AZURE_CODESIGN_DLIB_P
|
|
|
47
53
|
|
|
48
54
|
const tempDir = mkdtempSync(path.join(os.tmpdir(), "right-sign-"));
|
|
49
55
|
const metadata = metadataPath(tempDir);
|
|
56
|
+
const evidence = [];
|
|
50
57
|
try {
|
|
51
58
|
for (const file of files) {
|
|
59
|
+
const before = fileEvidence(file);
|
|
52
60
|
run(signtool, ["sign", "/v", "/fd", "SHA256", "/tr", "http://timestamp.acs.microsoft.com", "/td", "SHA256", "/dlib", dlib, "/dmdf", metadata, file]);
|
|
53
|
-
run(signtool, ["verify", "/pa", "/v", file]);
|
|
61
|
+
const verification = assertVerification(file, run(signtool, ["verify", "/pa", "/v", file]));
|
|
62
|
+
evidence.push({ file, before, after: fileEvidence(file), ...verification });
|
|
54
63
|
}
|
|
55
64
|
} finally {
|
|
56
65
|
rmSync(tempDir, { recursive: true, force: true });
|
|
57
66
|
}
|
|
67
|
+
if (receiptPath) {
|
|
68
|
+
const absoluteReceipt = path.resolve(receiptPath);
|
|
69
|
+
mkdirSync(path.dirname(absoluteReceipt), { recursive: true });
|
|
70
|
+
writeFileSync(absoluteReceipt, `${JSON.stringify({ schema: 1, files: evidence }, null, 2)}\n`);
|
|
71
|
+
}
|
|
58
72
|
|
|
59
73
|
function metadataPath(dir) {
|
|
60
74
|
const existing = env("AZURE_ARTIFACT_SIGNING_METADATA") || env("AZURE_SIGNING_METADATA");
|
|
@@ -128,8 +142,23 @@ function userEnv(name) {
|
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
function run(cmd, runArgs) {
|
|
131
|
-
const result = spawnSync(cmd, runArgs, { env: childEnv,
|
|
145
|
+
const result = spawnSync(cmd, runArgs, { env: childEnv, encoding: "utf8", windowsHide: true });
|
|
146
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
147
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
132
148
|
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
149
|
+
return `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function assertVerification(file, output) {
|
|
153
|
+
if (!/successfully verified/i.test(output)) fail(`Authenticode verification did not report Valid for ${file}`);
|
|
154
|
+
if (!/^Issued to:\s*Damned Ventures LLC\s*$/im.test(output)) fail(`Authenticode subject must be Damned Ventures LLC for ${file}`);
|
|
155
|
+
if (!/(?:timestamp|time stamped|timestamped)/i.test(output)) fail(`Authenticode timestamp is missing for ${file}`);
|
|
156
|
+
return { authenticode: "Valid", subject: "CN=Damned Ventures LLC", timestampPresent: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function fileEvidence(file) {
|
|
160
|
+
const bytes = readFileSync(file);
|
|
161
|
+
return { sha256: createHash("sha256").update(bytes).digest("hex"), sizeBytes: statSync(file).size };
|
|
133
162
|
}
|
|
134
163
|
|
|
135
164
|
function fail(message) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"generatedAt": "2026-08-10T04:42:36.444Z",
|
|
4
|
+
"workRoot": "C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR",
|
|
5
|
+
"apps": [
|
|
6
|
+
{
|
|
7
|
+
"key": "viewright",
|
|
8
|
+
"remote": "https://github.com/bogusyogi/viewright.git",
|
|
9
|
+
"appDir": ".",
|
|
10
|
+
"revision": "21a4171fa8add2d8114bc5f07498b60d7e8eafe5",
|
|
11
|
+
"packageManager": "pnpm@11.18.0",
|
|
12
|
+
"clone": {
|
|
13
|
+
"command": "git clone --depth 1 --single-branch https://github.com/bogusyogi/viewright.git C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright",
|
|
14
|
+
"status": 0,
|
|
15
|
+
"stdout": "",
|
|
16
|
+
"stderr": "Cloning into 'C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright'...\nUpdating files: 91% (1902/2080)\rUpdating files: 92% (1914/2080)\rUpdating files: 93% (1935/2080)\rUpdating files: 94% (1956/2080)\rUpdating files: 95% (1976/2080)\rUpdating files: 96% (1997/2080)\rUpdating files: 97% (2018/2080)\rUpdating files: 98% (2039/2080)\rUpdating files: 99% (2060/2080)\rUpdating files: 100% (2080/2080)\rUpdating files: 100% (2080/2080), done."
|
|
17
|
+
},
|
|
18
|
+
"install": {
|
|
19
|
+
"command": "C:\\nvm4w\\nodejs\\node.exe C:\\nvm4w\\nodejs\\node_modules\\pnpm\\bin\\pnpm.mjs install --frozen-lockfile",
|
|
20
|
+
"status": 0,
|
|
21
|
+
"stdout": "✓ Lockfile passes supply-chain policies (verified 8h ago)\nLockfile is up to date, resolution step is skipped\nProgress: resolved 1, reused 0, downloaded 0, added 0\nPackages: +619\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nProgress: resolved 619, reused 0, downloaded 0, added 0\nProgress: resolved 619, reused 60, downloaded 0, added 0\nPackages are hard linked from the content-addressable store to the virtual store.\n Content-addressable store is at: C:\\Users\\adrds\\AppData\\Local\\pnpm\\store\\v11\n Virtual store is at: node_modules/.pnpm\nProgress: resolved 619, reused 573, downloaded 7, added 7\nProgress: resolved 619, reused 574, downloaded 9, added 10\nProgress: resolved 619, reused 574, downloaded 22, added 11\nProgress: resolved 619, reused 574, downloaded 23, added 11\nProgress: resolved 619, reused 574, downloaded 26, added 14\nProgress: resolved 619, reused 574, downloaded 30, added 24\nProgress: resolved 619, reused 574, downloaded 31, added 57\nProgress: resolved 619, reused 574, downloaded 34, added 128\nProgress: resolved 619, reused 574, downloaded 38, added 135\nProgress: resolved 619, reused 574, downloaded 38, added 136\nProgress: resolved 619, reused 574, downloaded 39, added 141\nProgress: resolved 619, reused 574, downloaded 39, added 148\nProgress: resolved 619, reused 574, downloaded 39, added 182\nProgress: resolved 619, reused 574, downloaded 40, added 201\nProgress: resolved 619, reused 574, downloaded 41, added 218\nProgress: resolved 619, reused 574, downloaded 41, added 242\nProgress: resolved 619, reused 574, downloaded 42, added 312\nProgress: resolved 619, reused 574, downloaded 42, added 371\nProgress: resolved 619, reused 574, downloaded 42, added 430\nProgress: resolved 619, reused 574, downloaded 42, added 486\nProgress: resolved 619, reused 574, downloaded 43, added 543\nProgress: resolved 619, reused 574, downloaded 43, added 578\nProgress: resolved 619, reused 574, downloaded 44, added 596\nProgress: resolved 619, reused 574, downloaded 44, added 611\nProgress: resolved 619, reused 574, downloaded 44, added 612\nProgress: resolved 619, reused 574, downloaded 44, added 613\nProgress: resolved 619, reused 574, downloaded 44, added 615\nProgress: resolved 619, reused 574, downloaded 44, added 616\nProgress: resolved 619, reused 574, downloaded 44, added 617\nProgress: resolved 619, reused 574, downloaded 44, added 618\nProgress: resolved 619, reused 574, downloaded 45, added 618\nProgress: resolved 619, reused 574, downloaded 45, added 619\nProgress: resolved 619, reused 574, downloaded 45, added 619, done\n\ndependencies:\n+ @codemirror/autocomplete 6.20.3\n+ @codemirror/commands 6.10.4\n+ @codemirror/lang-html 6.4.11\n+ @codemirror/lang-javascript 6.2.5\n+ @codemirror/lang-markdown 6.5.0\n+ @codemirror/language 6.12.4\n+ @codemirror/lint 6.9.7\n+ @codemirror/search 6.7.1\n+ @codemirror/state 6.7.1\n+ @codemirror/view 6.43.6\n+ @eigenpal/docx-editor-agents @eigenpal/docx-editor-agents@file:vendor/docx-editor/packages/agents(react@19.2.7)\n+ @eigenpal/docx-editor-core @eigenpal/docx-editor-core@file:vendor/docx-editor/packages/core(prosemirror-commands@1.7.1)(prosemirror-dropcursor@1.8.2)(prosemirror-history@1.5.0)(prosemirror-keymap@1.2.3)(prosemirror-model@1.25.10)(prosemirror-state@1.4.4)(prosemirror-tables@1.8.5)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.0)\n+ @eigenpal/docx-editor-i18n @eigenpal/docx-editor-i18n@file:vendor/docx-editor/packages/i18n\n+ @eigenpal/docx-editor-react @eigenpal/docx-editor-react@file:vendor/docx-editor/packages/react(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(prosemirror-commands@1.7.1)(prosemirror-dropcursor@1.8.2)(prosemirror-history@1.5.0)(prosemirror-keymap@1.2.3)(prosemirror-model@1.25.10)(prosemirror-state@1.4.4)(prosemirror-tables@1.8.5)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)\n+ @lezer/highlight 1.2.3\n+ @mdx-js/mdx 3.1.1\n+ @phosphor-icons/react 2.1.10\n+ @radix-ui/react-select 2.3.2\n+ @rightkit/legal-ui 0.1.0\n+ @rightkit/license 0.1.6\n+ @rightkit/logs 0.1.3\n+ @rightkit/tauri 0.1.0\n+ @rightkit/updates 0.2.3\n+ @tauri-apps/api 2.11.1\n+ @tauri-apps/plugin-http 2.5.9\n+ @tauri-apps/plugin-process 2.3.1\n+ @tauri-apps/plugin-updater 2.10.1\n+ clsx 2.1.1\n+ docxtemplater 3.69.0\n+ dompurify 3.4.13\n+ fabric 7.4.0\n+ github-slugger 2.0.0\n+ jszip 3.10.1\n+ katex 0.17.0\n+ mermaid 11.16.1\n+ pdfjs-dist 6.2.108\n+ pizzip 3.2.0\n+ prosemirror-commands 1.7.1\n+ prosemirror-dropcursor 1.8.2\n+ prosemirror-history 1.5.0\n+ prosemirror-keymap 1.2.3\n+ prosemirror-model 1.25.10\n+ prosemirror-state 1.4.4\n+ prosemirror-tables 1.8.5\n+ prosemirror-transform 1.12.0\n+ prosemirror-view 1.42.0\n+ react 19.2.7\n+ react-dom 19.2.7\n+ react-image-crop 11.1.2\n+ rehype-stringify 10.0.1\n+ remark-frontmatter 5.0.0\n+ remark-gfm 4.0.1\n+ remark-math 6.0.0\n+ remark-parse 11.0.0\n+ remark-rehype 11.1.2\n+ remark-smartypants 3.0.2\n+ shiki 4.3.1\n+ sonner 2.0.7\n+ sucrase 3.35.1\n+ unified 11.0.5\n+ xml-js 1.6.11\n+ yaml 2.9.0\n\ndevDependencies:\n+ @biomejs/biome 2.5.3\n+ @rightkit/legal 0.3.0\n+ @rightkit/release 0.2.50\n+ @tailwindcss/vite 4.3.2\n+ @tauri-apps/cli 2.11.4\n+ @testing-library/dom 10.4.1\n+ @testing-library/jest-dom 6.9.1\n+ @testing-library/react 16.3.2\n+ @types/mdast 4.0.4\n+ @types/react 19.2.17\n+ @types/react-dom 19.2.3\n+ @types/ws 8.18.1\n+ @vitejs/plugin-react 6.0.3\n+ happy-dom 20.10.6\n+ jscpd 5.0.12\n+ jsdom 29.1.1\n+ knip 6.25.0\n+ tailwindcss 4.3.2\n+ typescript 6.0.3\n+ vite 8.1.4\n+ vitest 4.1.10\n+ ws 8.21.0\n\nDone in 40.8s using pnpm v11.18.0",
|
|
22
|
+
"stderr": ""
|
|
23
|
+
},
|
|
24
|
+
"doctor": {
|
|
25
|
+
"command": "C:\\nvm4w\\nodejs\\node.exe C:\\nvm4w\\nodejs\\node_modules\\pnpm\\bin\\pnpm.mjs release:doctor",
|
|
26
|
+
"status": 0,
|
|
27
|
+
"stdout": "right-release 0.2.50\nconfig: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\right-release.config.mjs\napp: viewright\nplatform: win\ntier: <required for release/publish>\npackageManager: pnpm\nworkdir: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\nhardeningscan: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\node_modules\\.pnpm\\@rightkit+release@0.2.50\\node_modules\\@rightkit\\release\\hardeningscan.mjs\nlegal: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\legal\\legal-manifest.json\nlegalAcceptance: viewright-2026-07-17-v3\nlegalManifestSha256: 035ea7cbd040ef001dbdc1385f114c8bb126178fea9a9f705016a726f96a7830\nsign: src-tauri/target/release/bundle/nsis/ViewRight_0.1.60_x64-setup.exe\npreflight:\n [ok ] target-bridge: src-tauri/target is ready for the shared cache bridge\n [ok ] version: 0.1.60 is free to build\n [ok ] windows-sdk: makeappx.exe from SDK 10.0.26100.0\n [ok ] signtool: signtool.exe from SDK 10.0.26100.0\n [ok ] sccache: sccache 0.17.0\n [ok ] disk: 628.7GB free",
|
|
28
|
+
"stderr": "$ right-release doctor"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
package/target-bridge.mjs
CHANGED
|
@@ -168,10 +168,26 @@ function isReclaimableLink(link, ownedRoot, realpath, platform) {
|
|
|
168
168
|
if (error?.code === "ENOENT") return true;
|
|
169
169
|
throw error;
|
|
170
170
|
}
|
|
171
|
-
const root =
|
|
171
|
+
const root = canonicalPlannedPath(ownedRoot, realpath, canonical);
|
|
172
172
|
return resolved === root || resolved.startsWith(root.endsWith(path.sep) ? root : `${root}${path.sep}`);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function canonicalPlannedPath(value, realpath, canonical) {
|
|
176
|
+
let cursor = path.resolve(value);
|
|
177
|
+
const missing = [];
|
|
178
|
+
while (true) {
|
|
179
|
+
try {
|
|
180
|
+
return canonical(path.join(realpath(cursor), ...missing.reverse()));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error?.code !== "ENOENT") throw error;
|
|
183
|
+
const parent = path.dirname(cursor);
|
|
184
|
+
if (parent === cursor) throw error;
|
|
185
|
+
missing.push(path.basename(cursor));
|
|
186
|
+
cursor = parent;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
175
191
|
function sameRealPath(left, right, realpath, platform) {
|
|
176
192
|
const canonical = (value) => {
|
|
177
193
|
const resolved = path.normalize(realpath(value));
|