@rightkit/release 0.2.62 → 0.2.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-release.mjs +43 -6
- package/build-release.test.mjs +29 -0
- package/cargo-contract.mjs +21 -4
- package/deps.mjs +0 -0
- package/lsclean.sh +0 -0
- package/package.json +8 -7
- package/process-liveness.mjs +39 -0
- package/release-state.mjs +5 -0
- package/release-state.test.mjs +3 -0
- package/release.mjs +0 -0
- package/right-suite-contract.test.mjs +36 -5
- package/rightkit-versions.json +4 -2
- package/target-bridge.mjs +2 -2
- package/target-bridge.test.mjs +17 -0
- package/upload-large.mjs +0 -0
package/build-release.mjs
CHANGED
|
@@ -30,12 +30,14 @@ import { assertNsisInPlaceUpgradeContract } from "./nsis-upgrade-contract.mjs";
|
|
|
30
30
|
import { buildPathPrefix, collectPreflight, formatPreflight, preflightFailures } from "./preflight.mjs";
|
|
31
31
|
import { assertCleanSource } from "./source-gate.mjs";
|
|
32
32
|
import { acquireHeavyWorkSlot, heavyCommandEnvironment, terminateProcessTree } from "./heavy-command.mjs";
|
|
33
|
+
import { processGroupCpuSeconds } from "./process-liveness.mjs";
|
|
33
34
|
|
|
34
35
|
// Fingerprint of the pipeline code that can change the bytes we ship or the way
|
|
35
36
|
// they are signed — deliberately NOT the package version, which moves for docs
|
|
36
37
|
// and test-only edits and would force a cold Rust rebuild of every app each time.
|
|
37
38
|
// These four files are the ones whose behaviour the cached target directory can
|
|
38
39
|
// outlive.
|
|
40
|
+
|
|
39
41
|
const PIPELINE_FINGERPRINT_SOURCES = ["release.mjs", "sign-windows.mjs", "tauri-bundle-marker.mjs", "nsis-payload.mjs"];
|
|
40
42
|
const PIPELINE_FINGERPRINT = createHash("sha256")
|
|
41
43
|
.update(PIPELINE_FINGERPRINT_SOURCES.map((file) => `${file}:${createHash("sha256").update(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), file))).digest("hex")}`).join("\n"))
|
|
@@ -281,7 +283,7 @@ try {
|
|
|
281
283
|
else if (realpathSync(targetLink) !== realpathSync(cacheTarget)) {
|
|
282
284
|
fail(`primary checkout target exists and is not the release cache link: ${targetLink}`);
|
|
283
285
|
}
|
|
284
|
-
await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"], appRoot, env, cacheTarget);
|
|
286
|
+
await runProgress(config.packageManager ?? "pnpm", ["install", "--frozen-lockfile"], appRoot, env, cacheTarget, path.join(stateRoot, "stall-install.log"));
|
|
285
287
|
},
|
|
286
288
|
build: async () => {
|
|
287
289
|
throwIfInterrupted();
|
|
@@ -291,6 +293,7 @@ try {
|
|
|
291
293
|
appRoot,
|
|
292
294
|
env,
|
|
293
295
|
[managedCargoTarget ?? env.CARGO_TARGET_DIR, path.join(appRoot, ".cache")],
|
|
296
|
+
path.join(stateRoot, "stall-build.log"),
|
|
294
297
|
);
|
|
295
298
|
checkpoint(stateRoot, "build_complete");
|
|
296
299
|
checkpoint(stateRoot, "signed");
|
|
@@ -429,7 +432,7 @@ function resolveManagedCargoTarget(manifestPath) {
|
|
|
429
432
|
return target;
|
|
430
433
|
}
|
|
431
434
|
|
|
432
|
-
async function runProgress(cmd, runArgs, cwd, env, watchDir) {
|
|
435
|
+
async function runProgress(cmd, runArgs, cwd, env, watchDir, diagnosticPath = null) {
|
|
433
436
|
const inactivityMs = Number(process.env.RIGHT_RELEASE_STALL_MS ?? 10 * 60 * 1000);
|
|
434
437
|
const absoluteMs = Number(process.env.RIGHT_RELEASE_ABSOLUTE_MS ?? 90 * 60 * 1000);
|
|
435
438
|
let lastProgress = Date.now();
|
|
@@ -448,17 +451,51 @@ async function runProgress(cmd, runArgs, cwd, env, watchDir) {
|
|
|
448
451
|
return;
|
|
449
452
|
}
|
|
450
453
|
const closeWatchers = watchProgress(watchDirs, () => { lastProgress = Date.now(); });
|
|
454
|
+
// A killed step used to leave no trace of what it was doing, so every stall
|
|
455
|
+
// cost a forensic dig. Keep a bounded tail to write out if we do kill it.
|
|
456
|
+
let tail = "";
|
|
451
457
|
for (const [stream, output] of [[child.stdout, process.stdout], [child.stderr, process.stderr]]) {
|
|
452
|
-
stream.on("data", (chunk) => {
|
|
458
|
+
stream.on("data", (chunk) => {
|
|
459
|
+
lastProgress = Date.now();
|
|
460
|
+
tail = `${tail}${chunk}`.slice(-64 * 1024);
|
|
461
|
+
output.write(chunk);
|
|
462
|
+
});
|
|
453
463
|
}
|
|
464
|
+
let lastCpu = processGroupCpuSeconds(child.pid);
|
|
454
465
|
const monitor = setInterval(() => {
|
|
455
466
|
const mtime = Math.max(...watchDirs.map(newestMtime));
|
|
456
467
|
if (mtime > lastMtime) { lastMtime = mtime; lastProgress = Date.now(); }
|
|
457
|
-
|
|
458
|
-
|
|
468
|
+
const cpu = processGroupCpuSeconds(child.pid);
|
|
469
|
+
if (cpu !== null && lastCpu !== null && cpu > lastCpu) lastProgress = Date.now();
|
|
470
|
+
if (cpu !== null) lastCpu = cpu;
|
|
471
|
+
if (Date.now() - started > absoluteMs) stop(`absolute limit exceeded (${Math.round(absoluteMs / 60000)}m)`, cpu);
|
|
472
|
+
else if (Date.now() - lastProgress > inactivityMs) stop(`no output, file, or CPU progress for ${Math.round(inactivityMs / 60000)}m`, cpu);
|
|
459
473
|
}, Math.min(30_000, Math.max(250, Math.floor(inactivityMs / 4))));
|
|
460
474
|
const cleanup = () => { clearInterval(monitor); closeWatchers(); };
|
|
461
|
-
const stop = (reason
|
|
475
|
+
const stop = (reason, cpu = null) => {
|
|
476
|
+
cleanup();
|
|
477
|
+
if (diagnosticPath) {
|
|
478
|
+
try {
|
|
479
|
+
mkdirSync(path.dirname(diagnosticPath), { recursive: true });
|
|
480
|
+
writeFileSync(diagnosticPath, [
|
|
481
|
+
`reason: ${reason}`,
|
|
482
|
+
`command: ${cmd} ${runArgs.join(" ")}`,
|
|
483
|
+
`cwd: ${cwd}`,
|
|
484
|
+
`watched: ${watchDirs.join(path.delimiter)}`,
|
|
485
|
+
`elapsedMs: ${Date.now() - started}`,
|
|
486
|
+
`cpuSeconds: ${cpu ?? "unavailable"}`,
|
|
487
|
+
`killedAt: ${new Date().toISOString()}`,
|
|
488
|
+
"",
|
|
489
|
+
"--- last output ---",
|
|
490
|
+
tail || "(the step produced no output at all)",
|
|
491
|
+
"",
|
|
492
|
+
].join("\n"));
|
|
493
|
+
console.error(`right-release: stall diagnostic written to ${diagnosticPath}`);
|
|
494
|
+
} catch { /* diagnostics are best effort; never mask the stall itself */ }
|
|
495
|
+
}
|
|
496
|
+
killTree(child.pid);
|
|
497
|
+
reject(new Error(`release step stalled: ${reason}`));
|
|
498
|
+
};
|
|
462
499
|
child.once("error", (error) => { cleanup(); reject(error); });
|
|
463
500
|
child.once("exit", (code) => { cleanup(); child = null; code === 0 ? resolve() : reject(new Error(`${cmd} exited ${code}`)); });
|
|
464
501
|
});
|
package/build-release.test.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { parseCpuSeconds, processGroupCpuSeconds } from "./process-liveness.mjs";
|
|
8
9
|
|
|
9
10
|
const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs"), "utf8");
|
|
10
11
|
const build = path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs");
|
|
@@ -85,3 +86,31 @@ test("Windows seal and cache identity bind the signing contract, config, and rec
|
|
|
85
86
|
assert.match(source, /receipts: signingIdentity\.receiptInputs\.map/);
|
|
86
87
|
assert.match(readFileSync(new URL("./release.mjs", import.meta.url), "utf8"), /item\.after\?\.sha256 !== currentHashes/);
|
|
87
88
|
});
|
|
89
|
+
|
|
90
|
+
test("CPU sampling separates a silent working step from a hang", () => {
|
|
91
|
+
// Real `ps -o cputime=` shapes, including a multi-process group.
|
|
92
|
+
assert.equal(parseCpuSeconds(" 0:03.21\n"), 3.21);
|
|
93
|
+
assert.equal(parseCpuSeconds(" 1:02:03\n"), 3723);
|
|
94
|
+
assert.equal(parseCpuSeconds(" 2-01:00:00\n"), 176400);
|
|
95
|
+
// A process group sums every member, which is how a linker under cargo shows up.
|
|
96
|
+
assert.equal(parseCpuSeconds(" 0:10.00\n 0:05.00\n"), 15);
|
|
97
|
+
// A vanished process yields no rows: null, not zero, so the caller falls back
|
|
98
|
+
// to output and mtime rather than treating it as "no CPU used".
|
|
99
|
+
assert.equal(parseCpuSeconds(""), null);
|
|
100
|
+
assert.equal(parseCpuSeconds(" \n"), null);
|
|
101
|
+
assert.equal(parseCpuSeconds("garbage"), null);
|
|
102
|
+
assert.equal(parseCpuSeconds(undefined), null);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("CPU sampling degrades to the old behaviour rather than inventing a verdict", () => {
|
|
106
|
+
const ok = (stdout) => () => ({ status: 0, stdout });
|
|
107
|
+
// A working process group reports advancing CPU.
|
|
108
|
+
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok(" 0:42.50\n") }), 42.5);
|
|
109
|
+
// Windows has no `ps`; sampling is skipped, not guessed.
|
|
110
|
+
assert.equal(processGroupCpuSeconds(123, { platform: "win32", run: ok(" 0:42.50\n") }), null);
|
|
111
|
+
// No pid, a failed probe, or an empty result must all be "cannot measure",
|
|
112
|
+
// never 0 — 0 would look like a hang and kill a healthy build.
|
|
113
|
+
assert.equal(processGroupCpuSeconds(0, { platform: "darwin", run: ok(" 0:42.50\n") }), null);
|
|
114
|
+
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: () => ({ status: 1, stdout: "" }) }), null);
|
|
115
|
+
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok("") }), null);
|
|
116
|
+
});
|
package/cargo-contract.mjs
CHANGED
|
@@ -10,19 +10,36 @@ const CRATES_IO_SOURCES = new Set([
|
|
|
10
10
|
]);
|
|
11
11
|
const CACHE_V2_ENVIRONMENT = ["CARGO_HOME", "CARGO_TARGET_DIR", "RUSTC_WRAPPER", "SCCACHE_DIR", "SCCACHE_BASEDIRS", "RIGHT_RELEASE_CACHE_OWNER"];
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function managedCargoShimOnPath(env = process.env, platform = process.platform, exists = existsSync) {
|
|
14
|
+
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
15
|
+
const delimiter = platform === "win32" ? ";" : ":";
|
|
16
|
+
const extensions = platform === "win32"
|
|
17
|
+
? String(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)
|
|
18
|
+
: [""];
|
|
19
|
+
for (const directory of String(env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
20
|
+
for (const extension of extensions) {
|
|
21
|
+
const candidate = pathApi.join(directory, `cargo${extension.toLowerCase()}`);
|
|
22
|
+
if (!exists(candidate)) continue;
|
|
23
|
+
return /(?:^|\/)(?:\.rightkit-managed|rightkitmanagedagent)\/agent-bin\/cargo(?:\.[^/]*)?$/i
|
|
24
|
+
.test(candidate.replaceAll("\\", "/"));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isolatedCargoMetadataEnv(cargoHome, env = process.env, platform = process.platform, exists = existsSync) {
|
|
14
31
|
const metadataEnv = { ...env };
|
|
15
32
|
for (const name of CACHE_V2_ENVIRONMENT) delete metadataEnv[name];
|
|
16
|
-
if (metadataEnv.RIGHTKIT_BUILD_BROKER_SOCKET) return metadataEnv;
|
|
33
|
+
if (metadataEnv.RIGHTKIT_BUILD_BROKER_SOCKET || managedCargoShimOnPath(metadataEnv, platform, exists)) return metadataEnv;
|
|
17
34
|
return { ...metadataEnv, CARGO_HOME: cargoHome };
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
export function cargoExecutable(platform = process.platform) {
|
|
21
|
-
return
|
|
38
|
+
return "cargo";
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
export function cargoArguments(args, platform = process.platform) {
|
|
25
|
-
return
|
|
42
|
+
return args;
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
export function validateRightKitCargoContract(root, publishedVersions, label = path.basename(root)) {
|
package/deps.mjs
CHANGED
|
File without changes
|
package/lsclean.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.64",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: native-host signed installers, updater artifacts, hardening, immutable GitHub Release upload, and add-on adoption.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"*.py"
|
|
16
16
|
],
|
|
17
17
|
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node --test *.test.mjs",
|
|
20
|
+
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
21
|
+
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
22
|
+
},
|
|
18
23
|
"publishConfig": {
|
|
19
24
|
"registry": "https://registry.npmjs.org/",
|
|
20
25
|
"access": "public"
|
|
@@ -24,9 +29,5 @@
|
|
|
24
29
|
"url": "git+https://github.com/bogusyogi/claude.git",
|
|
25
30
|
"directory": "tools/rightkit/packages/release"
|
|
26
31
|
},
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
30
|
-
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
32
|
+
"packageManager": "pnpm@11.18.0"
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Total CPU seconds reported by `ps` for a process group, summed across every
|
|
5
|
+
* member. Accepts the `[[dd-]hh:]mm:ss[.ff]` forms `ps` emits.
|
|
6
|
+
* Returns null when no row parses, so callers can tell "no CPU used" apart from
|
|
7
|
+
* "could not measure".
|
|
8
|
+
*/
|
|
9
|
+
export function parseCpuSeconds(text) {
|
|
10
|
+
if (typeof text !== "string") return null;
|
|
11
|
+
let total = null;
|
|
12
|
+
for (const line of text.split("\n")) {
|
|
13
|
+
const value = line.trim();
|
|
14
|
+
if (!value) continue;
|
|
15
|
+
const match = value.match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)$/);
|
|
16
|
+
if (!match) continue;
|
|
17
|
+
const [, days, hours, minutes, seconds] = match;
|
|
18
|
+
total = (total ?? 0)
|
|
19
|
+
+ Number(days ?? 0) * 86_400
|
|
20
|
+
+ Number(hours ?? 0) * 3_600
|
|
21
|
+
+ Number(minutes) * 60
|
|
22
|
+
+ Number(seconds);
|
|
23
|
+
}
|
|
24
|
+
return total;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A link or LTO pass can run for many minutes producing no console output and
|
|
29
|
+
* no new files, which output alone cannot tell apart from a hang. Consuming CPU
|
|
30
|
+
* is the signal that separates the two, so a step burning CPU is never stalled.
|
|
31
|
+
* Returns null when CPU cannot be sampled, which leaves the caller on its
|
|
32
|
+
* output-and-mtime behaviour rather than inventing a verdict.
|
|
33
|
+
*/
|
|
34
|
+
export function processGroupCpuSeconds(pid, { platform = process.platform, run = spawnSync } = {}) {
|
|
35
|
+
if (!pid || platform === "win32") return null;
|
|
36
|
+
const probe = run("ps", ["-o", "cputime=", "-g", String(pid)], { encoding: "utf8" });
|
|
37
|
+
if (!probe || probe.status !== 0) return null;
|
|
38
|
+
return parseCpuSeconds(probe.stdout ?? "");
|
|
39
|
+
}
|
package/release-state.mjs
CHANGED
|
@@ -94,6 +94,11 @@ export function releaseEnvironment({ root, cacheRoot, platform, architecture, ap
|
|
|
94
94
|
SCCACHE_CACHE_SIZE: `${policy.sccacheMaxBytes / 1024 ** 3}G`,
|
|
95
95
|
SCCACHE_BASEDIRS: [path.resolve(root), path.resolve(appRoot)].join(path.delimiter),
|
|
96
96
|
RUSTC_WRAPPER: "sccache",
|
|
97
|
+
// Doc-mandated (architecture section 5): a dropped sccache connection must
|
|
98
|
+
// fall through to plain rustc, not fail the build. Without this, a single
|
|
99
|
+
// transient server disconnect (seen on Windows as error 10054) kills a
|
|
100
|
+
// build that would otherwise have compiled fine uncached.
|
|
101
|
+
SCCACHE_IGNORE_SERVER_IO_ERROR: "1",
|
|
97
102
|
RIGHT_RELEASE_CACHE_OWNER: "rightkit-v2",
|
|
98
103
|
};
|
|
99
104
|
}
|
package/release-state.test.mjs
CHANGED
|
@@ -105,6 +105,9 @@ test("shared release environment is isolated from the app vault", () => {
|
|
|
105
105
|
assert.match(env.CARGO_HOME, /RightSuite[\\/]release[\\/]cargo-home$/);
|
|
106
106
|
assert.equal(env.RIGHT_RELEASE_CACHE_OWNER, "rightkit-v2");
|
|
107
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");
|
|
108
111
|
});
|
|
109
112
|
|
|
110
113
|
test("shared release environment honors the cache policy override", () => {
|
package/release.mjs
CHANGED
|
File without changes
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
cargoArguments,
|
|
12
12
|
cargoExecutable,
|
|
13
13
|
isolatedCargoMetadataEnv,
|
|
14
|
+
managedCargoShimOnPath,
|
|
14
15
|
validateRightKitCargoContract,
|
|
15
16
|
} from "./cargo-contract.mjs";
|
|
16
17
|
import { assertAsrAdapterPair } from "./asr-artifact-adoption.mjs";
|
|
@@ -467,12 +468,12 @@ test("RightKit exposes one current version manifest", () => {
|
|
|
467
468
|
"@rightkit/legal": "0.3.0",
|
|
468
469
|
"@rightkit/legal-ui": "0.1.1",
|
|
469
470
|
"@rightkit/license": "0.1.6",
|
|
470
|
-
"@rightkit/release": "0.2.
|
|
471
|
+
"@rightkit/release": "0.2.64",
|
|
471
472
|
"@rightkit/qa": "0.2.0",
|
|
472
473
|
});
|
|
473
474
|
assert.deepEqual(versions.legacyNpm, {
|
|
474
475
|
"@rightkit/legal-ui": ["0.1.0"],
|
|
475
|
-
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43", "0.2.44", "0.2.45", "0.2.46", "0.2.49", "0.2.50", "0.2.51", "0.2.53", "0.2.54", "0.2.55", "0.2.56", "0.2.61"],
|
|
476
|
+
"@rightkit/release": ["0.2.22", "0.2.29", "0.2.30", "0.2.31", "0.2.41", "0.2.42", "0.2.43", "0.2.44", "0.2.45", "0.2.46", "0.2.49", "0.2.50", "0.2.51", "0.2.53", "0.2.54", "0.2.55", "0.2.56", "0.2.61", "0.2.62", "0.2.63"],
|
|
476
477
|
"@rightkit/qa": ["0.1.0"],
|
|
477
478
|
});
|
|
478
479
|
assert.ok(
|
|
@@ -498,9 +499,9 @@ test("RightKit exposes one current version manifest", () => {
|
|
|
498
499
|
getCurrentCargoVersionContract();
|
|
499
500
|
});
|
|
500
501
|
|
|
501
|
-
test("Cargo metadata uses
|
|
502
|
-
assert.equal(cargoExecutable("win32"), "
|
|
503
|
-
assert.deepEqual(cargoArguments(["metadata"], "win32"), ["
|
|
502
|
+
test("Cargo metadata uses managed Cargo directly on Windows", () => {
|
|
503
|
+
assert.equal(cargoExecutable("win32"), "cargo");
|
|
504
|
+
assert.deepEqual(cargoArguments(["metadata"], "win32"), ["metadata"]);
|
|
504
505
|
assert.equal(cargoExecutable("darwin"), "cargo");
|
|
505
506
|
});
|
|
506
507
|
|
|
@@ -518,6 +519,36 @@ test("Cargo metadata delegates controlled storage to managed RightKit", () => {
|
|
|
518
519
|
});
|
|
519
520
|
});
|
|
520
521
|
|
|
522
|
+
test("Cargo metadata recognizes managed Cargo from PATH without inherited broker environment", () => {
|
|
523
|
+
const macShim = "/Volumes/D/.rightkit-managed/agent-bin/cargo";
|
|
524
|
+
const macEnv = { PATH: `/usr/bin:${path.dirname(macShim)}`, CARGO_HOME: "" };
|
|
525
|
+
assert.equal(managedCargoShimOnPath(macEnv, "darwin", (candidate) => candidate === macShim), true);
|
|
526
|
+
assert.deepEqual(isolatedCargoMetadataEnv("/isolated/cargo", macEnv, "darwin", (candidate) => candidate === macShim), {
|
|
527
|
+
PATH: macEnv.PATH,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const windowsShim = "D:\\RightKitManagedAgent\\agent-bin\\cargo.cmd";
|
|
531
|
+
const windowsEnv = {
|
|
532
|
+
PATH: "C:\\Windows\\System32;D:\\RightKitManagedAgent\\agent-bin",
|
|
533
|
+
PATHEXT: ".EXE;.CMD",
|
|
534
|
+
};
|
|
535
|
+
assert.equal(managedCargoShimOnPath(windowsEnv, "win32", (candidate) => candidate.toLowerCase() === windowsShim.toLowerCase()), true);
|
|
536
|
+
assert.deepEqual(isolatedCargoMetadataEnv("D:\\isolated\\cargo", windowsEnv, "win32", (candidate) => candidate.toLowerCase() === windowsShim.toLowerCase()), windowsEnv);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test("Cargo metadata respects PATH precedence when unmanaged Cargo resolves first", () => {
|
|
540
|
+
const env = { PATH: "/usr/local/bin:/Volumes/D/.rightkit-managed/agent-bin" };
|
|
541
|
+
const existing = new Set([
|
|
542
|
+
"/usr/local/bin/cargo",
|
|
543
|
+
"/Volumes/D/.rightkit-managed/agent-bin/cargo",
|
|
544
|
+
]);
|
|
545
|
+
assert.equal(managedCargoShimOnPath(env, "darwin", (candidate) => existing.has(candidate)), false);
|
|
546
|
+
assert.deepEqual(isolatedCargoMetadataEnv("/isolated/cargo", env, "darwin", (candidate) => existing.has(candidate)), {
|
|
547
|
+
PATH: env.PATH,
|
|
548
|
+
CARGO_HOME: "/isolated/cargo",
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
521
552
|
test("license v2 public vector is identical at every portable consumer boundary", () => {
|
|
522
553
|
const canonical = readFileSync(
|
|
523
554
|
path.join(workspace, "tools/rightkit/crates/rightkit-license/test-vectors/license-v2.json"),
|
package/rightkit-versions.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@rightkit/legal": "0.3.0",
|
|
20
20
|
"@rightkit/legal-ui": "0.1.1",
|
|
21
21
|
"@rightkit/license": "0.1.6",
|
|
22
|
-
"@rightkit/release": "0.2.
|
|
22
|
+
"@rightkit/release": "0.2.64",
|
|
23
23
|
"@rightkit/qa": "0.2.0"
|
|
24
24
|
},
|
|
25
25
|
"legacyNpm": {
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
"0.2.54",
|
|
45
45
|
"0.2.55",
|
|
46
46
|
"0.2.56",
|
|
47
|
-
"0.2.61"
|
|
47
|
+
"0.2.61",
|
|
48
|
+
"0.2.62",
|
|
49
|
+
"0.2.63"
|
|
48
50
|
],
|
|
49
51
|
"@rightkit/qa": [
|
|
50
52
|
"0.1.0"
|
package/target-bridge.mjs
CHANGED
|
@@ -51,7 +51,7 @@ export function createTargetBridge({
|
|
|
51
51
|
}
|
|
52
52
|
mkdir(target, { recursive: true });
|
|
53
53
|
if (!entry) {
|
|
54
|
-
symlink(target, link,
|
|
54
|
+
symlink(target, link, "dir");
|
|
55
55
|
entry = lstat(link);
|
|
56
56
|
if (!entry.isSymbolicLink()) throw new Error(`RightKit target bridge was not created as a symbolic link: ${link}`);
|
|
57
57
|
if (!sameRealPath(link, target, realpath, platform)) {
|
|
@@ -64,7 +64,7 @@ export function createTargetBridge({
|
|
|
64
64
|
// Stale link from an earlier fingerprint, or one whose cache entry a
|
|
65
65
|
// prune already removed. Both are ours to repoint.
|
|
66
66
|
unlink(link);
|
|
67
|
-
symlink(target, link,
|
|
67
|
+
symlink(target, link, "dir");
|
|
68
68
|
entry = lstat(link);
|
|
69
69
|
if (!sameRealPath(link, target, realpath, platform)) {
|
|
70
70
|
throw new Error(`RightKit target bridge did not repoint to the owned shared cache target: ${link}`);
|
package/target-bridge.test.mjs
CHANGED
|
@@ -27,6 +27,23 @@ test("removes an invocation-created bridge after success without touching the sh
|
|
|
27
27
|
assert.equal(readFileSync(path.join(fx.target, "keep.txt"), "utf8"), "shared-cache\n");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("Windows creates a directory symlink instead of an untrusted junction", () => {
|
|
31
|
+
const fx = fixture();
|
|
32
|
+
let requestedType;
|
|
33
|
+
const bridge = createTargetBridge({
|
|
34
|
+
...fx,
|
|
35
|
+
platform: "win32",
|
|
36
|
+
symlink(target, link, type) {
|
|
37
|
+
requestedType = type;
|
|
38
|
+
symlinkSync(target, link, type);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
bridge.ensure();
|
|
42
|
+
assert.equal(requestedType, "dir");
|
|
43
|
+
assert.equal(realpathSync(fx.link), realpathSync(fx.target));
|
|
44
|
+
bridge.release();
|
|
45
|
+
});
|
|
46
|
+
|
|
30
47
|
test("removes an invocation-created bridge after the build throws", async () => {
|
|
31
48
|
const fx = fixture();
|
|
32
49
|
const bridge = createTargetBridge(fx);
|
package/upload-large.mjs
CHANGED
|
File without changes
|