@rightkit/release 0.2.68 → 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 (46) hide show
  1. package/cli/right-release.mjs +0 -0
  2. package/package.json +13 -9
  3. package/release.mjs +47 -14
  4. package/rightkit-versions.json +4 -2
  5. package/addon-command.test.mjs +0 -54
  6. package/addon-contract.test.mjs +0 -48
  7. package/asr-artifact-adoption.test.mjs +0 -122
  8. package/build-invocation-contract.test.mjs +0 -124
  9. package/build-release.test.mjs +0 -242
  10. package/cache-command.test.mjs +0 -118
  11. package/cache-policy.test.mjs +0 -346
  12. package/cargo-guard.test.mjs +0 -195
  13. package/cargo-target.test.mjs +0 -82
  14. package/create-mac-updater.test.mjs +0 -14
  15. package/github-release.test.mjs +0 -103
  16. package/heavy-command.test.mjs +0 -221
  17. package/legal-contract.test.mjs +0 -151
  18. package/mirror-root-artifact.test.mjs +0 -58
  19. package/model-promote.test.mjs +0 -284
  20. package/notary-auth.test.mjs +0 -31
  21. package/nsis-payload.test.mjs +0 -139
  22. package/nsis-upgrade-contract.test.mjs +0 -57
  23. package/preflight.test.mjs +0 -123
  24. package/progress-control.test.mjs +0 -56
  25. package/prune-r2.test.mjs +0 -12
  26. package/publish-cargo.test.mjs +0 -43
  27. package/publish-swift.test.mjs +0 -44
  28. package/publish-update.test.mjs +0 -207
  29. package/qa-contract.test.mjs +0 -47
  30. package/registry-parity.test.mjs +0 -30
  31. package/release-cli-contract.test.mjs +0 -119
  32. package/release-invocation.test.mjs +0 -22
  33. package/release-state.test.mjs +0 -395
  34. package/release-token.test.mjs +0 -21
  35. package/release.test.mjs +0 -477
  36. package/right-suite-contract.test.mjs +0 -1007
  37. package/rightapps-register.test.mjs +0 -28
  38. package/runtime-artifact-manifest.test.mjs +0 -128
  39. package/sign-updater.test.mjs +0 -12
  40. package/source-gate.test.mjs +0 -25
  41. package/standalone-clone-evidence.json +0 -32
  42. package/standalone-clone-verify.test.mjs +0 -76
  43. package/suite-doctor.test.mjs +0 -19
  44. package/target-bridge.test.mjs +0 -269
  45. package/tauri-bundle-marker.test.mjs +0 -81
  46. package/upload-large.test.mjs +0 -70
