@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,395 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { createHash } from "node:crypto";
3
- import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { writeFile } from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import test from "node:test";
8
-
9
- import {
10
- cacheFingerprint,
11
- commandOutputPortable,
12
- watchProgress,
13
- resolveReleaseLayout,
14
- releaseEnvironment,
15
- runBuildStateMachine,
16
- runUploadStateMachine,
17
- verifySealedRelease,
18
- } from "./release-state.mjs";
19
-
20
- const expectedPnpmVersion = JSON.parse(readFileSync(new URL("./rightkit-versions.json", import.meta.url), "utf8")).packageManager.replace(/^pnpm@/, "");
21
-
22
- test("progress watcher observes writes in nested Cargo target directories", async () => {
23
- const root = mkdtempSync(path.join(os.tmpdir(), "right-release-watch-"));
24
- const nested = path.join(root, "release", "build", "openssl");
25
- mkdirSync(nested, { recursive: true });
26
- const readyFile = path.join(root, "watch-ready");
27
- const nestedFile = path.join(nested, "object.lib");
28
- let resolveReady;
29
- let resolveProgress;
30
- const ready = new Promise((resolve) => { resolveReady = resolve; });
31
- const progress = new Promise((resolve) => { resolveProgress = resolve; });
32
- const close = watchProgress([root], (_eventType, filename) => {
33
- const eventName = String(filename ?? "");
34
- if (eventName === path.basename(readyFile)) resolveReady();
35
- if (eventName.endsWith(path.basename(nestedFile))) resolveProgress();
36
- });
37
- try {
38
- await new Promise((resolve) => setTimeout(resolve, 50));
39
- await writeFile(readyFile, "ready");
40
- await waitForEvent(ready, "watcher readiness event missing");
41
- await writeFile(nestedFile, "progress");
42
- await waitForEvent(progress, "nested progress event missing");
43
- } finally {
44
- close();
45
- }
46
- });
47
-
48
- test("fallback progress watchers cap open handles when recursive watching is unavailable", () => {
49
- const root = mkdtempSync(path.join(os.tmpdir(), "right-release-watch-cap-"));
50
- for (let index = 0; index < 10; index += 1) mkdirSync(path.join(root, `dir-${index}`));
51
- const opened = [];
52
- const close = watchProgress([root], () => {}, {
53
- maxFallbackWatchers: 3,
54
- watchFactory: (directory, options) => {
55
- if (options?.recursive) throw new Error("recursive watch unsupported");
56
- opened.push(directory);
57
- return { close: () => {} };
58
- },
59
- });
60
- try {
61
- assert.equal(opened.length, 3);
62
- } finally {
63
- close();
64
- }
65
- });
66
-
67
- async function waitForEvent(event, message) {
68
- let timer;
69
- try {
70
- await Promise.race([
71
- event,
72
- new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), 5_000); }),
73
- ]);
74
- } finally {
75
- clearTimeout(timer);
76
- }
77
- }
78
-
79
- test("portable command capture resolves Windows command shims", { skip: process.platform !== "win32" }, () => {
80
- assert.equal(commandOutputPortable("pnpm", ["--version"]), expectedPnpmVersion);
81
- });
82
-
83
- test("nested app configs keep the vault at repo root and build from the app root", () => {
84
- const layout = resolveReleaseLayout({
85
- repoRoot: "D:/suite/heardright",
86
- configPath: "D:/suite/heardright/tauri-app-next/right-release.config.mjs",
87
- });
88
- assert.equal(layout.vaultRoot, path.resolve("D:/suite/heardright/.right-release"));
89
- assert.equal(layout.appRoot, path.resolve("D:/suite/heardright/tauri-app-next"));
90
- assert.equal(layout.targetLink, path.resolve("D:/suite/heardright/tauri-app-next/src-tauri/target"));
91
- });
92
-
93
- test("shared release environment is isolated from the app vault", () => {
94
- const env = releaseEnvironment({
95
- root: "/suite/.right-release",
96
- cacheRoot: "/Users/test/Library/Caches/RightSuite/release",
97
- platform: "mac",
98
- architecture: "aarch64",
99
- app: "fixture",
100
- appRoot: "/suite/fixture",
101
- cacheKey: "abcdef0123456789",
102
- mode: "shared",
103
- });
104
- assert.match(env.CARGO_TARGET_DIR, /RightSuite[\\/]release[\\/]targets[\\/]mac[\\/]fixture[\\/]abcdef0123456789$/);
105
- assert.match(env.CARGO_HOME, /RightSuite[\\/]release[\\/]cargo-home$/);
106
- assert.equal(env.RIGHT_RELEASE_CACHE_OWNER, "rightkit-v2");
107
- assert.equal(env.SCCACHE_CACHE_SIZE, "32G");
108
- // A dropped sccache server connection must fall through to plain rustc
109
- // instead of failing the build; seen on Windows as a bare error 10054.
110
- assert.equal(env.SCCACHE_IGNORE_SERVER_IO_ERROR, "1");
111
- });
112
-
113
- test("shared release environment honors the cache policy override", () => {
114
- const env = releaseEnvironment({
115
- root: "/suite/.right-release",
116
- cacheRoot: "/cache",
117
- platform: "mac",
118
- architecture: "aarch64",
119
- app: "fixture",
120
- appRoot: "/suite/fixture",
121
- cacheKey: "abcdef0123456789",
122
- mode: "shared",
123
- env: { RIGHT_RELEASE_SCCACHE_MAX_BYTES: String(48 * 1024 ** 3) },
124
- });
125
- assert.equal(env.SCCACHE_CACHE_SIZE, "48G");
126
- });
127
-
128
- function sha256(value) {
129
- return createHash("sha256").update(value).digest("hex");
130
- }
131
-
132
- function fixture() {
133
- const root = mkdtempSync(path.join(os.tmpdir(), "right-release-state-"));
134
- const sealedDir = path.join(root, ".right-release", "sealed", "fixture-1.2.3-abcdef12", "windows");
135
- mkdirSync(sealedDir, { recursive: true });
136
- const installer = path.join(sealedDir, "Fixture_x64-setup.exe");
137
- const signature = `${installer}.sig`;
138
- writeFileSync(installer, "signed-installer");
139
- writeFileSync(signature, "updater-signature");
140
- const manifest = {
141
- schema: 1,
142
- releaseId: "fixture-1.2.3-abcdef12",
143
- app: "fixture",
144
- version: "1.2.3",
145
- commit: "abcdef1234567890",
146
- platform: "win",
147
- files: [
148
- { role: "installer", name: path.basename(installer), sha256: sha256("signed-installer"), sizeBytes: 16 },
149
- { role: "updater-signature", name: path.basename(signature), sha256: sha256("updater-signature"), sizeBytes: 17 },
150
- ],
151
- routes: {
152
- patch: [{ role: "installer", bucket: "public", key: "fixture/installers/windows/current/Fixture_x64-setup.exe" }],
153
- update: [{ role: "installer", bucket: "private", key: "fixture/updates/windows/current/Fixture_x64-setup.exe" }],
154
- },
155
- checkpoints: ["preflight_complete", "build_complete", "signed", "hardened", "sealed"],
156
- };
157
- writeFileSync(path.join(sealedDir, "release-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
158
- return { root, sealedDir, installer, signature, manifest };
159
- }
160
-
161
- test("upload has no build operation and restores the previous stable object when registration fails", async () => {
162
- const fx = fixture();
163
- const calls = [];
164
- await assert.rejects(
165
- runUploadStateMachine({
166
- root: fx.root,
167
- releaseId: fx.manifest.releaseId,
168
- platform: "win",
169
- tier: "patch",
170
- ops: {
171
- verifyAuthenticode: async () => calls.push("verify-authenticode"),
172
- harden: async () => calls.push("harden"),
173
- backup: async () => calls.push("backup"),
174
- upload: async () => calls.push("upload"),
175
- register: async () => { calls.push("register"); throw new Error("R2/API unavailable"); },
176
- verifyRemote: async () => calls.push("verify-remote"),
177
- restore: async () => calls.push("restore"),
178
- discardBackup: async () => calls.push("discard-backup"),
179
- },
180
- }),
181
- /R2\/API unavailable/,
182
- );
183
- assert.deepEqual(calls, ["verify-authenticode", "harden", "backup", "upload", "register", "restore"]);
184
- });
185
- test("missing runtime input fails before build preparation and cannot touch an existing sealed installer", async () => {
186
- const fx = fixture();
187
- let prepared = false;
188
- await assert.rejects(
189
- runBuildStateMachine({
190
- root: fx.root,
191
- app: "fixture",
192
- version: "1.2.4",
193
- commit: "1234567890abcdef",
194
- platform: "win",
195
- requiredInputs: [path.join(fx.root, "missing-directml.dll")],
196
- ops: {
197
- preflight: async ({ requiredInputs }) => {
198
- for (const file of requiredInputs) readFileSync(file);
199
- },
200
- prepare: async () => { prepared = true; },
201
- build: async () => {},
202
- sign: async () => {},
203
- harden: async () => {},
204
- seal: async () => {},
205
- },
206
- }),
207
- /ENOENT/,
208
- );
209
- assert.equal(prepared, false);
210
- assert.equal(readFileSync(fx.installer, "utf8"), "signed-installer");
211
- });
212
-
213
- test("a version already sealed from another commit cannot be rebuilt", async () => {
214
- const fx = fixture();
215
- let prepared = false;
216
- await assert.rejects(
217
- runBuildStateMachine({
218
- root: fx.root,
219
- app: "fixture",
220
- version: fx.manifest.version,
221
- commit: "1234567890abcdef",
222
- platform: "win",
223
- requiredInputs: [],
224
- ops: {
225
- preflight: async () => {},
226
- prepare: async () => { prepared = true; },
227
- build: async () => {},
228
- sign: async () => {},
229
- harden: async () => {},
230
- seal: async () => {},
231
- },
232
- }),
233
- /version 1\.2\.3 is already sealed from commit abcdef1234567890/,
234
- );
235
- assert.equal(prepared, false);
236
- });
237
-
238
- test("an interrupted build leaves every previously sealed release untouched", async () => {
239
- const fx = fixture();
240
- await assert.rejects(
241
- runBuildStateMachine({
242
- root: fx.root,
243
- app: "fixture",
244
- version: "1.2.4",
245
- commit: "1234567890abcdef",
246
- platform: "win",
247
- requiredInputs: [],
248
- ops: {
249
- preflight: async () => {},
250
- prepare: async () => {},
251
- build: async () => { throw new Error("SIGINT"); },
252
- sign: async () => {},
253
- harden: async () => {},
254
- seal: async () => {},
255
- },
256
- }),
257
- /SIGINT/,
258
- );
259
- assert.equal(readFileSync(fx.installer, "utf8"), "signed-installer");
260
- });
261
-
262
- test("a valid sealed release resumes without rebuilding", async () => {
263
- const fx = fixture();
264
- let builds = 0;
265
- const result = await runBuildStateMachine({
266
- root: fx.root,
267
- app: "fixture",
268
- version: fx.manifest.version,
269
- commit: fx.manifest.commit,
270
- platform: "win",
271
- requiredInputs: [],
272
- ops: {
273
- preflight: async () => {},
274
- prepare: async () => {},
275
- build: async () => { builds += 1; },
276
- sign: async () => {},
277
- harden: async () => {},
278
- seal: async () => {},
279
- },
280
- });
281
- assert.equal(result.status, "sealed");
282
- assert.equal(result.resumed, true);
283
- assert.equal(builds, 0);
284
- });
285
-
286
- test("sealed build resume rejects changed input or cache identity", async () => {
287
- const fx = fixture();
288
- fx.manifest.inputs = { "src-tauri/Cargo.lock": "old-hash" };
289
- fx.manifest.cacheKey = "old-cache";
290
- writeFileSync(path.join(fx.sealedDir, "release-manifest.json"), `${JSON.stringify(fx.manifest, null, 2)}\n`);
291
-
292
- await assert.rejects(
293
- runBuildStateMachine({
294
- root: fx.root,
295
- app: fx.manifest.app,
296
- version: fx.manifest.version,
297
- commit: fx.manifest.commit,
298
- platform: fx.manifest.platform,
299
- inputHashes: { "src-tauri/Cargo.lock": "new-hash" },
300
- cacheKey: fx.manifest.cacheKey,
301
- ops: {},
302
- }),
303
- /sealed release input identity mismatch/i,
304
- );
305
- await assert.rejects(
306
- runBuildStateMachine({
307
- root: fx.root,
308
- app: fx.manifest.app,
309
- version: fx.manifest.version,
310
- commit: fx.manifest.commit,
311
- platform: fx.manifest.platform,
312
- inputHashes: fx.manifest.inputs,
313
- cacheKey: "new-cache",
314
- ops: {},
315
- }),
316
- /sealed release cache identity mismatch/i,
317
- );
318
- });
319
-
320
- test("manifest or file tampering blocks upload before any mutation", async () => {
321
- const fx = fixture();
322
- writeFileSync(fx.installer, "changed");
323
- assert.throws(() => verifySealedRelease(fx.sealedDir), /hash mismatch/);
324
- let uploaded = false;
325
- await assert.rejects(
326
- runUploadStateMachine({
327
- root: fx.root,
328
- releaseId: fx.manifest.releaseId,
329
- platform: "win",
330
- tier: "patch",
331
- ops: {
332
- upload: async () => { uploaded = true; },
333
- },
334
- }),
335
- /hash mismatch/,
336
- );
337
- assert.equal(uploaded, false);
338
- });
339
-
340
- test("tier or non-current route mismatch fails before R2 backup", async () => {
341
- const fx = fixture();
342
- fx.manifest.routes.patch[0].bucket = "private";
343
- fx.manifest.routes.patch[0].key = "fixture/updates/windows/1.2.3/Fixture.exe";
344
- writeFileSync(path.join(fx.sealedDir, "release-manifest.json"), `${JSON.stringify(fx.manifest, null, 2)}\n`);
345
- let backedUp = false;
346
- await assert.rejects(
347
- runUploadStateMachine({
348
- root: fx.root,
349
- releaseId: fx.manifest.releaseId,
350
- platform: "win",
351
- tier: "patch",
352
- ops: { backup: async () => { backedUp = true; } },
353
- }),
354
- /patch route.*public.*installers.*current/i,
355
- );
356
- assert.equal(backedUp, false);
357
- });
358
-
359
- test("release ids and R2 keys reject traversal and shell metacharacters", async () => {
360
- const fx = fixture();
361
- await assert.rejects(
362
- runUploadStateMachine({ root: fx.root, releaseId: "../../outside", platform: "win", tier: "patch" }),
363
- /invalid release id/i,
364
- );
365
- fx.manifest.routes.patch[0].key = "fixture/installers/windows/current/Fixture.exe&whoami";
366
- writeFileSync(path.join(fx.sealedDir, "release-manifest.json"), `${JSON.stringify(fx.manifest, null, 2)}\n`);
367
- await assert.rejects(
368
- runUploadStateMachine({ root: fx.root, releaseId: fx.manifest.releaseId, platform: "win", tier: "patch" }),
369
- /unsafe R2 key/i,
370
- );
371
- });
372
-
373
- test("release and test Cargo targets are disjoint", () => {
374
- const root = "C:/repo/.right-release";
375
- const build = releaseEnvironment({ root, platform: "win", cacheKey: "deps123", kind: "release" });
376
- const testEnv = releaseEnvironment({ root, platform: "win", cacheKey: "deps123", kind: "test" });
377
- assert.notEqual(build.CARGO_TARGET_DIR, testEnv.CARGO_TARGET_DIR);
378
- assert.match(build.CARGO_TARGET_DIR, /cache[\\/]cargo-target[\\/]win[\\/]deps123/);
379
- assert.match(testEnv.CARGO_TARGET_DIR, /cache[\\/]test-target[\\/]win[\\/]deps123/);
380
- assert.match(build.SCCACHE_DIR, /cache[\\/]sccache/);
381
- assert.equal(build.RUSTC_WRAPPER, "sccache");
382
- });
383
-
384
- test("dependency cache survives source commits and invalidates only dependency/toolchain inputs", () => {
385
- const base = {
386
- cargoLockSha256: "lock-a",
387
- rustc: "rustc 1.90.0 host x86_64-pc-windows-msvc",
388
- target: "x86_64-pc-windows-msvc",
389
- features: ["bundled-sqlcipher-vendored-openssl"],
390
- };
391
- assert.equal(cacheFingerprint({ ...base, commit: "aaa" }), cacheFingerprint({ ...base, commit: "bbb" }));
392
- assert.notEqual(cacheFingerprint(base), cacheFingerprint({ ...base, cargoLockSha256: "lock-b" }));
393
- assert.notEqual(cacheFingerprint(base), cacheFingerprint({ ...base, rustc: "rustc 1.91.0" }));
394
- assert.notEqual(cacheFingerprint(base), cacheFingerprint({ ...base, features: ["bundled-sqlcipher"] }));
395
- });
@@ -1,21 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import test from "node:test";
6
- import { defaultWindowsReleaseTokenFile, loadReleaseToken } from "./release-token.mjs";
7
-
8
- test("loads an explicit release token without exposing admin-session fallback", async () => {
9
- assert.equal(await loadReleaseToken({ env: { RIGHTAPPS_RELEASE_TOKEN: " durable-token ", RIGHTAPPS_ADMIN_TOKEN: "admin-session" } }), "durable-token");
10
- });
11
-
12
- test("loads the release token from an explicit protected-file path", async () => {
13
- const dir = mkdtempSync(path.join(os.tmpdir(), "right-release-token-"));
14
- const file = path.join(dir, "token");
15
- writeFileSync(file, "file-token\n");
16
- assert.equal(await loadReleaseToken({ env: { RIGHTAPPS_RELEASE_TOKEN_FILE: file }, platform: "linux" }), "file-token");
17
- });
18
-
19
- test("uses the portable Windows token location", () => {
20
- assert.equal(defaultWindowsReleaseTokenFile({ APPDATA: "C:\\Users\\fixture\\AppData\\Roaming" }), path.join("C:\\Users\\fixture\\AppData\\Roaming", "RightKit", "rightapps-release.token"));
21
- });