@rightkit/release 0.2.69 → 0.2.71
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 +19 -14
- package/cache-command.mjs +7 -4
- package/cli/right-release.mjs +0 -0
- package/native-cargo-layout.mjs +239 -0
- package/native-release-finalization.mjs +259 -0
- package/package.json +11 -11
- package/preflight.mjs +6 -3
- package/registry-parity.mjs +2 -2
- package/release-invocation.mjs +10 -2
- package/release-state.mjs +5 -2
- package/release.mjs +220 -12
- package/rightkit-versions.json +4 -2
- package/addon-command.test.mjs +0 -54
- package/addon-contract.test.mjs +0 -48
- package/asr-artifact-adoption.test.mjs +0 -122
- package/build-invocation-contract.test.mjs +0 -124
- package/build-release.test.mjs +0 -242
- package/cache-command.test.mjs +0 -118
- package/cache-policy.test.mjs +0 -346
- package/cargo-guard.test.mjs +0 -195
- package/cargo-target.test.mjs +0 -82
- package/create-mac-updater.test.mjs +0 -14
- package/github-release.test.mjs +0 -103
- package/heavy-command.test.mjs +0 -221
- package/legal-contract.test.mjs +0 -151
- package/mirror-root-artifact.test.mjs +0 -58
- package/model-promote.test.mjs +0 -284
- package/notary-auth.test.mjs +0 -31
- package/nsis-payload.test.mjs +0 -139
- package/nsis-upgrade-contract.test.mjs +0 -57
- package/pipeline-normalization-contract.test.mjs +0 -140
- package/preflight.test.mjs +0 -123
- package/progress-control.test.mjs +0 -56
- package/prune-r2.test.mjs +0 -12
- package/publish-cargo.test.mjs +0 -43
- package/publish-swift.test.mjs +0 -44
- package/publish-update.test.mjs +0 -207
- package/qa-contract.test.mjs +0 -47
- package/registry-parity.test.mjs +0 -30
- package/release-cli-contract.test.mjs +0 -119
- package/release-invocation.test.mjs +0 -22
- package/release-state.test.mjs +0 -395
- package/release-token.test.mjs +0 -21
- package/release.test.mjs +0 -533
- package/right-suite-contract.test.mjs +0 -1011
- package/rightapps-register.test.mjs +0 -28
- package/runtime-artifact-manifest.test.mjs +0 -128
- package/sign-updater.test.mjs +0 -12
- package/source-gate.test.mjs +0 -25
- package/standalone-clone-evidence.json +0 -32
- package/standalone-clone-verify.test.mjs +0 -76
- package/suite-doctor.test.mjs +0 -19
- package/target-bridge.test.mjs +0 -269
- package/tauri-bundle-marker.test.mjs +0 -81
- package/upload-large.test.mjs +0 -70
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { generateKeyPairSync } from "node:crypto";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
|
|
5
|
-
import { assertAsrAdapterPair, assertAsrArtifactAdoption } from "./asr-artifact-adoption.mjs";
|
|
6
|
-
import { buildRuntimeArtifactManifest, signRuntimeArtifactManifest } from "./runtime-artifact-manifest.mjs";
|
|
7
|
-
|
|
8
|
-
const digest = "a".repeat(64);
|
|
9
|
-
const notice = "b".repeat(64);
|
|
10
|
-
const evidence = "c".repeat(64);
|
|
11
|
-
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
12
|
-
|
|
13
|
-
function envelope(appKey, overrides = {}) {
|
|
14
|
-
const manifest = buildRuntimeArtifactManifest({
|
|
15
|
-
artifactKind: "asr-model",
|
|
16
|
-
entitlement: { appKey, tier: "pro" },
|
|
17
|
-
distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
|
|
18
|
-
target: { os: "windows", arch: "x86_64" },
|
|
19
|
-
filename: "parakeet-unified-en-0.6b.tar.zst",
|
|
20
|
-
sha256: digest,
|
|
21
|
-
sizeBytes: 1234,
|
|
22
|
-
versions: {
|
|
23
|
-
runtime: "rightkit-asr@0.1.0",
|
|
24
|
-
model: "parakeet-unified-en-0.6b",
|
|
25
|
-
tokenizer: "sha256:tokenizer-v1",
|
|
26
|
-
preprocessing: "heardright-frontend-v1",
|
|
27
|
-
license: "CC-BY-4.0",
|
|
28
|
-
provenance: "nvidia/parakeet-tdt-0.6b-v3",
|
|
29
|
-
},
|
|
30
|
-
provenance: {
|
|
31
|
-
source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
|
|
32
|
-
sourceRevision: "revision-1",
|
|
33
|
-
licenseId: "CC-BY-4.0",
|
|
34
|
-
noticeSha256: notice,
|
|
35
|
-
},
|
|
36
|
-
promotion: {
|
|
37
|
-
authorityAppKey: "heardright",
|
|
38
|
-
promotionId: "hr-asr-2026-07-14",
|
|
39
|
-
promotedAt: "2026-07-14T00:00:00.000Z",
|
|
40
|
-
evidenceSha256: evidence,
|
|
41
|
-
},
|
|
42
|
-
...overrides,
|
|
43
|
-
});
|
|
44
|
-
return signRuntimeArtifactManifest(manifest, privateKey, "rightkit-runtime-artifacts-2026-07");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
test("accepts app-scoped pointers with one exact HeardRight-certified ASR identity", () => {
|
|
48
|
-
const result = assertAsrArtifactAdoption({
|
|
49
|
-
authorityEnvelope: envelope("heardright"),
|
|
50
|
-
consumerEnvelope: envelope("scraperight"),
|
|
51
|
-
publicKey,
|
|
52
|
-
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
assert.equal(result.sha256, digest);
|
|
56
|
-
assert.equal(result.promotionId, "hr-asr-2026-07-14");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("rejects a validly signed ScrapeRight manifest with ASR drift", () => {
|
|
60
|
-
const drifted = envelope("scraperight", {
|
|
61
|
-
versions: {
|
|
62
|
-
runtime: "rightkit-asr@0.1.1",
|
|
63
|
-
model: "parakeet-unified-en-0.6b",
|
|
64
|
-
tokenizer: "sha256:tokenizer-v1",
|
|
65
|
-
preprocessing: "heardright-frontend-v1",
|
|
66
|
-
license: "CC-BY-4.0",
|
|
67
|
-
provenance: "nvidia/parakeet-tdt-0.6b-v3",
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
assert.throws(() => assertAsrArtifactAdoption({
|
|
72
|
-
authorityEnvelope: envelope("heardright"),
|
|
73
|
-
consumerEnvelope: drifted,
|
|
74
|
-
publicKey,
|
|
75
|
-
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
76
|
-
}), /ASR drift: versions\.runtime/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("rejects wrong roles, authority, target, signature key, and artifact kind", () => {
|
|
80
|
-
const base = {
|
|
81
|
-
authorityEnvelope: envelope("heardright"),
|
|
82
|
-
consumerEnvelope: envelope("scraperight"),
|
|
83
|
-
publicKey,
|
|
84
|
-
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
85
|
-
};
|
|
86
|
-
assert.throws(() => assertAsrArtifactAdoption({ ...base, authorityEnvelope: envelope("scraperight") }), /authority manifest must belong to heardright/);
|
|
87
|
-
assert.throws(() => assertAsrArtifactAdoption({ ...base, consumerEnvelope: envelope("heardright") }), /consumer manifest must belong to scraperight/);
|
|
88
|
-
assert.throws(() => assertAsrArtifactAdoption({ ...base, trustedKeyId: "rotated-without-app-update" }), /untrusted runtime artifact key id/);
|
|
89
|
-
assert.throws(() => assertAsrArtifactAdoption({
|
|
90
|
-
...base,
|
|
91
|
-
consumerEnvelope: envelope("scraperight", { target: { os: "darwin", arch: "aarch64" } }),
|
|
92
|
-
}), /ASR drift: target/);
|
|
93
|
-
assert.throws(() => assertAsrArtifactAdoption({
|
|
94
|
-
...base,
|
|
95
|
-
consumerEnvelope: envelope("scraperight", { artifactKind: "ocr-model" }),
|
|
96
|
-
}), /must be an asr-model/);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("rejects a consumer signed by a different key", () => {
|
|
100
|
-
const other = generateKeyPairSync("ed25519");
|
|
101
|
-
const consumer = signRuntimeArtifactManifest(
|
|
102
|
-
envelope("scraperight").manifest,
|
|
103
|
-
other.privateKey,
|
|
104
|
-
"rightkit-runtime-artifacts-2026-07",
|
|
105
|
-
);
|
|
106
|
-
assert.throws(() => assertAsrArtifactAdoption({
|
|
107
|
-
authorityEnvelope: envelope("heardright"),
|
|
108
|
-
consumerEnvelope: consumer,
|
|
109
|
-
publicKey,
|
|
110
|
-
trustedKeyId: "rightkit-runtime-artifacts-2026-07",
|
|
111
|
-
}), /signature verification failed/);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("locks the thin app adapters to HeardRight authority and private Pro delivery", () => {
|
|
115
|
-
const authority = {
|
|
116
|
-
schema: 1, appKey: "heardright", role: "authority", authorityAppKey: "heardright",
|
|
117
|
-
artifactKind: "asr-model", delivery: "private-r2", entitlementTier: "pro",
|
|
118
|
-
};
|
|
119
|
-
const consumer = { ...authority, appKey: "scraperight", role: "consumer" };
|
|
120
|
-
assert.equal(assertAsrAdapterPair(authority, consumer), true);
|
|
121
|
-
assert.throws(() => assertAsrAdapterPair(authority, { ...consumer, authorityAppKey: "scraperight" }), /ASR drift/);
|
|
122
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
|
|
8
|
-
import { assertPrimaryReleaseCheckout, dirtyBuildInputs } from "./release-invocation.mjs";
|
|
9
|
-
|
|
10
|
-
function git(cwd, ...args) {
|
|
11
|
-
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function makeRepo() {
|
|
15
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "right-release-invocation-"));
|
|
16
|
-
git(root, "init", "--initial-branch", "main");
|
|
17
|
-
git(root, "config", "user.email", "release-test@example.com");
|
|
18
|
-
git(root, "config", "user.name", "Release Test");
|
|
19
|
-
mkdirSync(path.join(root, "src"));
|
|
20
|
-
mkdirSync(path.join(root, "tests"));
|
|
21
|
-
mkdirSync(path.join(root, "docs"));
|
|
22
|
-
writeFileSync(path.join(root, "package.json"), "{}\n");
|
|
23
|
-
writeFileSync(path.join(root, "src", "app.js"), "export const app = true;\n");
|
|
24
|
-
writeFileSync(path.join(root, "tests", "app.test.js"), "// test\n");
|
|
25
|
-
writeFileSync(path.join(root, "docs", "notes.md"), "notes\n");
|
|
26
|
-
git(root, "add", ".");
|
|
27
|
-
git(root, "commit", "-m", "initial");
|
|
28
|
-
return root;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const buildInputs = {
|
|
32
|
-
include: ["package.json", "src/**"],
|
|
33
|
-
exclude: ["**/*.test.*", "**/tests/**"],
|
|
34
|
-
json: {
|
|
35
|
-
"package.json": [["dependencies"], ["scripts", "build"]],
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
test("rejects release invocation from a linked Git worktree", () => {
|
|
40
|
-
const primary = makeRepo();
|
|
41
|
-
const linked = path.join(path.dirname(primary), `${path.basename(primary)}-linked`);
|
|
42
|
-
git(primary, "worktree", "add", "--detach", linked);
|
|
43
|
-
|
|
44
|
-
assert.throws(
|
|
45
|
-
() => assertPrimaryReleaseCheckout(linked),
|
|
46
|
-
/must be invoked from the primary Git worktree/i,
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("allows release invocation from the primary Git checkout", () => {
|
|
51
|
-
const primary = makeRepo();
|
|
52
|
-
|
|
53
|
-
assert.doesNotThrow(() => assertPrimaryReleaseCheckout(primary));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("rejects a detached HEAD even in the primary Git checkout", () => {
|
|
57
|
-
const primary = makeRepo();
|
|
58
|
-
git(primary, "checkout", "--detach");
|
|
59
|
-
|
|
60
|
-
assert.throws(
|
|
61
|
-
() => assertPrimaryReleaseCheckout(primary),
|
|
62
|
-
/detached HEAD is forbidden/i,
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("reports a dirty tracked file declared as a build input", () => {
|
|
67
|
-
const primary = makeRepo();
|
|
68
|
-
writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
|
|
69
|
-
|
|
70
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["src/app.js"]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("reports an untracked file declared as a build input", () => {
|
|
74
|
-
const primary = makeRepo();
|
|
75
|
-
writeFileSync(path.join(primary, "src", "new-runtime.js"), "export {};\n");
|
|
76
|
-
|
|
77
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["src/new-runtime.js"]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("ignores dirty files outside the packaged app input set", () => {
|
|
81
|
-
const primary = makeRepo();
|
|
82
|
-
writeFileSync(path.join(primary, "docs", "notes.md"), "changed notes\n");
|
|
83
|
-
writeFileSync(path.join(primary, "tests", "app.test.js"), "// changed test\n");
|
|
84
|
-
writeFileSync(path.join(primary, "scratch.txt"), "evaluation output\n");
|
|
85
|
-
|
|
86
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs), []);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("required release metadata cannot be hidden by build-input exclusions", () => {
|
|
90
|
-
const primary = makeRepo();
|
|
91
|
-
writeFileSync(path.join(primary, "package.json"), "{\"version\":\"2.0.0\"}\n");
|
|
92
|
-
|
|
93
|
-
assert.deepEqual(dirtyBuildInputs(primary, {
|
|
94
|
-
...buildInputs,
|
|
95
|
-
exclude: [...buildInputs.exclude, "package.json"],
|
|
96
|
-
required: ["package.json"],
|
|
97
|
-
}), ["package.json"]);
|
|
98
|
-
assert.deepEqual(dirtyBuildInputs(primary, {
|
|
99
|
-
include: ["package.json"],
|
|
100
|
-
required: ["package.json"],
|
|
101
|
-
}), ["package.json"]);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("checks only build-relevant fields in mixed-purpose JSON manifests", () => {
|
|
105
|
-
const primary = makeRepo();
|
|
106
|
-
writeFileSync(path.join(primary, "package.json"), `${JSON.stringify({ scripts: { bakeoff: "node eval.mjs" } })}\n`);
|
|
107
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs), []);
|
|
108
|
-
|
|
109
|
-
writeFileSync(path.join(primary, "package.json"), `${JSON.stringify({ dependencies: { tauri: "2.0.0" } })}\n`);
|
|
110
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["package.json"]);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("does not report a locally dirty file whose bytes already equal the release commit", () => {
|
|
114
|
-
const primary = makeRepo();
|
|
115
|
-
const localHead = git(primary, "rev-parse", "HEAD");
|
|
116
|
-
writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
|
|
117
|
-
git(primary, "add", "src/app.js");
|
|
118
|
-
git(primary, "commit", "-m", "release change");
|
|
119
|
-
const releaseCommit = git(primary, "rev-parse", "HEAD");
|
|
120
|
-
git(primary, "checkout", localHead);
|
|
121
|
-
writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
|
|
122
|
-
|
|
123
|
-
assert.deepEqual(dirtyBuildInputs(primary, buildInputs, releaseCommit), []);
|
|
124
|
-
});
|
package/build-release.test.mjs
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { parseCpuSeconds, processGroupCpuSeconds } from "./process-liveness.mjs";
|
|
9
|
-
import { brokerManagedHost } from "./cargo-contract.mjs";
|
|
10
|
-
|
|
11
|
-
const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs"), "utf8");
|
|
12
|
-
const build = path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs");
|
|
13
|
-
|
|
14
|
-
test("general build dry-run is filesystem-pure and never starts its package command", () => {
|
|
15
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "right-build-dry-run-"));
|
|
16
|
-
writeFileSync(path.join(root, "right-release.config.mjs"), "export default { app: 'fixture', version: '1.0.0', targets: { mac: { package: { cmd: 'false' } } } };\n");
|
|
17
|
-
for (const args of [["init", "--initial-branch", "main"], ["config", "user.email", "test@example.com"], ["config", "user.name", "Test"], ["add", "."], ["commit", "-m", "init"]]) execFileSync("git", args, { cwd: root });
|
|
18
|
-
const before = readdirSync(root).sort();
|
|
19
|
-
const result = spawnSync(process.execPath, [build, "--config", "right-release.config.mjs", "--platform", "mac", "--dry-run"], { cwd: root, encoding: "utf8" });
|
|
20
|
-
assert.equal(result.status, 0, result.stderr);
|
|
21
|
-
assert.match(result.stdout, /build dry-run/);
|
|
22
|
-
assert.deepEqual(readdirSync(root).sort(), before);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("macOS and Windows builds default to Cache V2 while explicit legacy mode remains available", () => {
|
|
26
|
-
assert.match(source, /assertCleanSource/);
|
|
27
|
-
assert.match(source, /right-release build/);
|
|
28
|
-
assert.match(source, /RIGHT_RELEASE_CACHE_MODE/);
|
|
29
|
-
assert.match(source, /platform === "mac" \|\| platform === "win" \? "shared" : "legacy"/);
|
|
30
|
-
assert.match(source, /cacheModeRequested !== "legacy" && cacheModeRequested !== "shared"/);
|
|
31
|
-
assert.match(source, /resolveSharedCacheRoot/);
|
|
32
|
-
assert.match(source, /acquireSuiteBuildSlot/);
|
|
33
|
-
assert.match(source, /acquireHeavyWorkSlot/);
|
|
34
|
-
assert.match(source, /heavyCommandEnvironment/);
|
|
35
|
-
assert.match(source, /heavySlot\.trackChild/);
|
|
36
|
-
assert.match(source, /detached: process\.platform !== "win32"/);
|
|
37
|
-
assert.match(source, /acquireCacheLease/);
|
|
38
|
-
assert.match(source, /markCacheEntrySuccessful/);
|
|
39
|
-
assert.match(source, /RIGHT_RELEASE_CACHE_OWNER/);
|
|
40
|
-
assert.match(source, /suiteSlot\?\.release\(\)/);
|
|
41
|
-
assert.match(source, /heavySlot\?\.release\(\)/);
|
|
42
|
-
assert.match(source, /cacheLease\?\.release\(\)/);
|
|
43
|
-
assert.doesNotMatch(source, /process\.exit\(signal/);
|
|
44
|
-
assert.match(source, /throwIfInterrupted/);
|
|
45
|
-
assert.match(source, /delete env\.RIGHT_RELEASE_CACHE_OWNER/);
|
|
46
|
-
assert.match(source, /assertPrimaryReleaseCheckout\(repoRoot\)/);
|
|
47
|
-
assert.match(source, /dirtyBuildInputs\(repoRoot/);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("production builds package from the real primary app checkout", () => {
|
|
51
|
-
assert.match(source, /const configPath = path\.resolve\(configName\)/);
|
|
52
|
-
assert.match(source, /const appRoot = path\.dirname\(configPath\)/);
|
|
53
|
-
assert.match(source, /\[WORKER, "--config", configPath, "--platform", platform, "--no-upload"\]/);
|
|
54
|
-
assert.match(source, /sealRelease\(\{ configRoot: appRoot,/);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("Windows native executables preserve arguments containing spaces", () => {
|
|
58
|
-
assert.match(source, /const useShell = process\.platform === "win32" && !\/\\\.\(\?:exe\|com\)\$\/i\.test\(cmd\);/);
|
|
59
|
-
assert.match(source, /shell: useShell/);
|
|
60
|
-
assert.doesNotMatch(source, /shell: process\.platform === "win32"/);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("sealing resolves installer and updater artifacts against the broker target on a managed host", () => {
|
|
64
|
-
assert.match(source, /sealRelease\(\{ configRoot: appRoot, managedCargoTarget,/);
|
|
65
|
-
assert.match(source, /function sealRelease\(\{ configRoot, managedCargoTarget,/);
|
|
66
|
-
assert.match(source, /const targetPrefix = "src-tauri\/target\/";/);
|
|
67
|
-
assert.match(source, /if \(managedCargoTarget && normalized\.startsWith\(targetPrefix\)\) \{/);
|
|
68
|
-
// Every configRoot-relative artifact lookup inside sealRelease must route
|
|
69
|
-
// through the resolver, or a managed build fails at seal after a full
|
|
70
|
-
// compile, sign, and notarize because src-tauri/target is never populated
|
|
71
|
-
// locally on a broker-managed host (target-bridge.mjs leaves it alone).
|
|
72
|
-
assert.doesNotMatch(source, /addSource\(path\.resolve\(configRoot, artifact\.file\)/);
|
|
73
|
-
assert.doesNotMatch(source, /addSource\(path\.resolve\(configRoot, artifact\.signature\)/);
|
|
74
|
-
assert.match(source, /addSource\(resolveArtifactPath\(artifact\.file\), "installer"\)/);
|
|
75
|
-
assert.match(source, /addSource\(resolveArtifactPath\(artifact\.file\), "updater"\)/);
|
|
76
|
-
assert.match(source, /addSource\(resolveArtifactPath\(artifact\.signature\), "updater-signature"\)/);
|
|
77
|
-
assert.match(source, /const source = resolveArtifactPath\(artifact\.file\);/);
|
|
78
|
-
assert.match(source, /const signatureSource = resolveArtifactPath\(artifact\.signature\);/);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("production builds never create or use an internal Git worktree", () => {
|
|
82
|
-
assert.doesNotMatch(source, /git", \["worktree", "add"/);
|
|
83
|
-
assert.doesNotMatch(source, /path\.join\(vaultRoot, "worktrees"/);
|
|
84
|
-
assert.doesNotMatch(source, /worktreeConfigPath/);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("dirty release config is rejected before the config module is imported", () => {
|
|
88
|
-
const guard = source.indexOf("dirtyBuildInputs(repoRoot, { include: [relativeConfig], required: [relativeConfig] }, commit)");
|
|
89
|
-
const imported = source.indexOf("await import(");
|
|
90
|
-
assert.ok(guard >= 0, "config cleanliness guard must exist");
|
|
91
|
-
assert.ok(guard < imported, "config cleanliness guard must run before module import");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("shared Cache V2 target bridge is owned by a finally-cleaned lifecycle", () => {
|
|
95
|
-
assert.match(source, /createTargetBridge\(\{\s*link: targetLink,\s*target: managedCargoTarget \?\? env\.CARGO_TARGET_DIR,/);
|
|
96
|
-
// ownedRoot must stay wired: without it the bridge refuses every link left by
|
|
97
|
-
// an earlier fingerprint, so any Cargo.lock or version bump hard-stops the
|
|
98
|
-
// next build until someone deletes the link by hand.
|
|
99
|
-
assert.match(source, /ownedRoot: path\.dirname\(managedCargoTarget \?\? env\.CARGO_TARGET_DIR\)/);
|
|
100
|
-
assert.match(source, /targetBridge\.run\(.*runBuildStateMachine/s);
|
|
101
|
-
// A broker-managed host also routes through the bridge even though cacheMode
|
|
102
|
-
// is forced to "broker" there — its brokerManaged flag is what keeps a real
|
|
103
|
-
// (non-symlink) target directory from being treated as corruption.
|
|
104
|
-
assert.match(source, /if \(cacheMode === "shared" \|\| managedCargoTarget\) targetBridge\.ensure\(\)/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("a broker-managed host forces broker cacheMode so Cache V2's lease/prune/floor chain never runs against an unrelated volume", () => {
|
|
108
|
-
assert.match(
|
|
109
|
-
source,
|
|
110
|
-
/const cacheMode = managedCargoTarget \? "broker" : cacheModeRequested;/,
|
|
111
|
-
);
|
|
112
|
-
// Every Cache V2 lease/slot/prune/floor/mark call stays gated on cacheMode,
|
|
113
|
-
// never on cacheModeRequested directly, so forcing cacheMode to "broker" on a
|
|
114
|
-
// broker host is sufficient to skip all of them without a second gate.
|
|
115
|
-
for (const guarded of [
|
|
116
|
-
/acquireSuiteBuildSlot\(\{ layout: sharedLayout/,
|
|
117
|
-
/cacheLease = acquireCacheLease\(\{ layout: sharedLayout/,
|
|
118
|
-
/planCachePrune\(\{ snapshot, policy, protectedEntryIds: new Set\(\[sharedLayout\.id\]\) \}\)/,
|
|
119
|
-
/assertWriteVolumeFloors\(\{ volumes, policy,/,
|
|
120
|
-
/markCacheEntrySuccessful\(\{ layout: sharedLayout \}\)/,
|
|
121
|
-
]) {
|
|
122
|
-
assert.match(source, guarded);
|
|
123
|
-
}
|
|
124
|
-
assert.doesNotMatch(source, /if \(cacheModeRequested === "shared"\) \{\s*\n\s*suiteSlot/);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("Windows seal and cache identity bind the signing contract, config, and receipts", () => {
|
|
128
|
-
assert.match(source, /signingIdentity = platform === "win"/);
|
|
129
|
-
assert.match(source, /contract: target\.signingContract/);
|
|
130
|
-
assert.match(source, /configSha256: hashFile\(configPath\)/);
|
|
131
|
-
assert.match(source, /windows-\$\{phase\}\.json/);
|
|
132
|
-
assert.match(source, /inputHashes\["\.right-release\/signing-identity\.json"\]/);
|
|
133
|
-
assert.match(source, /cache: cacheIdentity\.fingerprint, signingIdentity/);
|
|
134
|
-
assert.match(source, /receipts: signingIdentity\.receiptInputs\.map/);
|
|
135
|
-
assert.match(readFileSync(new URL("./release.mjs", import.meta.url), "utf8"), /item\.after\?\.sha256 !== currentHashes/);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("CPU sampling separates a silent working step from a hang", () => {
|
|
139
|
-
// Real `ps -o cputime=` shapes, including a multi-process group.
|
|
140
|
-
assert.equal(parseCpuSeconds(" 0:03.21\n"), 3.21);
|
|
141
|
-
assert.equal(parseCpuSeconds(" 1:02:03\n"), 3723);
|
|
142
|
-
assert.equal(parseCpuSeconds(" 2-01:00:00\n"), 176400);
|
|
143
|
-
// A process group sums every member, which is how a linker under cargo shows up.
|
|
144
|
-
assert.equal(parseCpuSeconds(" 0:10.00\n 0:05.00\n"), 15);
|
|
145
|
-
// A vanished process yields no rows: null, not zero, so the caller falls back
|
|
146
|
-
// to output and mtime rather than treating it as "no CPU used".
|
|
147
|
-
assert.equal(parseCpuSeconds(""), null);
|
|
148
|
-
assert.equal(parseCpuSeconds(" \n"), null);
|
|
149
|
-
assert.equal(parseCpuSeconds("garbage"), null);
|
|
150
|
-
assert.equal(parseCpuSeconds(undefined), null);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("CPU sampling degrades to the old behaviour rather than inventing a verdict", () => {
|
|
154
|
-
const ok = (stdout) => () => ({ status: 0, stdout });
|
|
155
|
-
// A working process group reports advancing CPU.
|
|
156
|
-
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok(" 0:42.50\n") }), 42.5);
|
|
157
|
-
// Windows has no `ps`; sampling is skipped, not guessed.
|
|
158
|
-
assert.equal(processGroupCpuSeconds(123, { platform: "win32", run: ok(" 0:42.50\n") }), null);
|
|
159
|
-
// No pid, a failed probe, or an empty result must all be "cannot measure",
|
|
160
|
-
// never 0 — 0 would look like a hang and kill a healthy build.
|
|
161
|
-
assert.equal(processGroupCpuSeconds(0, { platform: "darwin", run: ok(" 0:42.50\n") }), null);
|
|
162
|
-
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: () => ({ status: 1, stdout: "" }) }), null);
|
|
163
|
-
assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok("") }), null);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("package liveness owner timeout floors outer watchdog through shared release limits", () => {
|
|
167
|
-
assert.match(source, /livenessOwnerTimeoutMs: target\.package\.livenessOwnerTimeoutMs \?\? null/);
|
|
168
|
-
assert.match(source, /releaseProgressLimits\(env, \{ livenessOwnerTimeoutMs \}\)/);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Regression: the managed-host gate must not depend on RIGHTKIT_BUILD_BROKER_SOCKET
|
|
172
|
-
// alone. That variable is exported by login shells only, while the managed cargo
|
|
173
|
-
// shim is on PATH in every shell — so an agent shell took the Cache V2 branch,
|
|
174
|
-
// bridged src-tauri/target into the release cache, and then lost CARGO_TARGET_DIR
|
|
175
|
-
// to the broker's unconditional override. The app built into the broker workspace
|
|
176
|
-
// and packaging failed on an empty cache before notarize/staple.
|
|
177
|
-
const managedHostDetected = (env, platform, exists) => brokerManagedHost(env, platform, exists);
|
|
178
|
-
|
|
179
|
-
test("managed-host gate fires from the managed shim on PATH when no broker socket is exported", () => {
|
|
180
|
-
// The build gate is exactly the shared brokerManagedHost predicate cargo-contract.mjs exports,
|
|
181
|
-
// so there is exactly one definition of "is this a broker-managed host" in the codebase.
|
|
182
|
-
assert.match(
|
|
183
|
-
source,
|
|
184
|
-
/const managedCargoTarget = brokerManagedHost\(process\.env\) \? resolveTargetRoot\(cargoToml\) : null;/,
|
|
185
|
-
);
|
|
186
|
-
assert.match(source, /import \{ brokerManagedHost \} from "\.\/cargo-contract\.mjs";/);
|
|
187
|
-
assert.match(source, /import \{ resolveTargetRoot \} from "\.\/cargo-target\.mjs";/);
|
|
188
|
-
|
|
189
|
-
const shim = "/Volumes/D/.rightkit-managed/agent-bin/cargo";
|
|
190
|
-
const exists = (candidate) => candidate === shim;
|
|
191
|
-
|
|
192
|
-
// Agent (non-login) shell: no socket variable, managed shim first on PATH.
|
|
193
|
-
assert.equal(managedHostDetected({ PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists), true);
|
|
194
|
-
// Login shell: unchanged behaviour, with or without the shim resolvable.
|
|
195
|
-
assert.equal(
|
|
196
|
-
managedHostDetected({ PATH: `${path.dirname(shim)}:/usr/bin`, RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" }, "darwin", exists),
|
|
197
|
-
true,
|
|
198
|
-
);
|
|
199
|
-
assert.equal(managedHostDetected({ PATH: "/usr/bin", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" }, "darwin", () => false), true);
|
|
200
|
-
// Unmanaged host: neither signal, so the Cache V2 branch still owns the build.
|
|
201
|
-
assert.equal(managedHostDetected({ PATH: "/usr/bin:/usr/local/bin" }, "darwin", () => false), false);
|
|
202
|
-
// PATH precedence still decides: an unmanaged toolchain ahead of the shim is unmanaged.
|
|
203
|
-
const both = new Set(["/usr/local/bin/cargo", shim]);
|
|
204
|
-
assert.equal(managedHostDetected({ PATH: `/usr/local/bin:${path.dirname(shim)}` }, "darwin", (c) => both.has(c)), false);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("brokerManagedHost is the single shared predicate: env var alone, shim alone, both, or neither", () => {
|
|
208
|
-
const shim = "/Volumes/D/.rightkit-managed/agent-bin/cargo";
|
|
209
|
-
const exists = (candidate) => candidate === shim;
|
|
210
|
-
|
|
211
|
-
// Broker socket alone (login shell), no shim resolvable.
|
|
212
|
-
assert.equal(brokerManagedHost({ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", PATH: "/usr/bin" }, "darwin", () => false), true);
|
|
213
|
-
// Shim on PATH alone (agent/non-login shell), no socket exported.
|
|
214
|
-
assert.equal(brokerManagedHost({ PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists), true);
|
|
215
|
-
// Both signals present.
|
|
216
|
-
assert.equal(
|
|
217
|
-
brokerManagedHost({ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists),
|
|
218
|
-
true,
|
|
219
|
-
);
|
|
220
|
-
// Neither signal: unmanaged host.
|
|
221
|
-
assert.equal(brokerManagedHost({ PATH: "/usr/bin:/usr/local/bin" }, "darwin", () => false), false);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("the managed branch hands the Rust target directory to the broker while the unmanaged branch keeps Cache V2", () => {
|
|
225
|
-
// Managed: CARGO_TARGET_DIR (and the rest of the Cache V2 environment) is deleted
|
|
226
|
-
// from the child environment, so the broker's own override is the only one.
|
|
227
|
-
assert.match(
|
|
228
|
-
source,
|
|
229
|
-
/if \(managedCargoTarget\) \{\n\s*for \(const name of \["CARGO_HOME", "CARGO_TARGET_DIR", "RUSTC_WRAPPER", "SCCACHE_DIR", "SCCACHE_BASEDIRS", "SCCACHE_CACHE_SIZE", "RUSTFLAGS"\]\) \{\n\s*delete env\[name\];/,
|
|
230
|
-
);
|
|
231
|
-
// Managed: no vault build root, and the bridge points at the metadata-reported target.
|
|
232
|
-
assert.match(source, /const buildRoot = managedCargoTarget \? null :/);
|
|
233
|
-
assert.match(source, /target: managedCargoTarget \?\? env\.CARGO_TARGET_DIR,/);
|
|
234
|
-
assert.match(source, /ownedRoot: path\.dirname\(managedCargoTarget \?\? env\.CARGO_TARGET_DIR\),/);
|
|
235
|
-
// The managed target is read from crate metadata through the shim, which is what
|
|
236
|
-
// makes the bridge target equal to the directory the broker actually builds into.
|
|
237
|
-
// resolveTargetRoot (cargo-target.mjs) is the single shared implementation —
|
|
238
|
-
// build-release.mjs no longer defines its own copy.
|
|
239
|
-
assert.doesNotMatch(source, /function resolveManagedCargoTarget/);
|
|
240
|
-
// Unmanaged (managedCargoTarget === null): Cache V2 still supplies CARGO_TARGET_DIR.
|
|
241
|
-
assert.match(source, /\.\.\.releaseEnvironment\(\{ root: vaultRoot, cacheRoot: sharedCacheRoot,/);
|
|
242
|
-
});
|
package/cache-command.test.mjs
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, symlinkSync, 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
|
-
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const cli = path.join(root, "cli", "right-release.mjs");
|
|
11
|
-
|
|
12
|
-
// This suite must exercise the non-broker cache-command path deterministically,
|
|
13
|
-
// regardless of whether the machine running the tests is itself broker-managed
|
|
14
|
-
// (this workspace's own agent shells are: the managed cargo shim sits on PATH).
|
|
15
|
-
// Strip both broker signals from the inherited environment so "unmanaged host"
|
|
16
|
-
// tests stay unmanaged; individual tests opt back into broker signals explicitly.
|
|
17
|
-
function unmanagedEnv(base = process.env) {
|
|
18
|
-
const sanitized = { ...base };
|
|
19
|
-
delete sanitized.RIGHTKIT_BUILD_BROKER_SOCKET;
|
|
20
|
-
const delimiter = process.platform === "win32" ? ";" : ":";
|
|
21
|
-
sanitized.PATH = String(base.PATH ?? "")
|
|
22
|
-
.split(delimiter)
|
|
23
|
-
.filter((entry) => !/(?:^|[/\\])(?:\.rightkit-managed|rightkitmanagedagent)[/\\]agent-bin$/i.test(entry))
|
|
24
|
-
.join(delimiter);
|
|
25
|
-
return sanitized;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function run(args, cacheRoot, env = {}) {
|
|
29
|
-
return spawnSync(process.execPath, [cli, "cache", ...args], { encoding: "utf8", env: { ...unmanagedEnv(), RIGHT_RELEASE_CACHE_ROOT: cacheRoot, ...env } });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test("cache status has human and schema-stable JSON output", () => {
|
|
33
|
-
const cacheRoot = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
34
|
-
const human = run(["status"], cacheRoot);
|
|
35
|
-
const json = run(["status", "--json"], cacheRoot);
|
|
36
|
-
assert.equal(human.status, 0, human.stderr);
|
|
37
|
-
assert.match(human.stdout, /RightKit cache root:/);
|
|
38
|
-
assert.match(human.stdout, /Backups: 0 bytes/);
|
|
39
|
-
assert.equal(json.status, 0, json.stderr);
|
|
40
|
-
assert.equal(JSON.parse(json.stdout).schema, 1);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("cache prune defaults to dry-run and only apply can mutate an admitted temporary entry", () => {
|
|
44
|
-
const cacheRoot = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
45
|
-
const target = path.join(cacheRoot, "targets", "mac", "fixture", "1111111111111111");
|
|
46
|
-
mkdirSync(target, { recursive: true });
|
|
47
|
-
writeFileSync(path.join(target, "payload"), "payload");
|
|
48
|
-
writeFileSync(path.join(target, ".rightkit-cache-entry.json"), JSON.stringify({ schema: 1, id: "target:mac:fixture:1111111111111111", kind: "release", platform: "mac", architecture: "aarch64", app: "fixture", fingerprint: "1111111111111111", createdAt: "2020-01-01T00:00:00.000Z", lastUsedAt: "2020-01-01T00:00:00.000Z", lastSuccessfulBuildAt: "2020-01-01T00:00:00.000Z", toolchain: {} }));
|
|
49
|
-
const dry = run(["prune", "--dry-run", "--json"], cacheRoot);
|
|
50
|
-
assert.equal(dry.status, 0, dry.stderr);
|
|
51
|
-
assert.equal(JSON.parse(dry.stdout).dryRun, true);
|
|
52
|
-
assert.equal(run(["prune", "--apply", "--json"], cacheRoot).status, 0);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("cache rejects unknown commands and migration requires a config", () => {
|
|
56
|
-
const cacheRoot = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
57
|
-
assert.notEqual(run(["unknown"], cacheRoot).status, 0);
|
|
58
|
-
assert.notEqual(run(["migrate"], cacheRoot).status, 0);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("status and prune dry-run do not create a cache root or lock file", () => {
|
|
62
|
-
const parent = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
63
|
-
const cacheRoot = path.join(parent, "never-created");
|
|
64
|
-
assert.equal(run(["status", "--json"], cacheRoot).status, 0);
|
|
65
|
-
assert.equal(run(["prune", "--dry-run", "--json"], cacheRoot).status, 0);
|
|
66
|
-
assert.equal(existsSync(cacheRoot), false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("cache migrate uses the repository vault Cargo home, not an app-local lookalike", () => {
|
|
70
|
-
const sandbox = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
71
|
-
const repo = path.join(sandbox, "repo"); const app = path.join(repo, "app"); const cacheRoot = path.join(sandbox, "shared-cache");
|
|
72
|
-
const config = path.join(app, "right-release.config.mjs"); const fingerprint = "abcdef0123456789";
|
|
73
|
-
const target = path.join(repo, ".right-release", "cache", "cargo-target", "mac", fingerprint);
|
|
74
|
-
const vaultCargoHome = path.join(repo, ".right-release", "cache", "cargo-home");
|
|
75
|
-
const appCargoHome = path.join(app, ".right-release", "cache", "cargo-home");
|
|
76
|
-
mkdirSync(path.join(repo, ".git"), { recursive: true });
|
|
77
|
-
mkdirSync(path.dirname(config), { recursive: true });
|
|
78
|
-
writeFileSync(config, "export default { app: 'fixture', targets: { mac: { targetTriple: 'aarch64-apple-darwin' } } };\n");
|
|
79
|
-
mkdirSync(path.join(app, "src-tauri"), { recursive: true });
|
|
80
|
-
writeFileSync(path.join(app, "src-tauri", "Cargo.lock"), "fixture-lock\n");
|
|
81
|
-
writeFileSync(path.join(app, "src-tauri", "Cargo.toml"), "[package]\nname = 'fixture'\n");
|
|
82
|
-
mkdirSync(target, { recursive: true }); writeFileSync(path.join(target, "artifact"), "target");
|
|
83
|
-
symlinkSync(target, path.join(app, "src-tauri", "target"));
|
|
84
|
-
mkdirSync(vaultCargoHome, { recursive: true }); writeFileSync(path.join(vaultCargoHome, "registry"), "vault");
|
|
85
|
-
mkdirSync(appCargoHome, { recursive: true }); writeFileSync(path.join(appCargoHome, "registry"), "app-local");
|
|
86
|
-
const result = run(["migrate", "--config", config, "--apply", "--json"], cacheRoot, { RIGHT_RELEASE_PLATFORM: "mac", RIGHT_RELEASE_RUSTC_VERBOSE: "rustc 1.91.0\nhost: aarch64-apple-darwin", RIGHT_RELEASE_CACHE_KEY: fingerprint });
|
|
87
|
-
assert.equal(result.status, 0, result.stderr);
|
|
88
|
-
assert.equal(JSON.parse(result.stdout).cargoHomeMoved, true);
|
|
89
|
-
assert.equal(readFileSync(path.join(cacheRoot, "cargo-home", "registry"), "utf8"), "vault");
|
|
90
|
-
assert.equal(readFileSync(path.join(appCargoHome, "registry"), "utf8"), "app-local");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("status, prune, and migrate short-circuit cleanly on a broker-managed host without proposing a repair", () => {
|
|
94
|
-
const cacheRoot = mkdtempSync(path.join(os.tmpdir(), "rightkit-cache-command-"));
|
|
95
|
-
const brokerEnv = { RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" };
|
|
96
|
-
|
|
97
|
-
const status = run(["status", "--json"], cacheRoot, brokerEnv);
|
|
98
|
-
assert.equal(status.status, 0, status.stderr);
|
|
99
|
-
const statusBody = JSON.parse(status.stdout);
|
|
100
|
-
assert.equal(statusBody.brokerManaged, true);
|
|
101
|
-
assert.match(statusBody.message, /broker-managed host/);
|
|
102
|
-
assert.doesNotMatch(status.stdout + status.stderr, /warn|repair|corrupt/i);
|
|
103
|
-
|
|
104
|
-
const prune = run(["prune", "--dry-run", "--json"], cacheRoot, brokerEnv);
|
|
105
|
-
assert.equal(prune.status, 0, prune.stderr);
|
|
106
|
-
assert.equal(JSON.parse(prune.stdout).brokerManaged, true);
|
|
107
|
-
|
|
108
|
-
// migrate short-circuits before even requiring --config, since a broker host has
|
|
109
|
-
// nothing under Cache V2's target layout for it to migrate into.
|
|
110
|
-
const migrate = run(["migrate", "--json"], cacheRoot, brokerEnv);
|
|
111
|
-
assert.equal(migrate.status, 0, migrate.stderr);
|
|
112
|
-
assert.equal(JSON.parse(migrate.stdout).brokerManaged, true);
|
|
113
|
-
|
|
114
|
-
// Non-broker host: unchanged behaviour.
|
|
115
|
-
const unmanaged = run(["status", "--json"], cacheRoot);
|
|
116
|
-
assert.equal(unmanaged.status, 0, unmanaged.stderr);
|
|
117
|
-
assert.equal(JSON.parse(unmanaged.stdout).brokerManaged, undefined);
|
|
118
|
-
});
|