@rightkit/release 0.2.69 → 0.2.70

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.
Files changed (45) hide show
  1. package/package.json +4 -2
  2. package/rightkit-versions.json +3 -2
  3. package/addon-command.test.mjs +0 -54
  4. package/addon-contract.test.mjs +0 -48
  5. package/asr-artifact-adoption.test.mjs +0 -122
  6. package/build-invocation-contract.test.mjs +0 -124
  7. package/build-release.test.mjs +0 -242
  8. package/cache-command.test.mjs +0 -118
  9. package/cache-policy.test.mjs +0 -346
  10. package/cargo-guard.test.mjs +0 -195
  11. package/cargo-target.test.mjs +0 -82
  12. package/create-mac-updater.test.mjs +0 -14
  13. package/github-release.test.mjs +0 -103
  14. package/heavy-command.test.mjs +0 -221
  15. package/legal-contract.test.mjs +0 -151
  16. package/mirror-root-artifact.test.mjs +0 -58
  17. package/model-promote.test.mjs +0 -284
  18. package/notary-auth.test.mjs +0 -31
  19. package/nsis-payload.test.mjs +0 -139
  20. package/nsis-upgrade-contract.test.mjs +0 -57
  21. package/pipeline-normalization-contract.test.mjs +0 -140
  22. package/preflight.test.mjs +0 -123
  23. package/progress-control.test.mjs +0 -56
  24. package/prune-r2.test.mjs +0 -12
  25. package/publish-cargo.test.mjs +0 -43
  26. package/publish-swift.test.mjs +0 -44
  27. package/publish-update.test.mjs +0 -207
  28. package/qa-contract.test.mjs +0 -47
  29. package/registry-parity.test.mjs +0 -30
  30. package/release-cli-contract.test.mjs +0 -119
  31. package/release-invocation.test.mjs +0 -22
  32. package/release-state.test.mjs +0 -395
  33. package/release-token.test.mjs +0 -21
  34. package/release.test.mjs +0 -533
  35. package/right-suite-contract.test.mjs +0 -1011
  36. package/rightapps-register.test.mjs +0 -28
  37. package/runtime-artifact-manifest.test.mjs +0 -128
  38. package/sign-updater.test.mjs +0 -12
  39. package/source-gate.test.mjs +0 -25
  40. package/standalone-clone-evidence.json +0 -32
  41. package/standalone-clone-verify.test.mjs +0 -76
  42. package/suite-doctor.test.mjs +0 -19
  43. package/target-bridge.test.mjs +0 -269
  44. package/tauri-bundle-marker.test.mjs +0 -81
  45. package/upload-large.test.mjs +0 -70