@@ -1,346 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, utimesSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import test from "node:test";
6
- import { spawn } from "node:child_process";
7
- import { once } from "node:events";
8
-
9
- import {
10
- CACHE_SCHEMA,
11
- DEFAULT_BACKUP_RETENTION_DAYS,
12
- DEFAULT_DESIRED_FREE_BYTES,
13
- DEFAULT_HARD_FREE_BYTES,
14
- DEFAULT_SCCACHE_MAX_BYTES,
15
- DEFAULT_TARGET_MAX_BYTES,
16
- acquireCacheLease,
17
- acquireSuiteBuildSlot,
18
- applyCachePrune,
19
- assertWriteVolumeFloors,
20
- ensureCacheEntry,
21
- inspectCache,
22
- inspectWriteVolumes,
23
- markCacheEntrySuccessful,
24
- migrateLegacyCache,
25
- planCachePrune,
26
- readCachePolicy,
27
- resolveCacheLayout,
28
- resolveSharedCacheIdentity,
29
- resolveSharedCacheRoot,
30
- resolveStableRoot,
31
- } from "./cache-policy.mjs";
32
-
33
- function root() { return path.join(os.tmpdir(), `rightkit-cache-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`); }
34
- function layout(dir, fingerprint = "a1b2c3d4e5f60708") {
35
- return resolveCacheLayout({ cacheRoot: dir, platform: "mac", architecture: "aarch64", app: "fixture", fingerprint, kind: "release" });
36
- }
37
-
38
- function migrationFixture() {
39
- const base = root();
40
- const fingerprint = "abcdef0123456789";
41
- const legacyRoot = path.join(base, "repo", "app", "src-tauri", "target");
42
- const legacyTargetRoot = path.join(base, "repo", ".right-release", "cache", "cargo-target", "mac", fingerprint);
43
- const legacyCargoHome = path.join(base, "repo", ".right-release", "cache", "cargo-home");
44
- mkdirSync(legacyTargetRoot, { recursive: true });
45
- writeFileSync(path.join(legacyTargetRoot, "artifact"), "legacy-target");
46
- mkdirSync(legacyCargoHome, { recursive: true });
47
- writeFileSync(path.join(legacyCargoHome, "registry"), "legacy-cargo-home");
48
- mkdirSync(path.dirname(legacyRoot), { recursive: true });
49
- symlinkSync(legacyTargetRoot, legacyRoot);
50
- const sharedCacheRoot = path.join(base, "shared-cache");
51
- return {
52
- fingerprint,
53
- legacyRoot,
54
- legacyTargetRoot,
55
- legacyCargoHome,
56
- layout: layout(sharedCacheRoot, fingerprint),
57
- app: "fixture",
58
- legacyReleaseLockPath: path.join(base, "repo", ".right-release", "locks", "mac.lock.json"),
59
- };
60
- }
61
-
62
- test("cache roots are platform-native and overrides must be absolute", () => {
63
- assert.throws(() => resolveSharedCacheRoot({ platform: "mac", home: "/Users/test", env: {} }), /RIGHT_RELEASE_CACHE_ROOT is required/);
64
- assert.equal(resolveSharedCacheRoot({ platform: "win", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }), "C:\\Users\\test\\AppData\\Local\\RightSuite\\Cache\\release");
65
- assert.equal(resolveSharedCacheRoot({ platform: "linux", home: "/home/test", env: {} }), "/home/test/.cache/rightsuite/release");
66
- assert.equal(resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "/tmp/cache" } }), "/tmp/cache");
67
- assert.throws(() => resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "relative" } }), /absolute/i);
68
- });
69
-
70
- test("shared cache root ignores XDG_CACHE_HOME regardless of value", () => {
71
- assert.equal(
72
- resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }),
73
- resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u" }),
74
- );
75
- assert.equal(resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }), "/home/u/.cache/rightsuite/release");
76
- // resolveSharedCacheRoot no longer accepts an xdgCacheHome option; passing one has no effect.
77
- assert.equal(
78
- resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u", xdgCacheHome: "/should/be/ignored" }),
79
- "/home/u/.cache/rightsuite/release",
80
- );
81
- });
82
-
83
- test("resolveStableRoot: explicit override wins, win32 requires the env var, other platforms ignore arbitrary env", () => {
84
- const opts = {
85
- darwinSegments: ["Library", "Caches", "Example"],
86
- win32Segments: ["Example"],
87
- otherSegments: ["example"],
88
- };
89
- assert.equal(
90
- resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "/explicit/path", overrideName: "EXAMPLE_ROOT", env: { EXAMPLE_ROOT: "ignored-by-caller-arg" } }),
91
- "/explicit/path",
92
- );
93
- assert.throws(
94
- () => resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "relative/path", overrideName: "EXAMPLE_ROOT" }),
95
- /EXAMPLE_ROOT must be an absolute path/,
96
- );
97
- assert.throws(
98
- () => resolveStableRoot({ ...opts, platform: "win32", env: {} }),
99
- /LOCALAPPDATA must be an absolute path for RightSuite/,
100
- );
101
- assert.equal(
102
- resolveStableRoot({ ...opts, platform: "win32", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }),
103
- "C:\\Users\\test\\AppData\\Local\\Example",
104
- );
105
- assert.equal(
106
- resolveStableRoot({ ...opts, platform: "linux", env: { XDG_CACHE_HOME: "/ignored", npm_config_cache: "/ignored" }, home: "/home/u" }),
107
- "/home/u/.cache/example",
108
- );
109
- });
110
-
111
- test("layout isolates app, platform, kind, and fingerprint", () => {
112
- const base = root();
113
- const release = layout(base);
114
- const testLayout = resolveCacheLayout({ cacheRoot: base, platform: "mac", architecture: "aarch64", app: "other", fingerprint: "a1b2c3d4e5f60708", kind: "test" });
115
- assert.match(release.targetDir, /targets[\\/]mac[\\/]fixture[\\/]a1b2c3d4e5f60708$/);
116
- assert.match(testLayout.targetDir, /test-targets[\\/]mac[\\/]other[\\/]a1b2c3d4e5f60708$/);
117
- assert.notEqual(release.targetDir, testLayout.targetDir);
118
- });
119
-
120
- test("policy defaults are the approved target, compiler, desired and hard limits", () => {
121
- const policy = readCachePolicy({});
122
- assert.equal(CACHE_SCHEMA, 1);
123
- assert.equal(policy.targetMaxBytes, DEFAULT_TARGET_MAX_BYTES);
124
- assert.equal(policy.sccacheMaxBytes, DEFAULT_SCCACHE_MAX_BYTES);
125
- assert.equal(policy.desiredFreeBytes, DEFAULT_DESIRED_FREE_BYTES);
126
- assert.equal(policy.hardFreeBytes, DEFAULT_HARD_FREE_BYTES);
127
- assert.equal(policy.backupRetentionDays, DEFAULT_BACKUP_RETENTION_DAYS);
128
- });
129
-
130
- test("pruning expires only old direct backup directories", () => {
131
- const base = root();
132
- const old = path.join(base, "manual-backups", "old-target");
133
- const recent = path.join(base, "recovery", "recent-target");
134
- const outside = root();
135
- mkdirSync(old, { recursive: true }); writeFileSync(path.join(old, "artifact"), "old");
136
- mkdirSync(recent, { recursive: true }); writeFileSync(path.join(recent, "artifact"), "recent");
137
- mkdirSync(outside, { recursive: true });
138
- mkdirSync(path.join(base, "legacy-backups"), { recursive: true });
139
- symlinkSync(outside, path.join(base, "legacy-backups", "escape"));
140
- utimesSync(old, new Date("2026-07-01T00:00:00Z"), new Date("2026-07-01T00:00:00Z"));
141
- utimesSync(recent, new Date("2026-08-07T00:00:00Z"), new Date("2026-08-07T00:00:00Z"));
142
- const snapshot = inspectCache({ cacheRoot: base, now: new Date("2026-08-08T00:00:00Z"), statfs: () => ({ bavail: 100, bsize: 1 }) });
143
- assert.deepEqual(snapshot.backups.map((entry) => entry.id).sort(), ["backup:manual-backups:old-target", "backup:recovery:recent-target"]);
144
- const plan = planCachePrune({ snapshot, policy: { ...readCachePolicy({}), targetMaxBytes: 1, desiredFreeBytes: 0 } });
145
- assert.deepEqual(plan.candidateIds, ["backup:manual-backups:old-target"]);
146
- const result = applyCachePrune({ plan, cacheRoot: base, dryRun: false });
147
- assert.deepEqual(result.removedIds, ["backup:manual-backups:old-target"]);
148
- assert.equal(existsSync(old), false);
149
- assert.equal(existsSync(recent), true);
150
- assert.equal(existsSync(outside), true);
151
- });
152
-
153
- test("entry metadata is atomic, complete, and updates success only after requested", () => {
154
- const entry = layout(root());
155
- ensureCacheEntry({ layout: entry, metadata: { toolchain: { cargo: "cargo 1", rustc: "rustc 1", host: "host" } }, now: new Date("2026-07-21T00:00:00Z") });
156
- const created = JSON.parse(readFileSync(entry.markerPath, "utf8"));
157
- assert.equal(created.schema, CACHE_SCHEMA);
158
- assert.equal(created.id, "target:mac:fixture:a1b2c3d4e5f60708");
159
- assert.equal(created.lastSuccessfulBuildAt, null);
160
- markCacheEntrySuccessful({ layout: entry, now: new Date("2026-07-21T01:00:00Z") });
161
- assert.equal(JSON.parse(readFileSync(entry.markerPath, "utf8")).lastSuccessfulBuildAt, "2026-07-21T01:00:00.000Z");
162
- });
163
-
164
- test("entry leases protect live entries, reclaim stale owners, and suite slots time out without a fallback", () => {
165
- const entry = layout(root());
166
- ensureCacheEntry({ layout: entry });
167
- const lease = acquireCacheLease({ layout: entry, pid: process.pid, argv: ["test"] });
168
- assert.throws(() => acquireCacheLease({ layout: entry, pid: process.pid + 100000, argv: ["second"] }), /lease.*active/i);
169
- lease.release();
170
- writeFileSync(entry.leasePath, JSON.stringify({ pid: 99999999, createdAt: "2000-01-01T00:00:00.000Z" }));
171
- const reclaimed = acquireCacheLease({ layout: entry, pid: process.pid, argv: ["reclaimed"] });
172
- reclaimed.release();
173
- const slot = acquireSuiteBuildSlot({ layout: entry, pid: process.pid, argv: ["holder"], waitMs: 0 });
174
- assert.throws(() => acquireSuiteBuildSlot({ layout: entry, pid: process.pid + 100000, argv: ["waiter"], waitMs: 0, sleep: () => {} }), /suite build slot.*active|timed out/i);
175
- slot.release();
176
- });
177
-
178
- test("build and migration derive identical native-feature cache fingerprints", () => {
179
- const base = root(); const cargoLock = path.join(base, "Cargo.lock"); const cargoToml = path.join(base, "Cargo.toml");
180
- mkdirSync(base, { recursive: true });
181
- writeFileSync(cargoLock, "[[package]]\nname = 'fixture'\n");
182
- writeFileSync(cargoToml, "[dependencies]\nrusqlite = { version = '1', features = [\"bundled-sqlcipher\"] }\n");
183
- const inputs = { cargoLockPath: cargoLock, cargoTomlPath: cargoToml, rustcVerbose: "rustc 1.91.0\nhost: aarch64-apple-darwin", targetTriple: "aarch64-apple-darwin", profile: "release" };
184
- const build = resolveSharedCacheIdentity(inputs);
185
- const migration = resolveSharedCacheIdentity(inputs);
186
- assert.equal(build.fingerprint, migration.fingerprint);
187
- assert.notEqual(build.fingerprint, resolveSharedCacheIdentity({ ...inputs, features: [] }).fingerprint);
188
- });
189
-
190
- test("cache identity ignores local workspace version bumps but keeps registry dependency identity", () => {
191
- const base = root();
192
- const cargoLock = path.join(base, "Cargo.lock");
193
- mkdirSync(base, { recursive: true });
194
- const lock = (appVersion, depVersion, checksum) => `version = 4\n\n[[package]]\nname = "fixture"\nversion = "${appVersion}"\ndependencies = [\n "serde",\n]\n\n[[package]]\nname = "serde"\nversion = "${depVersion}"\nsource = "registry+https://github.com/rust-lang/crates.io-index"\nchecksum = "${checksum}"\n`;
195
- const inputs = { cargoLockPath: cargoLock, rustcVerbose: "rustc 1.91.0\nhost: aarch64-apple-darwin", targetTriple: "aarch64-apple-darwin" };
196
-
197
- writeFileSync(cargoLock, lock("1.0.0", "1.0.0", "aaa"));
198
- const original = resolveSharedCacheIdentity(inputs);
199
- writeFileSync(cargoLock, lock("1.0.1", "1.0.0", "aaa"));
200
- assert.equal(resolveSharedCacheIdentity(inputs).fingerprint, original.fingerprint);
201
- writeFileSync(cargoLock, lock("1.0.1", "1.0.1", "bbb"));
202
- assert.notEqual(resolveSharedCacheIdentity(inputs).fingerprint, original.fingerprint);
203
- });
204
-
205
- test("migration moves an actual src-tauri target symlink and keeps dry-run physically pure", () => {
206
- const fixture = migrationFixture();
207
- const preview = migrateLegacyCache({ ...fixture, dryRun: true });
208
- assert.equal(preview.moved, false);
209
- assert.equal(existsSync(fixture.layout.cacheRoot), false);
210
- assert.equal(lstatSync(fixture.legacyRoot).isSymbolicLink(), true);
211
- const result = migrateLegacyCache({ ...fixture, dryRun: false });
212
- assert.equal(result.moved, true);
213
- assert.equal(existsSync(fixture.legacyRoot), false);
214
- assert.equal(readFileSync(path.join(fixture.layout.targetDir, "artifact"), "utf8"), "legacy-target");
215
- assert.equal(readFileSync(path.join(fixture.layout.cargoHome, "registry"), "utf8"), "legacy-cargo-home");
216
- });
217
-
218
- test("migration rejects target symlinks outside the exact derived fingerprint directory", () => {
219
- const fixture = migrationFixture();
220
- const other = path.join(path.dirname(fixture.legacyTargetRoot), "other-fingerprint");
221
- mkdirSync(other, { recursive: true });
222
- symlinkSync(other, `${fixture.legacyRoot}-wrong`);
223
- assert.throws(() => migrateLegacyCache({ ...fixture, legacyRoot: `${fixture.legacyRoot}-wrong`, dryRun: true }), /unexpected target link/i);
224
- symlinkSync(path.join(other, "missing"), `${fixture.legacyRoot}-dangling`);
225
- assert.throws(() => migrateLegacyCache({ ...fixture, legacyRoot: `${fixture.legacyRoot}-dangling`, dryRun: true }), /unexpected target link/i);
226
- });
227
-
228
- test("migration rolls back target, Cargo home, and src-tauri link after every injected post-move failure", () => {
229
- for (const failureAt of ["after-target-move", "after-entry-marker", "after-cargo-home-move", "before-receipt", "receipt-write"]) {
230
- const fixture = migrationFixture();
231
- assert.throws(() => migrateLegacyCache({ ...fixture, dryRun: false, onPostMove: (step) => { if (step === failureAt) throw new Error(`injected ${step}`); }, writeMigrationReceipt: failureAt === "receipt-write" ? () => { throw new Error("injected receipt write"); } : undefined }), /injected/);
232
- assert.equal(readFileSync(path.join(fixture.legacyTargetRoot, "artifact"), "utf8"), "legacy-target", failureAt);
233
- assert.equal(realpathSync(fixture.legacyRoot), realpathSync(fixture.legacyTargetRoot), failureAt);
234
- assert.equal(lstatSync(fixture.legacyRoot).isSymbolicLink(), true, failureAt);
235
- assert.equal(readFileSync(path.join(fixture.legacyCargoHome, "registry"), "utf8"), "legacy-cargo-home", failureAt);
236
- assert.equal(existsSync(fixture.layout.targetDir), false, failureAt);
237
- assert.equal(existsSync(fixture.layout.cargoHome), false, failureAt);
238
- }
239
- });
240
-
241
- test("a competing suite build slot blocks migration before it moves a legacy cache", () => {
242
- const fixture = migrationFixture();
243
- const slot = acquireSuiteBuildSlot({ layout: fixture.layout, pid: process.pid, argv: ["build"], waitMs: 0 });
244
- try {
245
- assert.throws(() => migrateLegacyCache({ ...fixture, dryRun: false, waitMs: 0 }), /suite build slot.*active|timed out/i);
246
- assert.equal(realpathSync(fixture.legacyRoot), realpathSync(fixture.legacyTargetRoot));
247
- assert.equal(existsSync(fixture.layout.targetDir), false);
248
- } finally { slot.release(); }
249
- });
250
-
251
- test("a live shared build lease blocks migration during in-lock revalidation", () => {
252
- const fixture = migrationFixture();
253
- mkdirSync(fixture.layout.leasesDir, { recursive: true });
254
- writeFileSync(path.join(fixture.layout.leasesDir, "another-build.json"), JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
255
- assert.throws(() => migrateLegacyCache({ ...fixture, dryRun: false }), /shared entry lease is live/i);
256
- assert.equal(realpathSync(fixture.legacyRoot), realpathSync(fixture.legacyTargetRoot));
257
- assert.equal(existsSync(fixture.layout.targetDir), false);
258
- assert.equal(existsSync(fixture.layout.gcLockPath), false);
259
- assert.equal(existsSync(fixture.layout.suiteSlotPath), false);
260
- });
261
-
262
- test("a live legacy release lock blocks migration during in-lock revalidation", () => {
263
- const fixture = migrationFixture();
264
- mkdirSync(path.dirname(fixture.legacyReleaseLockPath), { recursive: true });
265
- writeFileSync(fixture.legacyReleaseLockPath, JSON.stringify({ pid: process.pid }));
266
- assert.throws(() => migrateLegacyCache({ ...fixture, dryRun: false }), /legacy release lock is live/i);
267
- assert.equal(realpathSync(fixture.legacyRoot), realpathSync(fixture.legacyTargetRoot));
268
- assert.equal(existsSync(fixture.layout.targetDir), false);
269
- assert.equal(existsSync(fixture.layout.gcLockPath), false);
270
- assert.equal(existsSync(fixture.layout.suiteSlotPath), false);
271
- });
272
-
273
- test("pruning is oldest successful use first and dry-run equals apply candidates", () => {
274
- const base = root();
275
- const older = layout(base, "1111111111111111");
276
- const newer = layout(base, "2222222222222222");
277
- for (const [entry, stamp] of [[older, "2026-01-01T00:00:00.000Z"], [newer, "2026-02-01T00:00:00.000Z"]]) {
278
- ensureCacheEntry({ layout: entry, now: new Date(stamp) });
279
- writeFileSync(path.join(entry.targetDir, "payload"), "x".repeat(32));
280
- markCacheEntrySuccessful({ layout: entry, now: new Date(stamp) });
281
- }
282
- const snapshot = { cacheRoot: base, targetBytes: 64, entries: [
283
- { id: newer.id, dir: newer.targetDir, bytes: 32, marker: JSON.parse(readFileSync(newer.markerPath)), leased: false },
284
- { id: older.id, dir: older.targetDir, bytes: 32, marker: JSON.parse(readFileSync(older.markerPath)), leased: false },
285
- ] };
286
- const plan = planCachePrune({ snapshot, policy: { targetMaxBytes: 31, desiredFreeBytes: 0 }, protectedEntryIds: new Set() });
287
- assert.deepEqual(plan.candidates.map((x) => x.id), [older.id, newer.id]);
288
- assert.deepEqual(applyCachePrune({ plan, cacheRoot: base, dryRun: true }).candidateIds, applyCachePrune({ plan, cacheRoot: base, dryRun: false }).candidateIds);
289
- assert.equal(existsSync(older.targetDir), false);
290
- assert.equal(existsSync(newer.targetDir), false);
291
- });
292
-
293
- test("pruning fails closed for malformed markers, symlink escapes, and cache root", () => {
294
- const base = root();
295
- const safe = layout(base);
296
- mkdirSync(safe.targetDir, { recursive: true });
297
- writeFileSync(safe.markerPath, "not-json");
298
- const outside = root(); mkdirSync(outside, { recursive: true });
299
- const link = layout(base, "2222222222222222"); mkdirSync(path.dirname(link.targetDir), { recursive: true }); symlinkSync(outside, link.targetDir);
300
- const plan = { candidates: [
301
- { id: "bad", dir: safe.targetDir, bytes: 1 },
302
- { id: "link", dir: link.targetDir, bytes: 1 },
303
- { id: "root", dir: base, bytes: 1 },
304
- ] };
305
- const result = applyCachePrune({ plan, cacheRoot: base, dryRun: false });
306
- assert.deepEqual(result.removedIds, []);
307
- assert.equal(existsSync(safe.targetDir), true);
308
- assert.equal(lstatSync(link.targetDir).isSymbolicLink(), true);
309
- });
310
-
311
- test("entry creation rejects a symlinked cache ancestor before writing outside the cache root", () => {
312
- const parent = root(); const outside = root(); mkdirSync(parent, { recursive: true }); mkdirSync(outside, { recursive: true });
313
- symlinkSync(outside, path.join(parent, "cache"));
314
- const unsafe = resolveCacheLayout({ cacheRoot: path.join(parent, "cache"), platform: "mac", architecture: "aarch64", app: "fixture", fingerprint: "3333333333333333", kind: "release" });
315
- assert.throws(() => ensureCacheEntry({ layout: unsafe }), /symlink|unsafe/i);
316
- assert.equal(existsSync(path.join(outside, "targets")), false);
317
- });
318
-
319
- test("prune takes the global GC lock and blocks a racing lease before deletion", () => {
320
- const base = root(); const entry = layout(base, "4444444444444444"); ensureCacheEntry({ layout: entry }); writeFileSync(path.join(entry.targetDir, "payload"), "x");
321
- const plan = { cacheRoot: base, candidates: [{ id: entry.id, dir: entry.targetDir, bytes: 1 }] };
322
- assert.throws(() => applyCachePrune({ plan, cacheRoot: base, dryRun: false, beforeDelete: () => acquireCacheLease({ layout: entry }) }), /global GC lock active/);
323
- assert.equal(existsSync(entry.targetDir), true);
324
- assert.equal(existsSync(path.join(base, "gc", "gc.lock.json")), false);
325
- });
326
-
327
- test("a real child-process lease acquired after planning excludes its entry from prune", async () => {
328
- const base = root(); const entry = layout(base, "5555555555555555"); ensureCacheEntry({ layout: entry }); writeFileSync(path.join(entry.targetDir, "payload"), "x");
329
- const plan = { cacheRoot: base, candidates: [{ id: entry.id, dir: entry.targetDir, bytes: 1 }] };
330
- const moduleUrl = new URL("./cache-policy.mjs", import.meta.url).href;
331
- const child = spawn(process.execPath, ["--input-type=module", "--eval", `import { resolveCacheLayout, acquireCacheLease } from ${JSON.stringify(moduleUrl)}; const l=resolveCacheLayout({cacheRoot:process.argv[1],platform:'mac',architecture:'aarch64',app:'fixture',fingerprint:'5555555555555555',kind:'release'}); const lease=acquireCacheLease({layout:l}); console.log('ready'); setTimeout(()=>lease.release(),500);`, base], { stdio: ["ignore", "pipe", "pipe"] });
332
- await once(child.stdout, "data");
333
- const result = applyCachePrune({ plan, cacheRoot: base, dryRun: false });
334
- assert.deepEqual(result.removedIds, []);
335
- assert.equal(existsSync(entry.targetDir), true);
336
- await once(child, "exit");
337
- });
338
-
339
- test("write-volume inspection deduplicates same devices and enforces both hard floors", () => {
340
- const statfs = (dir) => ({ bsize: 1024, bavail: dir.includes("cache") ? 20 : 30, dev: dir.includes("same") ? 1 : dir.includes("cache") ? 2 : 3 });
341
- const same = inspectWriteVolumes({ repoRoot: "/same/repo", vaultRoot: "/same/vault", cacheRoot: "/same/cache", statfs });
342
- assert.equal(same.length, 1);
343
- const distinct = inspectWriteVolumes({ repoRoot: "/repo", vaultRoot: "/repo", cacheRoot: "/cache", statfs });
344
- assert.equal(distinct.length, 2);
345
- assert.throws(() => assertWriteVolumeFloors({ volumes: distinct, policy: { hardFreeBytes: 25 * 1024 }, configuredRepoMinimumBytes: 25 * 1024 }), /cache|repo/i);
346
- });
@@ -1,195 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import path from "node:path";
3
- import test from "node:test";
4
-
5
- import { assertComputePolicyAllowsCargo, cargoCacheEnvironment, cargoProjectRoot, cargoSubcommand, checkHookInput, computePolicyPath, resolveRealCargo, resolveRealRustc, runCargoGuard, shellRunsCargoTest, shouldGuardCargo } from "./cargo-guard.mjs";
6
-
7
- test("compute policy path ignores XDG_CONFIG_HOME regardless of value", () => {
8
- assert.equal(
9
- computePolicyPath({ platform: "linux", env: { XDG_CONFIG_HOME: "/one/path" }, home: "/home/u" }),
10
- computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }),
11
- );
12
- assert.equal(computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }), path.join("/home/u/.config/rightsuite", "compute-policy.json"));
13
- });
14
-
15
- test("Cargo guard serializes compiling commands but bypasses inspection and formatting", () => {
16
- for (const args of [["build"], ["test"], ["check"], ["clippy"], ["nextest", "run"], ["clean"], ["+stable", "bench"]]) {
17
- assert.equal(shouldGuardCargo(args), true, args.join(" "));
18
- }
19
- for (const args of [["fmt", "--check"], ["metadata"], ["tree"], ["fetch"], ["--version"], []]) {
20
- assert.equal(shouldGuardCargo(args), false, args.join(" "));
21
- }
22
- assert.equal(cargoSubcommand(["+stable", "test"]), "test");
23
- assert.equal(shouldGuardCargo(["--color", "always", "fmt"]), false);
24
- assert.equal(shouldGuardCargo(["--config", "net.git-fetch-with-cli=true", "check"]), true);
25
- assert.equal(shouldGuardCargo(["-Z", "unstable-options", "fmt"]), false);
26
- });
27
-
28
- test("global policy denies Cargo test before Cargo resolution", async () => {
29
- const policy = { exists: () => true, read: () => '{"schemaVersion":1,"cargoTest":"deny"}', env: {} };
30
- assert.throws(() => assertComputePolicyAllowsCargo(["test", "-p", "app"], policy), /denied by global/);
31
- assert.doesNotThrow(() => assertComputePolicyAllowsCargo(["check"], policy));
32
- let resolved = false;
33
- await assert.rejects(runCargoGuard(["test"], {
34
- env: { RIGHTSUITE_COMPUTE_POLICY: "/policy.json" },
35
- policyOptions: { exists: () => true, read: () => '{"schemaVersion":1,"cargoTest":"deny"}' },
36
- resolveCargo: () => { resolved = true; return "/real/cargo"; },
37
- }), /denied by global/);
38
- assert.equal(resolved, false);
39
- });
40
-
41
- test("Bash policy detects direct, absolute, rustup and nested Cargo tests only", () => {
42
- for (const command of ["cargo test -p app", "cd app && /toolchain/bin/cargo +stable test", "rustup run stable cargo test", "bash -c 'cargo test'"]) {
43
- assert.equal(shellRunsCargoTest(command), true, command);
44
- }
45
- for (const command of ["cargo check", "rg 'cargo test' docs", "echo cargo test", "cargo fmt --check"]) {
46
- assert.equal(shellRunsCargoTest(command), false, command);
47
- }
48
- assert.throws(() => checkHookInput(JSON.stringify({ tool_input: { command: "cargo test" } }), {
49
- exists: () => true, read: () => '{"schemaVersion":1,"cargoTest":"deny"}', env: {},
50
- }), /denied by global/);
51
- });
52
-
53
- test("Cargo guard resolves real Cargo through rustup or explicit override", () => {
54
- assert.equal(resolveRealCargo({ env: { RIGHTSUITE_REAL_CARGO: "/opt/toolchain/cargo" } }), path.resolve("/opt/toolchain/cargo"));
55
- const calls = [];
56
- const cargo = resolveRealCargo({ env: {}, run: (command, args) => { calls.push([command, args]); return { status: 0, stdout: "/real/cargo\n" }; } });
57
- assert.equal(cargo, path.resolve("/real/cargo"));
58
- assert.deepEqual(calls[0][1], ["which", "cargo"]);
59
- assert.equal(resolveRealRustc({ env: { RIGHTSUITE_REAL_RUSTC: "/opt/toolchain/rustc" } }), path.resolve("/opt/toolchain/rustc"));
60
- assert.equal(resolveRealRustc({ env: {}, run: () => ({ status: 0, stdout: "/real/rustc\n" }) }), path.resolve("/real/rustc"));
61
- });
62
-
63
- function cacheEnv(overrides = {}) {
64
- return { ...(process.platform === "win32" ? { LOCALAPPDATA: "C:\\Users\\test\\AppData\\Local" } : { RIGHT_RELEASE_CACHE_ROOT: "/tmp/rightkit-release-cache" }), ...overrides };
65
- }
66
-
67
- test("Cargo guard injects project-keyed target and sccache directories", async () => {
68
- let guarded;
69
- await runCargoGuard(["build"], {
70
- env: cacheEnv(), resolveCargo: () => "/real/cargo", resolveRustc: () => "/real/rustc",
71
- runHeavy: async (_args, config) => { guarded = config.env; return 0; },
72
- });
73
- assert.match(guarded.CARGO_TARGET_DIR, /dev-targets[/\\][^/\\]+-[a-f0-9]{12}$/);
74
- assert.match(guarded.SCCACHE_DIR, /[/\\]sccache$/);
75
- assert.equal(guarded.RUSTC_WRAPPER, "sccache");
76
- });
77
-
78
- test("Cargo guard rejects cache escapes and alternate compiler wrappers", async () => {
79
- const run = (args, overrides) => runCargoGuard(args, {
80
- env: cacheEnv(overrides), resolveCargo: () => "/real/cargo", resolveRustc: () => "/real/rustc",
81
- runHeavy: async () => 0,
82
- });
83
- await assert.rejects(run(["build"], { CARGO_TARGET_DIR: path.resolve("outside-target") }), /CARGO_TARGET_DIR/);
84
- await assert.rejects(run(["build"], { SCCACHE_DIR: path.resolve("outside-sccache") }), /SCCACHE_DIR/);
85
- await assert.rejects(run(["build", `--target-dir=${path.resolve("outside-flag")}`]), /--target-dir/);
86
- await assert.rejects(run(["build"], { RUSTC_WRAPPER: "rustc-wrapper" }), /RUSTC_WRAPPER/);
87
- });
88
-
89
- test("Cargo guard hands the whole invocation to the broker on a managed host", async () => {
90
- const calls = [];
91
- let resolvedReal = false;
92
- const options = {
93
- env: { TEST: "1" },
94
- policyOptions: { exists: () => false },
95
- isBrokerHost: () => true,
96
- resolveShim: () => "/managed/agent-bin/cargo",
97
- resolveCargo: () => { resolvedReal = true; return "/real/cargo"; },
98
- resolveRustc: () => { resolvedReal = true; return "/real/rustc"; },
99
- runHeavy: async () => { throw new Error("must not run heavy on a broker host"); },
100
- runLight: async (command, args) => { calls.push([command, args]); return 0; },
101
- };
102
- // A guarded command (build) and a light command (--version) both delegate to
103
- // the broker shim verbatim, and the real-toolchain resolver is never touched.
104
- assert.equal(await runCargoGuard(["build", "--release"], options), 0);
105
- assert.equal(await runCargoGuard(["--version"], options), 0);
106
- assert.deepEqual(calls, [["/managed/agent-bin/cargo", ["build", "--release"]], ["/managed/agent-bin/cargo", ["--version"]]]);
107
- assert.equal(resolvedReal, false);
108
- });
109
-
110
- test("Cargo guard falls back to real resolution when a broker signal has no shim", async () => {
111
- const calls = [];
112
- await runCargoGuard(["build"], {
113
- env: cacheEnv(), isBrokerHost: () => true, resolveShim: () => null,
114
- resolveCargo: () => "/real/cargo", resolveRustc: () => "/real/rustc",
115
- runHeavy: async (args) => { calls.push(args); return 0; },
116
- });
117
- assert.deepEqual(calls[0].slice(0, 2), ["--", "/real/cargo"]);
118
- });
119
-
120
- test("Cargo guard routes heavy and light commands without recursion", async () => {
121
- const calls = [];
122
- const options = {
123
- env: { TEST: "1", ...cacheEnv() },
124
- policyOptions: { exists: () => false },
125
- resolveCargo: () => "/real/cargo",
126
- resolveRustc: () => "/real/rustc",
127
- runHeavy: async (args, config) => { calls.push(["heavy", args, config.env]); return 0; },
128
- runLight: async (command, args, env) => { calls.push(["light", command, args, env]); return 0; },
129
- };
130
- assert.equal(await runCargoGuard(["test"], options), 0);
131
- assert.equal(await runCargoGuard(["fmt", "--check"], options), 0);
132
- assert.deepEqual(calls[0].slice(0, 2), ["heavy", ["--", "/real/cargo", "test"]]);
133
- const cacheRoot = cacheEnv().RIGHT_RELEASE_CACHE_ROOT ?? path.join(cacheEnv().LOCALAPPDATA, "RightSuite", "Cache", "release");
134
- assert.equal(calls[0][2].CARGO_TARGET_DIR.startsWith(path.join(path.resolve(cacheRoot), "dev-targets") + path.sep), true);
135
- assert.equal(calls[0][2].RUSTC_WRAPPER, "sccache");
136
- assert.deepEqual(calls[1], ["light", "/real/cargo", ["fmt", "--check"], options.env]);
137
- });
138
-
139
- test("Cargo guard forces target and compiler caches for each project", () => {
140
- const existing = new Set(["/repo/Cargo.toml", "/repo/crate/Cargo.toml"]);
141
- assert.equal(cargoProjectRoot(["test"], { cwd: "/repo/crate", platform: "mac", exists: (file) => existing.has(file) }), "/repo");
142
- const env = cargoCacheEnvironment(["test"], {
143
- cwd: "/repo/crate", platform: "mac", exists: (file) => existing.has(file),
144
- env: { RIGHT_RELEASE_CACHE_ROOT: "/cache" },
145
- });
146
- assert.match(env.CARGO_TARGET_DIR, /^\/cache\/dev-targets\/repo-[a-f0-9]{12}$/);
147
- assert.equal(env.RUSTC_WRAPPER, "sccache");
148
- assert.equal(env.SCCACHE_DIR, "/cache/sccache");
149
- assert.equal(env.RIGHTSUITE_CARGO_CACHE_GUARD, "1");
150
- });
151
-
152
- test("Cargo guard rejects cache bypasses and permits RightKit-owned cache paths", () => {
153
- const base = { cwd: "/repo", platform: "mac", exists: () => false };
154
- assert.throws(() => cargoCacheEnvironment(["test"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", CARGO_TARGET_DIR: "/tmp/target" } }), /CARGO_TARGET_DIR must stay inside/);
155
- assert.throws(() => cargoCacheEnvironment(["test", "--target-dir", "/tmp/target"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache" } }), /--target-dir must stay inside/);
156
- assert.throws(() => cargoCacheEnvironment(["test"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RUSTC_WRAPPER: "rustc-wrapper" } }), /must be sccache/);
157
- const env = cargoCacheEnvironment(["test", "--target-dir=/cache/test-targets/app"], {
158
- ...base,
159
- env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", CARGO_TARGET_DIR: "/cache/test-targets/app", SCCACHE_DIR: "/cache/sccache", RUSTC_WRAPPER: "/usr/bin/sccache" },
160
- });
161
- assert.equal(env.CARGO_TARGET_DIR, "/cache/test-targets/app");
162
- });
163
-
164
- test("Cargo guard passes a broker-owned CARGO_TARGET_DIR through instead of rejecting it", () => {
165
- const base = { cwd: "/repo", platform: "mac", exists: () => false };
166
- // Outside the shared cache root, which would be rejected on a non-broker host
167
- // (see the previous test) — but the broker owns this value, not Cache V2.
168
- const env = cargoCacheEnvironment(["test"], {
169
- ...base,
170
- env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", CARGO_TARGET_DIR: "/broker/workspace/target" },
171
- });
172
- assert.equal(env.CARGO_TARGET_DIR, "/broker/workspace/target");
173
-
174
- // Same for an explicit --target-dir outside the cache root.
175
- const explicit = cargoCacheEnvironment(["test", "--target-dir", "/broker/workspace/target"], {
176
- ...base,
177
- env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" },
178
- });
179
- assert.doesNotThrow(() => explicit);
180
-
181
- // Non-broker host: unchanged, still rejects the cache bypass.
182
- assert.throws(
183
- () => cargoCacheEnvironment(["test"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", CARGO_TARGET_DIR: "/tmp/target" } }),
184
- /CARGO_TARGET_DIR must stay inside/,
185
- );
186
- });
187
-
188
- test("Cargo guard derives native Windows cache paths", () => {
189
- const env = cargoCacheEnvironment(["build"], {
190
- cwd: "D:\\Claude\\citadel", platform: "win", exists: (file) => file === "D:\\Claude\\citadel\\Cargo.toml",
191
- env: { LOCALAPPDATA: "C:\\Users\\test\\AppData\\Local" },
192
- });
193
- assert.match(env.CARGO_TARGET_DIR, /^C:\\Users\\test\\AppData\\Local\\RightSuite\\Cache\\release\\dev-targets\\citadel-[a-f0-9]{12}$/);
194
- assert.equal(env.SCCACHE_DIR, "C:\\Users\\test\\AppData\\Local\\RightSuite\\Cache\\release\\sccache");
195
- });
@@ -1,82 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { spawnSync } from "node:child_process";
6
- import test from "node:test";
7
- import { fileURLToPath } from "node:url";
8
-
9
- import { resolveTargetRoot } from "./cargo-target.mjs";
10
-
11
- const here = path.dirname(fileURLToPath(import.meta.url));
12
- const cli = path.join(here, "cargo-target.mjs");
13
-
14
- function fakeCargo() {
15
- const bin = mkdtempSync(path.join(os.tmpdir(), "rightkit-fake-cargo-"));
16
- return {
17
- bin,
18
- execFileSync(_command, args) {
19
- const manifest = args.at(-1);
20
- if (manifest.includes("fail-manifest")) throw new Error("boom");
21
- if (manifest.includes("bad-target-manifest")) return '{"target_directory":"relative/target"}';
22
- return JSON.stringify({ target_directory: path.join(bin, "target") });
23
- },
24
- };
25
- }
26
-
27
- test("resolveTargetRoot returns the absolute target_directory Cargo metadata reports", () => {
28
- const { bin, execFileSync } = fakeCargo();
29
- const manifest = path.join(mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-")), "Cargo.toml");
30
- const result = resolveTargetRoot(manifest, { execFileSync });
31
- assert.equal(result, path.resolve(path.join(bin, "target")));
32
- });
33
-
34
- test("resolveTargetRoot fails closed, naming the manifest, when target_directory is not absolute", () => {
35
- const { execFileSync } = fakeCargo();
36
- const manifest = path.join(mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-")), "bad-target-manifest-Cargo.toml");
37
- assert.throws(
38
- () => resolveTargetRoot(manifest, { execFileSync }),
39
- new RegExp(`metadata for .*bad-target-manifest-Cargo\\.toml did not report an absolute target_directory`),
40
- );
41
- });
42
-
43
- test("resolveTargetRoot fails closed, naming the manifest, when the metadata command exits non-zero", () => {
44
- const { execFileSync } = fakeCargo();
45
- const manifest = path.join(mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-")), "fail-manifest-Cargo.toml");
46
- assert.throws(
47
- () => resolveTargetRoot(manifest, { execFileSync }),
48
- new RegExp(`metadata failed for .*fail-manifest-Cargo\\.toml`),
49
- );
50
- });
51
-
52
- test("resolveTargetRoot throws when no manifest path is given", () => {
53
- assert.throws(() => resolveTargetRoot(), /resolveTargetRoot requires a Cargo.toml manifest path/);
54
- assert.throws(() => resolveTargetRoot(""), /resolveTargetRoot requires a Cargo.toml manifest path/);
55
- });
56
-
57
- test("CLI mode writes only the resolved path to stdout on success", () => {
58
- const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
59
- const manifest = path.join(manifestDir, "Cargo.toml");
60
- mkdirSync(path.join(manifestDir, "src"));
61
- writeFileSync(path.join(manifestDir, "src", "lib.rs"), "");
62
- writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
63
- const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8" });
64
- assert.equal(result.status, 0, result.stderr);
65
- assert.equal(path.isAbsolute(result.stdout.trim()), true);
66
- });
67
-
68
- test("CLI mode writes a message to stderr and exits 1 on failure", () => {
69
- const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
70
- const manifest = path.join(manifestDir, "fail-manifest-Cargo.toml");
71
- writeFileSync(manifest, "not valid toml");
72
- const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8" });
73
- assert.equal(result.status, 1);
74
- assert.equal(result.stdout, "");
75
- assert.match(result.stderr, /metadata failed for .*fail-manifest-Cargo\.toml/);
76
- });
77
-
78
- test("CLI mode requires a manifest argument", () => {
79
- const result = spawnSync(process.execPath, [cli], { encoding: "utf8" });
80
- assert.equal(result.status, 1);
81
- assert.match(result.stderr, /usage: cargo-target\.mjs/);
82
- });
@@ -1,14 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { spawnSync } from "node:child_process";
3
- import { fileURLToPath } from "node:url";
4
- import test from "node:test";
5
-
6
- const helper = fileURLToPath(new URL("./create-mac-updater.mjs", import.meta.url));
7
-
8
- test("documents the final signed-app to signed-updater sequence in dry-run mode", () => {
9
- const result = spawnSync(process.execPath, [helper, "--app", "bundle/Test.app", "--output", "bundle/Test.app.tar.gz", "--dry-run"], { encoding: "utf8" });
10
- assert.equal(result.status, 0, result.stderr);
11
- assert.match(result.stdout, /COPYFILE_DISABLE=1 tar .*Test\.app\.tar\.gz.*Test\.app[\\/]Contents/);
12
- assert.doesNotMatch(result.stdout, / Test\.app$/m);
13
- assert.match(result.stdout, /tauri signer sign .*Test\.app\.tar\.gz/);
14
- });