@@ -1,151 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createHash } from "node:crypto";
3
- import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import test from "node:test";
7
- import { assertLegalReleaseContract } from "./legal-contract.mjs";
8
-
9
- function digest(text) {
10
- return createHash("sha256").update(text).digest("hex");
11
- }
12
-
13
- function fixture() {
14
- const root = mkdtempSync(path.join(os.tmpdir(), "right-legal-contract-"));
15
- const legalDir = path.join(root, "legal");
16
- const tauriDir = path.join(root, "src-tauri");
17
- mkdirSync(legalDir, { recursive: true });
18
- mkdirSync(tauriDir, { recursive: true });
19
- const definitions = [
20
- ["license", "LICENSE.md", "Ownership notice\n"],
21
- ["eula", "EULA.md", "End user license agreement\n"],
22
- ["acceptable_use", "ACCEPTABLE_USE.md", "Acceptable use\n"],
23
- ["product_schedule", "PRODUCT_SCHEDULE.md", "Product schedule\n"],
24
- ["privacy_notice", "PRIVACY.md", "Privacy notice\n"],
25
- ["third_party_notices", "THIRD_PARTY_NOTICES.md", "Build provenance: fixture 1.0 windows-x86_64; inputs: pnpm-lock=abc Cargo.lock=def; generated-by=fixture\n"],
26
- ];
27
- for (const [, file, content] of definitions) writeFileSync(path.join(legalDir, file), content);
28
- const manifest = {
29
- schema: 2,
30
- suite: "right-suite-desktop",
31
- appKey: "fixture",
32
- productName: "Fixture",
33
- licensor: "Damned Ventures LLC",
34
- effectiveDate: "2026-07-16",
35
- acceptanceVersion: "fixture-2026-07-16-v1",
36
- documents: definitions.map(([role, file, content]) => ({
37
- id: `fixture-${role}`,
38
- role,
39
- title: role,
40
- version: "1.0",
41
- sha256: digest(content),
42
- path: file,
43
- publicUrl: `https://example.test/legal/${role}`,
44
- treatment: ["privacy_notice"].includes(role) ? "acknowledge" : role === "third_party_notices" ? "notice" : "agree",
45
- material: !["privacy_notice", "third_party_notices"].includes(role),
46
- })),
47
- };
48
- writeFileSync(path.join(legalDir, "legal-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
49
- // No bundle.licenseFile: the in-app gate is the single assent surface, so a
50
- // compliant app ships an installer with no license page (Adrian, 2026-07-17).
51
- writeFileSync(
52
- path.join(tauriDir, "tauri.conf.json"),
53
- `${JSON.stringify({ bundle: { publisher: "Damned Ventures LLC" } }, null, 2)}\n`,
54
- );
55
- return {
56
- root,
57
- legal: { manifest: "legal/legal-manifest.json", tauriConfig: "src-tauri/tauri.conf.json" },
58
- manifest,
59
- };
60
- }
61
-
62
- test("accepts a complete, hash-bound legal snapshot", () => {
63
- const { root, legal } = fixture();
64
- const result = assertLegalReleaseContract(root, legal, "fixture", "win");
65
- assert.equal(result.appKey, "fixture");
66
- assert.equal(result.documents.length, 6);
67
- });
68
-
69
- test("rejects a missing legal config", () => {
70
- const { root } = fixture();
71
- assert.throws(() => assertLegalReleaseContract(root, null, "fixture", "win"), /must declare legal\.manifest/i);
72
- });
73
-
74
- test("rejects changed bytes and unsafe manifest paths", () => {
75
- const changed = fixture();
76
- writeFileSync(path.join(changed.root, "legal", "EULA.md"), "changed without a new manifest\n");
77
- assert.throws(() => assertLegalReleaseContract(changed.root, changed.legal, "fixture", "win"), /EULA\.md.*sha-?256/i);
78
-
79
- const traversal = fixture();
80
- traversal.manifest.documents[0].path = "../outside.md";
81
- writeFileSync(path.join(traversal.root, "legal", "legal-manifest.json"), `${JSON.stringify(traversal.manifest)}\n`);
82
- assert.throws(() => assertLegalReleaseContract(traversal.root, traversal.legal, "fixture", "win"), /path.*snapshot|traversal/i);
83
- });
84
-
85
- test("rejects placeholders and unresolved checklist markers", () => {
86
- const { root, legal, manifest } = fixture();
87
- const eula = "Terms [COUNSEL: decide later]\n- [ ] fill this in\n";
88
- writeFileSync(path.join(root, "legal", "EULA.md"), eula);
89
- manifest.documents.find((document) => document.role === "eula").sha256 = digest(eula);
90
- writeFileSync(path.join(root, "legal", "legal-manifest.json"), `${JSON.stringify(manifest)}\n`);
91
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /placeholder|unchecked|release blocker/i);
92
- });
93
-
94
- test("rejects an explicitly unresolved release blocker even without a checkbox", () => {
95
- const { root, legal, manifest } = fixture();
96
- const notice = "Build provenance: fixture; inputs: lock=abc; generated-by=fixture\nRELEASE BLOCKER: exact model notice is unresolved.\n";
97
- writeFileSync(path.join(root, "legal", "THIRD_PARTY_NOTICES.md"), notice);
98
- manifest.documents.find((document) => document.role === "third_party_notices").sha256 = digest(notice);
99
- writeFileSync(path.join(root, "legal", "legal-manifest.json"), `${JSON.stringify(manifest)}\n`);
100
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /release blocker/i);
101
- });
102
-
103
- test("rejects a Windows installer license page: the in-app gate is the only assent surface", () => {
104
- const { root, legal } = fixture();
105
- writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
106
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /licenseFile must be absent/i);
107
- });
108
-
109
- test("rejects a macOS DMG SLA too — one licenseFile drives both bundlers", () => {
110
- // branded-dmg.mjs derives its --eula from bundle.licenseFile, so a Windows-only
111
- // check would let a mac release quietly reintroduce the mount-time SLA.
112
- const { root, legal } = fixture();
113
- writeFileSync(path.join(root, "src-tauri", "tauri.conf.json"), JSON.stringify({ bundle: { licenseFile: "../legal/EULA.md" } }));
114
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "mac"), /licenseFile must be absent/i);
115
- });
116
-
117
- test("accepts an app whose installer carries no license page", () => {
118
- const { root, legal } = fixture();
119
- for (const platform of ["win", "mac"]) {
120
- assert.doesNotThrow(() => assertLegalReleaseContract(root, legal, "fixture", platform));
121
- }
122
- });
123
-
124
- test("requires third-party build provenance", () => {
125
- const { root, legal, manifest } = fixture();
126
- const notice = "Third-party software is included.\n";
127
- writeFileSync(path.join(root, "legal", "THIRD_PARTY_NOTICES.md"), notice);
128
- manifest.documents.find((document) => document.role === "third_party_notices").sha256 = digest(notice);
129
- writeFileSync(path.join(root, "legal", "legal-manifest.json"), `${JSON.stringify(manifest)}\n`);
130
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "win"), /third-party.*provenance/i);
131
- });
132
-
133
- test("manifest hash evidence is the hash of the manifest bytes", () => {
134
- const { root, legal } = fixture();
135
- const result = assertLegalReleaseContract(root, legal, "fixture", "mac");
136
- const bytes = readFileSync(path.join(root, legal.manifest));
137
- assert.equal(result.manifestSha256, createHash("sha256").update(bytes).digest("hex"));
138
- });
139
-
140
- test("hash-binds declared third-party appendices and rejects missing files", () => {
141
- const { root, legal, manifest } = fixture();
142
- const appendix = "full dependency license text\n";
143
- writeFileSync(path.join(root, "legal", "RUST_LICENSES.txt"), appendix);
144
- manifest.appendices = [{ id: "rust-licenses", title: "Rust licenses", path: "RUST_LICENSES.txt", sha256: digest(appendix) }];
145
- writeFileSync(path.join(root, "legal", "legal-manifest.json"), `${JSON.stringify(manifest)}\n`);
146
- assert.equal(assertLegalReleaseContract(root, legal, "fixture", "mac").appendices.length, 1);
147
-
148
- manifest.appendices[0].path = "missing.txt";
149
- writeFileSync(path.join(root, "legal", "legal-manifest.json"), `${JSON.stringify(manifest)}\n`);
150
- assert.throws(() => assertLegalReleaseContract(root, legal, "fixture", "mac"), /appendix.*missing/i);
151
- });
@@ -1,58 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { execFileSync } from "node:child_process";
3
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import test from "node:test";
7
- import { fileURLToPath } from "node:url";
8
-
9
- const helper = fileURLToPath(new URL("./mirror-root-artifact.mjs", import.meta.url));
10
-
11
- test("mirrors a nested package artifact from a linked worktree into both package roots", () => {
12
- const temp = mkdtempSync(path.join(os.tmpdir(), "right-release-mirror-"));
13
- const main = path.join(temp, "suite");
14
- const worktree = path.join(temp, "release-worktree");
15
- try {
16
- mkdirSync(path.join(main, "apps", "desktop"), { recursive: true });
17
- git(temp, "init", "suite");
18
- git(main, "config", "user.email", "test@example.com");
19
- git(main, "config", "user.name", "Right Release Test");
20
- writeFileSync(path.join(main, "tracked"), "fixture\n");
21
- git(main, "add", "tracked");
22
- git(main, "commit", "-m", "fixture");
23
- git(main, "worktree", "add", worktree, "-b", "release-test");
24
-
25
- const packageRoot = path.join(worktree, "apps", "desktop");
26
- const source = path.join(packageRoot, "target", "App_1.0.0.dmg");
27
- mkdirSync(path.dirname(source), { recursive: true });
28
- writeFileSync(source, "signed-dmg-fixture");
29
-
30
- execFileSync(process.execPath, [helper, "--file", source, "--package-root", packageRoot], { encoding: "utf8" });
31
-
32
- assert.equal(readFileSync(path.join(packageRoot, "App_1.0.0.dmg"), "utf8"), "signed-dmg-fixture");
33
- assert.equal(readFileSync(path.join(main, "apps", "desktop", "App_1.0.0.dmg"), "utf8"), "signed-dmg-fixture");
34
- } finally {
35
- rmSync(temp, { recursive: true, force: true });
36
- }
37
- });
38
-
39
- test("copies a main-worktree artifact into its package root", () => {
40
- const temp = mkdtempSync(path.join(os.tmpdir(), "right-release-mirror-main-"));
41
- try {
42
- git(temp, "init", "app");
43
- const root = path.join(temp, "app");
44
- const source = path.join(root, "target", "App_2.0.0.dmg");
45
- mkdirSync(path.dirname(source), { recursive: true });
46
- writeFileSync(source, "main-worktree-dmg");
47
-
48
- execFileSync(process.execPath, [helper, "--file", source, "--package-root", root], { encoding: "utf8" });
49
-
50
- assert.equal(readFileSync(path.join(root, "App_2.0.0.dmg"), "utf8"), "main-worktree-dmg");
51
- } finally {
52
- rmSync(temp, { recursive: true, force: true });
53
- }
54
- });
55
-
56
- function git(cwd, ...args) {
57
- return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
58
- }
@@ -1,284 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createHash, generateKeyPairSync, sign } from "node:crypto";
3
- import { spawnSync } from "node:child_process";
4
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import test from "node:test";
8
- import { fileURLToPath } from "node:url";
9
-
10
- import {
11
- RUNTIME_ARTIFACT_TRUSTED_KEY_ID,
12
- defaultWindowsRuntimeArtifactSigningKeyFile,
13
- loadRuntimeArtifactSigningKey,
14
- promoteRuntimeArtifact,
15
- runtimeArtifactUploadEnv,
16
- } from "./model-promote.mjs";
17
- const { privateKey } = generateKeyPairSync("ed25519");
18
- const artifactDigest = "a".repeat(64);
19
- const evidenceDigest = "b".repeat(64);
20
- const packageRoot = path.dirname(fileURLToPath(import.meta.url));
21
- const candidateCommit = "a".repeat(40);
22
-
23
- // Source identity as build/upload/promote read it: the exact commit, plus the
24
- // porcelain status that must be empty for the promotion to run.
25
- function verificationFixture(commit = candidateCommit, status = "") {
26
- return { candidateCommit: commit, status };
27
- }
28
-
29
- function request(overrides = {}) {
30
- return {
31
- authority: "heardright",
32
- privateKey,
33
- keyId: RUNTIME_ARTIFACT_TRUSTED_KEY_ID,
34
- artifactFile: "C:/fixtures/parakeet.onnx",
35
- evidenceFile: "C:/fixtures/eval.json",
36
- config: {
37
- artifact: {
38
- filename: "parakeet.onnx",
39
- sha256: artifactDigest,
40
- sizeBytes: 42,
41
- },
42
- evidence: { sha256: evidenceDigest },
43
- manifest: {
44
- artifactKind: "asr-model",
45
- entitlement: { appKey: "scraperight", tier: "pro" },
46
- distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
47
- target: { os: "windows", arch: "x86_64" },
48
- versions: {
49
- runtime: "rightkit-asr-0.1.0",
50
- model: "parakeet-tdt-0.6b-v3",
51
- tokenizer: "sentencepiece-2026-07-14",
52
- preprocessing: "rightkit-asr-0.1.0",
53
- license: "cc-by-4.0",
54
- provenance: "heardright-approved-2026-07-14",
55
- },
56
- provenance: {
57
- source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
58
- sourceRevision: "0123456789abcdef",
59
- licenseId: "CC-BY-4.0",
60
- noticeSha256: "c".repeat(64),
61
- },
62
- promotion: {
63
- promotionId: "hr-asr-2026-07-14-001",
64
- promotedAt: "2026-07-14T12:00:00.000Z",
65
- },
66
- },
67
- },
68
- verification: verificationFixture(),
69
- ...overrides,
70
- };
71
- }
72
-
73
- function boundaries(events) {
74
- return {
75
- metadata: async (file) => file.endsWith("eval.json")
76
- ? { sha256: evidenceDigest, sizeBytes: 10 }
77
- : { sha256: artifactDigest, sizeBytes: 42 },
78
- upload: async ({ purpose }) => { events.push(`upload:${purpose}`); },
79
- register: async () => { events.push("register"); },
80
- };
81
- }
82
-
83
- test("uploads immutable object, registers signed envelope, then replaces stable pointer", async () => {
84
- const events = [];
85
- const result = await promoteRuntimeArtifact(request(), boundaries(events));
86
-
87
- assert.deepEqual(events, ["upload:object", "register", "upload:pointer"]);
88
- assert.equal(result.envelope.signature.keyId, RUNTIME_ARTIFACT_TRUSTED_KEY_ID);
89
- assert.equal(result.envelope.manifest.object.sha256, artifactDigest);
90
- assert.equal(result.envelope.manifest.promotion.evidenceSha256, evidenceDigest);
91
- assert.match(result.envelope.manifest.pointerKey, /\/current\/manifest\.json$/);
92
- });
93
-
94
- test("dry-run validates and signs without invoking process or network boundaries", async () => {
95
- const events = [];
96
- const result = await promoteRuntimeArtifact(request({ dryRun: true }), boundaries(events));
97
- assert.deepEqual(events, []);
98
- assert.deepEqual(result.steps, ["upload immutable object", "register signed envelope", "replace stable pointer"]);
99
- assert.equal(result.envelope.signature.algorithm, "Ed25519");
100
- });
101
-
102
- test("model promotion refuses a dirty working tree", async () => {
103
- const events = [];
104
- const verification = verificationFixture(candidateCommit, " M packages/release/model-promote.mjs\0");
105
- await assert.rejects(
106
- promoteRuntimeArtifact(request({ verification, dryRun: true }), boundaries(events)),
107
- /clean working tree/,
108
- );
109
- assert.deepEqual(events, []);
110
- });
111
-
112
- test("model promotion requires source identity", async () => {
113
- const events = [];
114
- await assert.rejects(
115
- promoteRuntimeArtifact(request({ verification: undefined, dryRun: true }), boundaries(events)),
116
- /source identity is required/,
117
- );
118
- assert.deepEqual(events, []);
119
- });
120
-
121
- test("never registers or publishes a pointer after immutable upload failure", async () => {
122
- const events = [];
123
- const deps = boundaries(events);
124
- deps.upload = async ({ purpose }) => {
125
- events.push(`upload:${purpose}`);
126
- throw new Error("R2 failed");
127
- };
128
- await assert.rejects(promoteRuntimeArtifact(request(), deps), /R2 failed/);
129
- assert.deepEqual(events, ["upload:object"]);
130
- });
131
-
132
- test("never publishes a pointer when RightApps registration fails", async () => {
133
- const events = [];
134
- const deps = boundaries(events);
135
- deps.register = async () => {
136
- events.push("register");
137
- throw new Error("registration failed");
138
- };
139
- await assert.rejects(promoteRuntimeArtifact(request(), deps), /registration failed/);
140
- assert.deepEqual(events, ["upload:object", "register"]);
141
- });
142
-
143
- test("fails before mutation on digest, authority, key-id, or lane contradiction", async () => {
144
- const badRequests = [
145
- request({ authority: "scraperight" }),
146
- request({ keyId: "untrusted-key" }),
147
- request({ config: { ...request().config, artifact: { ...request().config.artifact, sha256: "d".repeat(64) } } }),
148
- request({ config: {
149
- ...request().config,
150
- manifest: {
151
- ...request().config.manifest,
152
- entitlement: { appKey: "scraperight", tier: "public" },
153
- distribution: { delivery: "public-r2", bucket: "rightapps-downloads" },
154
- },
155
- } }),
156
- request({ config: {
157
- ...request().config,
158
- manifest: {
159
- ...request().config.manifest,
160
- promotion: {
161
- ...request().config.manifest.promotion,
162
- authorityAppKey: "scraperight",
163
- },
164
- },
165
- } }),
166
- ];
167
- for (const input of badRequests) {
168
- const events = [];
169
- await assert.rejects(promoteRuntimeArtifact(input, boundaries(events)));
170
- assert.deepEqual(events, []);
171
- }
172
- });
173
-
174
- test("right-release routes model promote to the portable promotion lane", () => {
175
- const result = spawnSync(process.execPath, [path.join(packageRoot, "cli/right-release.mjs"), "model", "promote", "--authority", "heardright"], {
176
- cwd: packageRoot,
177
- encoding: "utf8",
178
- windowsHide: true,
179
- });
180
- assert.equal(result.status, 1);
181
- assert.match(result.stderr, /model promote requires --config/);
182
- assert.doesNotMatch(result.stderr, /unknown command/);
183
- });
184
-
185
- test("model promote help documents the portable config and secure key inputs", () => {
186
- const result = spawnSync(process.execPath, [path.join(packageRoot, "cli/right-release.mjs"), "model", "promote", "--help"], {
187
- cwd: packageRoot,
188
- encoding: "utf8",
189
- windowsHide: true,
190
- });
191
- assert.equal(result.status, 0, result.stderr);
192
- assert.match(result.stdout, /artifact\.file/);
193
- assert.match(result.stdout, /evidence\.sha256/);
194
- assert.match(result.stdout, /RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY_FILE/);
195
- assert.match(result.stdout, /RIGHTAPPS_RELEASE_TOKEN/);
196
- });
197
-
198
- test("portable CLI promotes from a clean checkout, refuses a dirty one, and rejects injected trust flags", async () => {
199
- const root = await mkdtemp(path.join(os.tmpdir(), "rightkit-model-promote-test-"));
200
- try {
201
- const artifact = Buffer.from("tiny model fixture");
202
- const evidence = Buffer.from('{"certified":true}');
203
- const testKeys = generateKeyPairSync("ed25519");
204
- const keyPem = testKeys.privateKey.export({ type: "pkcs8", format: "pem" }).toString();
205
- await Promise.all([
206
- writeFile(path.join(root, "model.onnx"), artifact),
207
- writeFile(path.join(root, "evidence.json"), evidence),
208
- writeFile(path.join(root, "key.pem"), keyPem, { mode: 0o600 }),
209
- ]);
210
- spawnSync("git", ["init"], { cwd: root, encoding: "utf8" });
211
- spawnSync("git", ["add", "."], { cwd: root, encoding: "utf8" });
212
- const committed = spawnSync("git", ["-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", "commit", "-m", "fixture"], { cwd: root, encoding: "utf8" });
213
- assert.equal(committed.status, 0, committed.stderr);
214
- const config = {
215
- artifact: {
216
- file: "model.onnx",
217
- filename: "model.onnx",
218
- sha256: createHash("sha256").update(artifact).digest("hex"),
219
- sizeBytes: artifact.length,
220
- },
221
- evidence: {
222
- file: "evidence.json",
223
- sha256: createHash("sha256").update(evidence).digest("hex"),
224
- },
225
- manifest: request().config.manifest,
226
- };
227
- await writeFile(path.join(root, "promotion.json"), JSON.stringify(config));
228
- spawnSync("git", ["add", "."], { cwd: root, encoding: "utf8" });
229
- spawnSync("git", ["-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", "commit", "-m", "config"], { cwd: root, encoding: "utf8" });
230
-
231
- const promoteArgs = [
232
- path.join(packageRoot, "cli/right-release.mjs"), "model", "promote",
233
- "--authority", "heardright",
234
- "--config", path.join(root, "promotion.json"),
235
- "--signing-key-file", path.join(root, "key.pem"),
236
- "--dry-run",
237
- ];
238
- const result = spawnSync(process.execPath, promoteArgs, { cwd: root, encoding: "utf8", windowsHide: true });
239
- assert.equal(result.status, 0, result.stderr);
240
-
241
- await writeFile(path.join(root, "untracked.txt"), "dirty");
242
- const dirty = spawnSync(process.execPath, promoteArgs, { cwd: root, encoding: "utf8", windowsHide: true });
243
- assert.equal(dirty.status, 1);
244
- assert.match(dirty.stderr, /clean working tree/);
245
-
246
- const attacker = spawnSync(process.execPath, [
247
- path.join(packageRoot, "cli/right-release.mjs"), "model", "promote",
248
- "--authority", "heardright", "--config", path.join(root, "promotion.json"),
249
- "--verification-public-key", path.join(root, "verification-public.pem"),
250
- ], { cwd: root, encoding: "utf8", windowsHide: true });
251
- assert.equal(attacker.status, 1);
252
- assert.match(attacker.stderr, /unknown model promote argument: --verification-public-key/);
253
- assert.doesNotMatch(`${result.stdout}${result.stderr}`, /BEGIN PRIVATE KEY/);
254
- assert.doesNotMatch(`${result.stdout}${result.stderr}`, /Uploading|POST https:/);
255
- } finally {
256
- await rm(root, { recursive: true, force: true });
257
- }
258
- });
259
-
260
- test("uses the canonical Windows signing-key path and supports a Keychain-fed PEM environment value", async () => {
261
- assert.equal(
262
- defaultWindowsRuntimeArtifactSigningKeyFile({ APPDATA: "C:/Users/test/AppData/Roaming" }),
263
- path.join("C:/Users/test/AppData/Roaming", "RightKit", "runtime-artifact-signing-key.pem"),
264
- );
265
- assert.equal(
266
- await loadRuntimeArtifactSigningKey({ env: { RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY: "line1\\nline2" }, platform: "darwin" }),
267
- "line1\nline2",
268
- );
269
- });
270
-
271
- test("does not forward signing or RightApps credentials into the Wrangler uploader process", () => {
272
- const env = runtimeArtifactUploadEnv({
273
- CLOUDFLARE_API_TOKEN: "cloudflare-token",
274
- RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY: "private-pem",
275
- RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY_FILE: "key.pem",
276
- RIGHTAPPS_RELEASE_TOKEN: "release-token",
277
- RIGHTAPPS_RELEASE_TOKEN_FILE: "release.token",
278
- });
279
- assert.equal(env.CLOUDFLARE_API_TOKEN, "cloudflare-token");
280
- assert.equal(env.RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY, undefined);
281
- assert.equal(env.RIGHTKIT_RUNTIME_ARTIFACT_PRIVATE_KEY_FILE, undefined);
282
- assert.equal(env.RIGHTAPPS_RELEASE_TOKEN, undefined);
283
- assert.equal(env.RIGHTAPPS_RELEASE_TOKEN_FILE, undefined);
284
- });
@@ -1,31 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import test from "node:test";
5
- import { notarytoolAuthArgs } from "./notary-auth.mjs";
6
-
7
- test("prefers a complete App Store Connect API credential set", () => {
8
- const before = { ...process.env };
9
- try {
10
- process.env.APPLE_API_KEY_PATH = "~/AuthKey.p8";
11
- process.env.APPLE_API_KEY = "KEY123";
12
- process.env.APPLE_API_ISSUER = "ISSUER123";
13
- assert.deepEqual(notarytoolAuthArgs(), ["--key", path.join(os.homedir(), "AuthKey.p8"), "--key-id", "KEY123", "--issuer", "ISSUER123"]);
14
- } finally {
15
- process.env = before;
16
- }
17
- });
18
-
19
- test("falls back to the device keychain profile and rejects partial API auth", () => {
20
- const before = { ...process.env };
21
- try {
22
- delete process.env.APPLE_API_KEY_PATH;
23
- delete process.env.APPLE_API_KEY;
24
- delete process.env.APPLE_API_ISSUER;
25
- assert.deepEqual(notarytoolAuthArgs(), ["--keychain-profile", "apple-dev-notary"]);
26
- process.env.APPLE_API_KEY = "KEY123";
27
- assert.throws(() => notarytoolAuthArgs(), /requires APPLE_API_KEY_PATH.*APPLE_API_ISSUER/);
28
- } finally {
29
- process.env = before;
30
- }
31
- });
@@ -1,139 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createHash } from "node:crypto";
3
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import path from "node:path";
6
- import test, { after } from "node:test";
7
- import { listNsisPayload, parseSevenZipListing, resolveSevenZip, verifyNsisEmbeddedBinary } from "./nsis-payload.mjs";
8
-
9
- const workspace = mkdtempSync(path.join(tmpdir(), "rightkit-nsis-"));
10
- after(() => rmSync(workspace, { recursive: true, force: true }));
11
-
12
- // 7-Zip's technical listing: an archive record, a rule, then one record per member.
13
- // The blank `Attributes` and absent timestamps are what the column-aligned listing
14
- // gets wrong, so they are reproduced here deliberately.
15
- const LISTING = [
16
- "Path = D:\\out\\App_1.2.3_x64-setup.exe",
17
- "Type = Nsis",
18
- "",
19
- "----------",
20
- "Path = $PLUGINSDIR\\System.dll",
21
- "Size = 12288",
22
- "Attributes = ",
23
- "",
24
- "Path = app with spaces.exe",
25
- "Size = 13750736",
26
- "Attributes = ",
27
- "",
28
- "Path = legal",
29
- "Size = 0",
30
- "Folder = +",
31
- "Attributes = D",
32
- "",
33
- "Path = legal\\EULA.md",
34
- "Size = 16723",
35
- "Attributes = A",
36
- "",
37
- ].join("\n");
38
-
39
- test("parses 7-Zip technical listings, including blank dates and spaced names", () => {
40
- const entries = parseSevenZipListing(LISTING);
41
- assert.deepEqual(entries.map((entry) => entry.name), [
42
- "$PLUGINSDIR/System.dll",
43
- "app with spaces.exe",
44
- "legal/EULA.md",
45
- ]);
46
- assert.equal(entries[1].sizeBytes, 13_750_736);
47
- });
48
-
49
- test("ignores the archive's own record above the rule", () => {
50
- const entries = parseSevenZipListing(LISTING);
51
- assert.equal(entries.some((entry) => entry.name.endsWith("setup.exe")), false);
52
- });
53
-
54
- test("listNsisPayload surfaces the 7-Zip failure instead of reporting an empty payload", () => {
55
- assert.throws(
56
- () => listNsisPayload("installer.exe", {
57
- sevenZip: "7z",
58
- run: () => ({ status: 2, stdout: "", stderr: "Cannot open the file as archive" }),
59
- }),
60
- /could not read the NSIS payload.*Cannot open the file as archive/s,
61
- );
62
- });
63
-
64
- test("verifyNsisEmbeddedBinary accepts a payload whose bytes match the signed EXE", () => {
65
- const installer = path.join(workspace, "setup.exe");
66
- writeFileSync(installer, "installer-container");
67
- const payload = Buffer.from("signed-executable-bytes");
68
- const sha256 = createHash("sha256").update(payload).digest("hex");
69
-
70
- const receipt = verifyNsisEmbeddedBinary({
71
- installer,
72
- entryName: "app.exe",
73
- expectedSha256: sha256,
74
- sevenZip: "7z",
75
- run: (_command, args) => {
76
- const destination = args.find((arg) => arg.startsWith("-o")).slice(2);
77
- writeFileSync(path.join(destination, "app.exe"), payload);
78
- return { status: 0, stdout: "Everything is Ok", stderr: "" };
79
- },
80
- });
81
-
82
- assert.equal(receipt.sha256, sha256);
83
- assert.equal(receipt.entry, "app.exe");
84
- assert.equal(receipt.sizeBytes, payload.length);
85
- assert.equal(receipt.installerSha256, createHash("sha256").update(readFileSync(installer)).digest("hex"));
86
- });
87
-
88
- test("verifyNsisEmbeddedBinary rejects a payload that drifted from the signed EXE", () => {
89
- const installer = path.join(workspace, "setup-drifted.exe");
90
- writeFileSync(installer, "installer-container");
91
- assert.throws(
92
- () => verifyNsisEmbeddedBinary({
93
- installer,
94
- entryName: "app.exe",
95
- expectedSha256: createHash("sha256").update("signed-executable-bytes").digest("hex"),
96
- sevenZip: "7z",
97
- run: (_command, args) => {
98
- const destination = args.find((arg) => arg.startsWith("-o")).slice(2);
99
- writeFileSync(path.join(destination, "app.exe"), "rewritten-after-signing");
100
- return { status: 0, stdout: "Everything is Ok", stderr: "" };
101
- },
102
- }),
103
- /does not embed the signed app\.exe.*Authenticode signature is invalid/s,
104
- );
105
- });
106
-
107
- test("verifyNsisEmbeddedBinary fails when the entry is absent rather than passing vacuously", () => {
108
- const installer = path.join(workspace, "setup-missing.exe");
109
- writeFileSync(installer, "installer-container");
110
- assert.throws(
111
- () => verifyNsisEmbeddedBinary({
112
- installer,
113
- entryName: "app.exe",
114
- expectedSha256: "a".repeat(64),
115
- sevenZip: "7z",
116
- run: () => ({ status: 2, stdout: "", stderr: "No files to process" }),
117
- }),
118
- /could not extract app\.exe/,
119
- );
120
- });
121
-
122
- test("verifyNsisEmbeddedBinary refuses a caller that has no digest to compare against", () => {
123
- assert.throws(
124
- () => verifyNsisEmbeddedBinary({ installer: "setup.exe", entryName: "app.exe", expectedSha256: undefined, sevenZip: "7z", run: () => ({ status: 0 }) }),
125
- /expected a sha256 digest/,
126
- );
127
- });
128
-
129
- test("resolveSevenZip names the fix when 7-Zip is absent", () => {
130
- assert.throws(
131
- () => resolveSevenZip({ env: {}, exists: () => false, which: () => null }),
132
- /install 7-Zip.*RIGHT_RELEASE_SEVENZIP/s,
133
- );
134
- });
135
-
136
- test("resolveSevenZip honours an explicit override and rejects a bad one", () => {
137
- assert.equal(resolveSevenZip({ env: { RIGHT_RELEASE_SEVENZIP: "C:\\tools\\7z.exe" }, exists: () => true }), "C:\\tools\\7z.exe");
138
- assert.throws(() => resolveSevenZip({ env: { RIGHT_RELEASE_SEVENZIP: "C:\\gone\\7z.exe" }, exists: () => false }), /does not exist/);
139
- });