@rightkit/release 0.2.69 → 0.2.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/rightkit-versions.json +3 -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,28 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import { registerRightAppsRelease } from "./rightapps-register.mjs";
|
|
5
|
-
|
|
6
|
-
test("registers through the shared durable release-token boundary", async () => {
|
|
7
|
-
const calls = [];
|
|
8
|
-
const response = await registerRightAppsRelease("/v1/admin/apps/runtime-artifacts", { schema: 1 }, {
|
|
9
|
-
token: "durable-token",
|
|
10
|
-
env: { RIGHTAPPS_API_URL: "https://rightapps.example", RIGHTAPPS_STORE_SLUG: "rightapps" },
|
|
11
|
-
fetchImpl: async (url, init) => {
|
|
12
|
-
calls.push({ url, init });
|
|
13
|
-
return { ok: true, text: async () => '{"id":"registered"}' };
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
assert.deepEqual(response, { id: "registered" });
|
|
17
|
-
assert.equal(calls[0].url, "https://rightapps.example/v1/admin/apps/runtime-artifacts");
|
|
18
|
-
assert.equal(calls[0].init.headers.authorization, "Bearer durable-token");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("rejects absent durable token and non-admin registration paths before network", async () => {
|
|
22
|
-
let called = false;
|
|
23
|
-
const fetchImpl = async () => { called = true; };
|
|
24
|
-
await assert.rejects(registerRightAppsRelease("/v1/admin/apps/runtime-artifacts", {}, { token: "", fetchImpl }), /release token/i);
|
|
25
|
-
await assert.rejects(registerRightAppsRelease("https://evil.invalid", {}, { token: "token", fetchImpl }), /route/i);
|
|
26
|
-
await assert.rejects(registerRightAppsRelease("/v1/admin/apps/../../evil", {}, { token: "token", fetchImpl }), /route/i);
|
|
27
|
-
assert.equal(called, false);
|
|
28
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { generateKeyPairSync } from "node:crypto";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
buildRuntimeArtifactManifest,
|
|
7
|
-
runtimeArtifactManifestPointerKey,
|
|
8
|
-
runtimeArtifactObjectKey,
|
|
9
|
-
signRuntimeArtifactManifest,
|
|
10
|
-
validateRuntimeArtifactManifest,
|
|
11
|
-
verifyRuntimeArtifactEnvelope,
|
|
12
|
-
} from "./runtime-artifact-manifest.mjs";
|
|
13
|
-
|
|
14
|
-
const digest = "a".repeat(64);
|
|
15
|
-
const evidenceDigest = "b".repeat(64);
|
|
16
|
-
|
|
17
|
-
function validManifest(overrides = {}) {
|
|
18
|
-
return buildRuntimeArtifactManifest({
|
|
19
|
-
artifactKind: "asr-model",
|
|
20
|
-
filename: "parakeet-tdt-v3.onnx",
|
|
21
|
-
sha256: digest,
|
|
22
|
-
sizeBytes: 42,
|
|
23
|
-
entitlement: { appKey: "scraperight", tier: "pro" },
|
|
24
|
-
distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
|
|
25
|
-
target: { os: "windows", arch: "x86_64" },
|
|
26
|
-
versions: {
|
|
27
|
-
runtime: "onnxruntime-1.22.0",
|
|
28
|
-
model: "parakeet-tdt-0.6b-v3",
|
|
29
|
-
tokenizer: "sentencepiece-2026-07-14",
|
|
30
|
-
preprocessing: "rightkit-asr-0.1.0",
|
|
31
|
-
license: "cc-by-4.0",
|
|
32
|
-
provenance: "heardright-approved-2026-07-14",
|
|
33
|
-
},
|
|
34
|
-
provenance: {
|
|
35
|
-
source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
|
|
36
|
-
sourceRevision: "0123456789abcdef",
|
|
37
|
-
licenseId: "CC-BY-4.0",
|
|
38
|
-
noticeSha256: "c".repeat(64),
|
|
39
|
-
},
|
|
40
|
-
promotion: {
|
|
41
|
-
authorityAppKey: "heardright",
|
|
42
|
-
promotionId: "hr-asr-2026-07-14-001",
|
|
43
|
-
promotedAt: "2026-07-14T12:00:00.000Z",
|
|
44
|
-
evidenceSha256: evidenceDigest,
|
|
45
|
-
},
|
|
46
|
-
...overrides,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
test("builds exact stable-pointer and immutable content-addressed object keys", () => {
|
|
51
|
-
const manifest = validManifest();
|
|
52
|
-
assert.equal(
|
|
53
|
-
manifest.pointerKey,
|
|
54
|
-
"scraperight/runtime-artifacts/asr-model/windows/x86_64/current/manifest.json",
|
|
55
|
-
);
|
|
56
|
-
assert.equal(
|
|
57
|
-
manifest.object.r2Key,
|
|
58
|
-
`scraperight/runtime-artifacts/objects/sha256/${digest}/parakeet-tdt-v3.onnx`,
|
|
59
|
-
);
|
|
60
|
-
assert.equal(runtimeArtifactManifestPointerKey(manifest), manifest.pointerKey);
|
|
61
|
-
assert.equal(runtimeArtifactObjectKey(manifest), manifest.object.r2Key);
|
|
62
|
-
assert.equal(validateRuntimeArtifactManifest(manifest), manifest);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("signs and verifies the canonical manifest with Ed25519", () => {
|
|
66
|
-
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
67
|
-
const envelope = signRuntimeArtifactManifest(validManifest(), privateKey, "rightapps-runtime-2026-01");
|
|
68
|
-
assert.equal(envelope.signature.algorithm, "Ed25519");
|
|
69
|
-
assert.equal(verifyRuntimeArtifactEnvelope(envelope, publicKey).artifactKind, "asr-model");
|
|
70
|
-
|
|
71
|
-
const tampered = structuredClone(envelope);
|
|
72
|
-
tampered.manifest.object.sizeBytes += 1;
|
|
73
|
-
assert.throws(() => verifyRuntimeArtifactEnvelope(tampered, publicKey), /signature verification failed/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("fails closed on missing or contradictory digest, provenance, version, scope, and promotion metadata", () => {
|
|
77
|
-
const mutations = [
|
|
78
|
-
(m) => delete m.versions.tokenizer,
|
|
79
|
-
(m) => { m.versions.license = ""; },
|
|
80
|
-
(m) => delete m.provenance.sourceRevision,
|
|
81
|
-
(m) => { m.provenance.licenseId = "MIT"; },
|
|
82
|
-
(m) => { m.entitlement.tier = "free"; },
|
|
83
|
-
(m) => { m.object.sha256 = "d".repeat(64); },
|
|
84
|
-
(m) => { m.object.sizeBytes = 0; },
|
|
85
|
-
(m) => { m.pointerKey = "scraperight/runtime-artifacts/asr-model/windows/x86_64/v1/manifest.json"; },
|
|
86
|
-
(m) => { m.promotion.authorityAppKey = "unknown"; },
|
|
87
|
-
(m) => { m.promotion.evidenceSha256 = "not-a-digest"; },
|
|
88
|
-
];
|
|
89
|
-
for (const mutate of mutations) {
|
|
90
|
-
const manifest = structuredClone(validManifest());
|
|
91
|
-
mutate(manifest);
|
|
92
|
-
assert.throws(() => validateRuntimeArtifactManifest(manifest));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("rejects unknown fields and path traversal instead of guessing", () => {
|
|
97
|
-
const extra = structuredClone(validManifest());
|
|
98
|
-
extra.secretUrl = "https://example.invalid/token";
|
|
99
|
-
assert.throws(() => validateRuntimeArtifactManifest(extra), /unknown field/);
|
|
100
|
-
assert.throws(() => validManifest({ filename: "../model.onnx" }), /filename/);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("requires an explicit compatible entitlement and delivery lane per artifact", () => {
|
|
104
|
-
const publicMedia = validManifest({
|
|
105
|
-
artifactKind: "media-runtime",
|
|
106
|
-
entitlement: { appKey: "scraperight", tier: "public" },
|
|
107
|
-
distribution: { delivery: "public-r2", bucket: "rightapps-downloads" },
|
|
108
|
-
});
|
|
109
|
-
assert.equal(publicMedia.distribution.delivery, "public-r2");
|
|
110
|
-
|
|
111
|
-
const bundledOrt = validManifest({
|
|
112
|
-
artifactKind: "ort-runtime",
|
|
113
|
-
entitlement: { appKey: "viewright", tier: "bundled" },
|
|
114
|
-
distribution: { delivery: "bundled", bucket: null },
|
|
115
|
-
});
|
|
116
|
-
assert.equal(bundledOrt.pointerKey, null);
|
|
117
|
-
assert.equal(bundledOrt.object.r2Key, null);
|
|
118
|
-
|
|
119
|
-
assert.throws(() => validManifest({
|
|
120
|
-
entitlement: { appKey: "scraperight", tier: "public" },
|
|
121
|
-
distribution: { delivery: "public-r2", bucket: "rightapps-downloads" },
|
|
122
|
-
}), /model.*private-r2.*pro/i);
|
|
123
|
-
assert.throws(() => validManifest({
|
|
124
|
-
artifactKind: "media-runtime",
|
|
125
|
-
entitlement: { appKey: "scraperight", tier: "public" },
|
|
126
|
-
distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
|
|
127
|
-
}), /contradict/);
|
|
128
|
-
});
|
package/sign-updater.test.mjs
DELETED
|
@@ -1,12 +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("./sign-updater.mjs", import.meta.url));
|
|
7
|
-
|
|
8
|
-
test("dry-run signs the final OS-signed artifact with Tauri minisign", () => {
|
|
9
|
-
const result = spawnSync(process.execPath, [helper, "--file", "Fixture.exe", "--dry-run"], { encoding: "utf8" });
|
|
10
|
-
assert.equal(result.status, 0, result.stderr);
|
|
11
|
-
assert.match(result.stdout, /tauri signer sign .*Fixture\.exe/);
|
|
12
|
-
});
|
package/source-gate.test.mjs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import { assertCleanSource } from "./source-gate.mjs";
|
|
5
|
-
|
|
6
|
-
test("a clean status passes the gate", () => {
|
|
7
|
-
assert.doesNotThrow(() => assertCleanSource({ status: "", commandId: "right-release build" }));
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test("a dirty status names the command and the offending files", () => {
|
|
11
|
-
assert.throws(
|
|
12
|
-
() => assertCleanSource({ status: " M src/main.rs\0?? notes.txt\0", commandId: "right-release build" }),
|
|
13
|
-
(error) => {
|
|
14
|
-
assert.match(error.message, /right-release build requires a clean working tree/);
|
|
15
|
-
assert.match(error.message, /- src\/main\.rs/);
|
|
16
|
-
assert.match(error.message, /- notes\.txt/);
|
|
17
|
-
return true;
|
|
18
|
-
},
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("a missing status is refused rather than treated as clean", () => {
|
|
23
|
-
assert.throws(() => assertCleanSource({ commandId: "right-release upload" }), /status is required/);
|
|
24
|
-
assert.throws(() => assertCleanSource({ status: null }), /status is required/);
|
|
25
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-08-10T04:42:36.444Z",
|
|
4
|
-
"workRoot": "C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR",
|
|
5
|
-
"apps": [
|
|
6
|
-
{
|
|
7
|
-
"key": "viewright",
|
|
8
|
-
"remote": "https://github.com/bogusyogi/viewright.git",
|
|
9
|
-
"appDir": ".",
|
|
10
|
-
"revision": "21a4171fa8add2d8114bc5f07498b60d7e8eafe5",
|
|
11
|
-
"packageManager": "pnpm@11.24.0",
|
|
12
|
-
"clone": {
|
|
13
|
-
"command": "git clone --depth 1 --single-branch https://github.com/bogusyogi/viewright.git C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright",
|
|
14
|
-
"status": 0,
|
|
15
|
-
"stdout": "",
|
|
16
|
-
"stderr": "Cloning into 'C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright'...\nUpdating files: 91% (1902/2080)\rUpdating files: 92% (1914/2080)\rUpdating files: 93% (1935/2080)\rUpdating files: 94% (1956/2080)\rUpdating files: 95% (1976/2080)\rUpdating files: 96% (1997/2080)\rUpdating files: 97% (2018/2080)\rUpdating files: 98% (2039/2080)\rUpdating files: 99% (2060/2080)\rUpdating files: 100% (2080/2080)\rUpdating files: 100% (2080/2080), done."
|
|
17
|
-
},
|
|
18
|
-
"install": {
|
|
19
|
-
"command": "C:\\nvm4w\\nodejs\\node.exe C:\\nvm4w\\nodejs\\node_modules\\pnpm\\bin\\pnpm.mjs install --frozen-lockfile",
|
|
20
|
-
"status": 0,
|
|
21
|
-
"stdout": "✓ Lockfile passes supply-chain policies (verified 8h ago)\nLockfile is up to date, resolution step is skipped\nProgress: resolved 1, reused 0, downloaded 0, added 0\nPackages: +619\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nProgress: resolved 619, reused 0, downloaded 0, added 0\nProgress: resolved 619, reused 60, downloaded 0, added 0\nPackages are hard linked from the content-addressable store to the virtual store.\n Content-addressable store is at: C:\\Users\\adrds\\AppData\\Local\\pnpm\\store\\v11\n Virtual store is at: node_modules/.pnpm\nProgress: resolved 619, reused 573, downloaded 7, added 7\nProgress: resolved 619, reused 574, downloaded 9, added 10\nProgress: resolved 619, reused 574, downloaded 22, added 11\nProgress: resolved 619, reused 574, downloaded 23, added 11\nProgress: resolved 619, reused 574, downloaded 26, added 14\nProgress: resolved 619, reused 574, downloaded 30, added 24\nProgress: resolved 619, reused 574, downloaded 31, added 57\nProgress: resolved 619, reused 574, downloaded 34, added 128\nProgress: resolved 619, reused 574, downloaded 38, added 135\nProgress: resolved 619, reused 574, downloaded 38, added 136\nProgress: resolved 619, reused 574, downloaded 39, added 141\nProgress: resolved 619, reused 574, downloaded 39, added 148\nProgress: resolved 619, reused 574, downloaded 39, added 182\nProgress: resolved 619, reused 574, downloaded 40, added 201\nProgress: resolved 619, reused 574, downloaded 41, added 218\nProgress: resolved 619, reused 574, downloaded 41, added 242\nProgress: resolved 619, reused 574, downloaded 42, added 312\nProgress: resolved 619, reused 574, downloaded 42, added 371\nProgress: resolved 619, reused 574, downloaded 42, added 430\nProgress: resolved 619, reused 574, downloaded 42, added 486\nProgress: resolved 619, reused 574, downloaded 43, added 543\nProgress: resolved 619, reused 574, downloaded 43, added 578\nProgress: resolved 619, reused 574, downloaded 44, added 596\nProgress: resolved 619, reused 574, downloaded 44, added 611\nProgress: resolved 619, reused 574, downloaded 44, added 612\nProgress: resolved 619, reused 574, downloaded 44, added 613\nProgress: resolved 619, reused 574, downloaded 44, added 615\nProgress: resolved 619, reused 574, downloaded 44, added 616\nProgress: resolved 619, reused 574, downloaded 44, added 617\nProgress: resolved 619, reused 574, downloaded 44, added 618\nProgress: resolved 619, reused 574, downloaded 45, added 618\nProgress: resolved 619, reused 574, downloaded 45, added 619\nProgress: resolved 619, reused 574, downloaded 45, added 619, done\n\ndependencies:\n+ @codemirror/autocomplete 6.20.3\n+ @codemirror/commands 6.10.4\n+ @codemirror/lang-html 6.4.11\n+ @codemirror/lang-javascript 6.2.5\n+ @codemirror/lang-markdown 6.5.0\n+ @codemirror/language 6.12.4\n+ @codemirror/lint 6.9.7\n+ @codemirror/search 6.7.1\n+ @codemirror/state 6.7.1\n+ @codemirror/view 6.43.6\n+ @eigenpal/docx-editor-agents @eigenpal/docx-editor-agents@file:vendor/docx-editor/packages/agents(react@19.2.7)\n+ @eigenpal/docx-editor-core @eigenpal/docx-editor-core@file:vendor/docx-editor/packages/core(prosemirror-commands@1.7.1)(prosemirror-dropcursor@1.8.2)(prosemirror-history@1.5.0)(prosemirror-keymap@1.2.3)(prosemirror-model@1.25.10)(prosemirror-state@1.4.4)(prosemirror-tables@1.8.5)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.0)\n+ @eigenpal/docx-editor-i18n @eigenpal/docx-editor-i18n@file:vendor/docx-editor/packages/i18n\n+ @eigenpal/docx-editor-react @eigenpal/docx-editor-react@file:vendor/docx-editor/packages/react(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(prosemirror-commands@1.7.1)(prosemirror-dropcursor@1.8.2)(prosemirror-history@1.5.0)(prosemirror-keymap@1.2.3)(prosemirror-model@1.25.10)(prosemirror-state@1.4.4)(prosemirror-tables@1.8.5)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)\n+ @lezer/highlight 1.2.3\n+ @mdx-js/mdx 3.1.1\n+ @phosphor-icons/react 2.1.10\n+ @radix-ui/react-select 2.3.2\n+ @rightkit/legal-ui 0.1.0\n+ @rightkit/license 0.1.6\n+ @rightkit/logs 0.1.3\n+ @rightkit/tauri 0.1.0\n+ @rightkit/updates 0.2.3\n+ @tauri-apps/api 2.11.1\n+ @tauri-apps/plugin-http 2.5.9\n+ @tauri-apps/plugin-process 2.3.1\n+ @tauri-apps/plugin-updater 2.10.1\n+ clsx 2.1.1\n+ docxtemplater 3.69.0\n+ dompurify 3.4.13\n+ fabric 7.4.0\n+ github-slugger 2.0.0\n+ jszip 3.10.1\n+ katex 0.17.0\n+ mermaid 11.16.1\n+ pdfjs-dist 6.2.108\n+ pizzip 3.2.0\n+ prosemirror-commands 1.7.1\n+ prosemirror-dropcursor 1.8.2\n+ prosemirror-history 1.5.0\n+ prosemirror-keymap 1.2.3\n+ prosemirror-model 1.25.10\n+ prosemirror-state 1.4.4\n+ prosemirror-tables 1.8.5\n+ prosemirror-transform 1.12.0\n+ prosemirror-view 1.42.0\n+ react 19.2.7\n+ react-dom 19.2.7\n+ react-image-crop 11.1.2\n+ rehype-stringify 10.0.1\n+ remark-frontmatter 5.0.0\n+ remark-gfm 4.0.1\n+ remark-math 6.0.0\n+ remark-parse 11.0.0\n+ remark-rehype 11.1.2\n+ remark-smartypants 3.0.2\n+ shiki 4.3.1\n+ sonner 2.0.7\n+ sucrase 3.35.1\n+ unified 11.0.5\n+ xml-js 1.6.11\n+ yaml 2.9.0\n\ndevDependencies:\n+ @biomejs/biome 2.5.3\n+ @rightkit/legal 0.3.0\n+ @rightkit/release 0.2.50\n+ @tailwindcss/vite 4.3.2\n+ @tauri-apps/cli 2.11.4\n+ @testing-library/dom 10.4.1\n+ @testing-library/jest-dom 6.9.1\n+ @testing-library/react 16.3.2\n+ @types/mdast 4.0.4\n+ @types/react 19.2.17\n+ @types/react-dom 19.2.3\n+ @types/ws 8.18.1\n+ @vitejs/plugin-react 6.0.3\n+ happy-dom 20.10.6\n+ jscpd 5.0.12\n+ jsdom 29.1.1\n+ knip 6.25.0\n+ tailwindcss 4.3.2\n+ typescript 6.0.3\n+ vite 8.1.4\n+ vitest 4.1.10\n+ ws 8.21.0\n\nDone in 40.8s using pnpm v11.18.0",
|
|
22
|
-
"stderr": ""
|
|
23
|
-
},
|
|
24
|
-
"doctor": {
|
|
25
|
-
"command": "C:\\nvm4w\\nodejs\\node.exe C:\\nvm4w\\nodejs\\node_modules\\pnpm\\bin\\pnpm.mjs release:doctor",
|
|
26
|
-
"status": 0,
|
|
27
|
-
"stdout": "right-release 0.2.50\nconfig: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\right-release.config.mjs\napp: viewright\nplatform: win\ntier: <required for release/publish>\npackageManager: pnpm\nworkdir: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\nhardeningscan: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\node_modules\\.pnpm\\@rightkit+release@0.2.50\\node_modules\\@rightkit\\release\\hardeningscan.mjs\nlegal: C:\\Users\\adrds\\AppData\\Local\\Temp\\rightkit-standalone-sBI0iR\\viewright\\legal\\legal-manifest.json\nlegalAcceptance: viewright-2026-07-17-v3\nlegalManifestSha256: 035ea7cbd040ef001dbdc1385f114c8bb126178fea9a9f705016a726f96a7830\nsign: src-tauri/target/release/bundle/nsis/ViewRight_0.1.60_x64-setup.exe\npreflight:\n [ok ] target-bridge: src-tauri/target is ready for the shared cache bridge\n [ok ] version: 0.1.60 is free to build\n [ok ] windows-sdk: makeappx.exe from SDK 10.0.26100.0\n [ok ] signtool: signtool.exe from SDK 10.0.26100.0\n [ok ] sccache: sccache 0.17.0\n [ok ] disk: 628.7GB free",
|
|
28
|
-
"stderr": "$ right-release doctor"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
|
|
8
|
-
import { verifyStandaloneClones } from "./standalone-clone-verify.mjs";
|
|
9
|
-
|
|
10
|
-
function run(command, args, cwd) {
|
|
11
|
-
const result = spawnSync(command, args, { cwd, encoding: "utf8", windowsHide: true });
|
|
12
|
-
assert.equal(result.status, 0, `${command} ${args.join(" ")}\n${result.stderr}`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
test("standalone verifier clones, installs, doctors from a nested app root, and removes only its own temp tree", async (t) => {
|
|
16
|
-
const fixtureRoot = mkdtempSync(path.join(tmpdir(), "rightkit-standalone-fixture-"));
|
|
17
|
-
t.after(() => rmSync(fixtureRoot, { recursive: true, force: true }));
|
|
18
|
-
const source = path.join(fixtureRoot, "source");
|
|
19
|
-
const outsideForge = path.join(fixtureRoot, "keep.txt");
|
|
20
|
-
mkdirSync(path.join(source, "apps", "desktop"), { recursive: true });
|
|
21
|
-
writeFileSync(outsideForge, "keep", "utf8");
|
|
22
|
-
writeFileSync(path.join(source, "apps", "desktop", "package.json"), JSON.stringify({
|
|
23
|
-
name: "standalone-fixture",
|
|
24
|
-
private: true,
|
|
25
|
-
packageManager: "pnpm@11.24.0",
|
|
26
|
-
scripts: { "release:doctor": "node doctor.mjs" },
|
|
27
|
-
}), "utf8");
|
|
28
|
-
writeFileSync(path.join(source, "apps", "desktop", "pnpm-lock.yaml"), "lockfileVersion: '9.0'\nsettings:\n autoInstallPeers: true\n excludeLinksFromLockfile: false\nimporters:\n .: {}\n", "utf8");
|
|
29
|
-
writeFileSync(path.join(source, "apps", "desktop", "doctor.mjs"), "process.stdout.write('fixture doctor passed\\n')\n", "utf8");
|
|
30
|
-
run("git", ["init", "-q"], source);
|
|
31
|
-
run("git", ["config", "user.email", "fixture@example.test"], source);
|
|
32
|
-
run("git", ["config", "user.name", "Fixture"], source);
|
|
33
|
-
run("git", ["add", "."], source);
|
|
34
|
-
run("git", ["commit", "-qm", "fixture"], source);
|
|
35
|
-
|
|
36
|
-
const evidencePath = path.join(fixtureRoot, "evidence.json");
|
|
37
|
-
const result = await verifyStandaloneClones({
|
|
38
|
-
apps: [{ key: "fixture", remote: source, appDir: "apps/desktop" }],
|
|
39
|
-
evidencePath,
|
|
40
|
-
tempParent: fixtureRoot,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
assert.equal(result.apps[0].install.status, 0);
|
|
44
|
-
assert.equal(result.apps[0].doctor.status, 0);
|
|
45
|
-
assert.match(result.apps[0].doctor.stdout, /fixture doctor passed/);
|
|
46
|
-
assert.equal(existsSync(result.workRoot), false, "generated clone tree must be removed");
|
|
47
|
-
assert.equal(readFileSync(outsideForge, "utf8"), "keep", "cleanup must not escape the generated tree");
|
|
48
|
-
assert.deepEqual(JSON.parse(readFileSync(evidencePath, "utf8")).apps.map(({ key }) => key), ["fixture"]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("release package exposes the local standalone verification lane", () => {
|
|
52
|
-
const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
53
|
-
assert.equal(pkg.scripts["verify:standalone"], "node standalone-clone-verify.mjs");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("standalone verifier rejects a package root that escapes its clone", async (t) => {
|
|
57
|
-
const fixtureRoot = mkdtempSync(path.join(tmpdir(), "rightkit-standalone-escape-"));
|
|
58
|
-
t.after(() => rmSync(fixtureRoot, { recursive: true, force: true }));
|
|
59
|
-
const source = path.join(fixtureRoot, "source");
|
|
60
|
-
mkdirSync(source, { recursive: true });
|
|
61
|
-
writeFileSync(path.join(source, "README.md"), "fixture", "utf8");
|
|
62
|
-
run("git", ["init", "-q"], source);
|
|
63
|
-
run("git", ["config", "user.email", "fixture@example.test"], source);
|
|
64
|
-
run("git", ["config", "user.name", "Fixture"], source);
|
|
65
|
-
run("git", ["add", "."], source);
|
|
66
|
-
run("git", ["commit", "-qm", "fixture"], source);
|
|
67
|
-
|
|
68
|
-
await assert.rejects(
|
|
69
|
-
verifyStandaloneClones({
|
|
70
|
-
apps: [{ key: "fixture", remote: source, appDir: ".." }],
|
|
71
|
-
evidencePath: path.join(fixtureRoot, "evidence.json"),
|
|
72
|
-
tempParent: fixtureRoot,
|
|
73
|
-
}),
|
|
74
|
-
/package root escapes its clone/,
|
|
75
|
-
);
|
|
76
|
-
});
|
package/suite-doctor.test.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const cli = path.join(packageRoot, "cli", "right-release.mjs");
|
|
9
|
-
|
|
10
|
-
test("suite-doctor runs the complete local multi-repo contract", () => {
|
|
11
|
-
const result = spawnSync(process.execPath, [cli, "suite-doctor"], {
|
|
12
|
-
cwd: packageRoot,
|
|
13
|
-
encoding: "utf8",
|
|
14
|
-
windowsHide: true,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
18
|
-
assert.match(result.stdout, /\[right-release\] suite-doctor passed/);
|
|
19
|
-
});
|
package/target-bridge.test.mjs
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
|
|
7
|
-
import { createTargetBridge } from "./target-bridge.mjs";
|
|
8
|
-
|
|
9
|
-
function fixture() {
|
|
10
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "rightkit-target-bridge-"));
|
|
11
|
-
const target = path.join(root, "shared", "target");
|
|
12
|
-
const link = path.join(root, "app", "src-tauri", "target");
|
|
13
|
-
mkdirSync(target, { recursive: true });
|
|
14
|
-
mkdirSync(path.dirname(link), { recursive: true });
|
|
15
|
-
writeFileSync(path.join(target, "keep.txt"), "shared-cache\n");
|
|
16
|
-
return { root, target, link };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
test("removes an invocation-created bridge after success without touching the shared target", async () => {
|
|
20
|
-
const fx = fixture();
|
|
21
|
-
const bridge = createTargetBridge(fx);
|
|
22
|
-
await bridge.run(async () => {
|
|
23
|
-
bridge.ensure();
|
|
24
|
-
assert.equal(realpathSync(fx.link), realpathSync(fx.target));
|
|
25
|
-
});
|
|
26
|
-
assert.throws(() => lstatSync(fx.link), { code: "ENOENT" });
|
|
27
|
-
assert.equal(readFileSync(path.join(fx.target, "keep.txt"), "utf8"), "shared-cache\n");
|
|
28
|
-
});
|
|
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
|
-
|
|
47
|
-
test("removes an invocation-created bridge after the build throws", async () => {
|
|
48
|
-
const fx = fixture();
|
|
49
|
-
const bridge = createTargetBridge(fx);
|
|
50
|
-
await assert.rejects(
|
|
51
|
-
bridge.run(async () => {
|
|
52
|
-
bridge.ensure();
|
|
53
|
-
throw new Error("package failed");
|
|
54
|
-
}),
|
|
55
|
-
/package failed/,
|
|
56
|
-
);
|
|
57
|
-
assert.throws(() => lstatSync(fx.link), { code: "ENOENT" });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("removes an invocation-created bridge after a signal-style abort", async () => {
|
|
61
|
-
const fx = fixture();
|
|
62
|
-
const bridge = createTargetBridge(fx);
|
|
63
|
-
await assert.rejects(
|
|
64
|
-
bridge.run(async () => {
|
|
65
|
-
bridge.ensure();
|
|
66
|
-
throw new Error("release interrupted by SIGTERM");
|
|
67
|
-
}),
|
|
68
|
-
/SIGTERM/,
|
|
69
|
-
);
|
|
70
|
-
assert.throws(() => lstatSync(fx.link), { code: "ENOENT" });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("accepts but preserves a pre-existing exact shared-target bridge", async () => {
|
|
74
|
-
const fx = fixture();
|
|
75
|
-
symlinkSync(fx.target, fx.link, process.platform === "win32" ? "junction" : "dir");
|
|
76
|
-
await createTargetBridge(fx).run(async (bridge) => {
|
|
77
|
-
assert.deepEqual(bridge.ensure(), { created: false, link: fx.link, target: fx.target });
|
|
78
|
-
});
|
|
79
|
-
assert.equal(realpathSync(fx.link), realpathSync(fx.target));
|
|
80
|
-
assert.equal(readFileSync(path.join(fx.target, "keep.txt"), "utf8"), "shared-cache\n");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("fails closed when the new bridge is replaced immediately after symlink creation", () => {
|
|
84
|
-
const fx = fixture();
|
|
85
|
-
const other = path.join(fx.root, "racing-target");
|
|
86
|
-
mkdirSync(other);
|
|
87
|
-
const bridge = createTargetBridge({
|
|
88
|
-
...fx,
|
|
89
|
-
symlink(target, link, type) {
|
|
90
|
-
symlinkSync(target, link, type);
|
|
91
|
-
unlinkSync(link);
|
|
92
|
-
symlinkSync(other, link, type);
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
assert.throws(() => bridge.ensure(), /owned shared cache target/i);
|
|
96
|
-
assert.equal(realpathSync(fx.link), realpathSync(other));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("fails closed and preserves an unrelated symlink", async () => {
|
|
100
|
-
const fx = fixture();
|
|
101
|
-
const other = path.join(fx.root, "other-target");
|
|
102
|
-
mkdirSync(other);
|
|
103
|
-
symlinkSync(other, fx.link, process.platform === "win32" ? "junction" : "dir");
|
|
104
|
-
await assert.rejects(
|
|
105
|
-
createTargetBridge(fx).run(async (bridge) => bridge.ensure()),
|
|
106
|
-
/not the owned shared cache target/i,
|
|
107
|
-
);
|
|
108
|
-
assert.equal(realpathSync(fx.link), realpathSync(other));
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("preserves a same-target symlink that replaces an acquired bridge", () => {
|
|
112
|
-
const fx = fixture();
|
|
113
|
-
let successfulStats = 0;
|
|
114
|
-
const bridge = createTargetBridge({
|
|
115
|
-
...fx,
|
|
116
|
-
lstat(file) {
|
|
117
|
-
const stats = lstatSync(file);
|
|
118
|
-
successfulStats += 1;
|
|
119
|
-
if (successfulStats === 1) return stats;
|
|
120
|
-
return { dev: stats.dev, ino: stats.ino + 1, isSymbolicLink: () => stats.isSymbolicLink() };
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
bridge.ensure();
|
|
124
|
-
unlinkSync(fx.link);
|
|
125
|
-
symlinkSync(fx.target, fx.link, process.platform === "win32" ? "junction" : "dir");
|
|
126
|
-
assert.equal(bridge.release(), false);
|
|
127
|
-
assert.equal(realpathSync(fx.link), realpathSync(fx.target));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("preserves a different-target symlink that replaces an acquired bridge", () => {
|
|
131
|
-
const fx = fixture();
|
|
132
|
-
const other = path.join(fx.root, "replacement-target");
|
|
133
|
-
mkdirSync(other);
|
|
134
|
-
const bridge = createTargetBridge(fx);
|
|
135
|
-
bridge.ensure();
|
|
136
|
-
unlinkSync(fx.link);
|
|
137
|
-
symlinkSync(other, fx.link, process.platform === "win32" ? "junction" : "dir");
|
|
138
|
-
assert.equal(bridge.release(), false);
|
|
139
|
-
assert.equal(realpathSync(fx.link), realpathSync(other));
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("fails closed and preserves a real target directory with user content", async () => {
|
|
143
|
-
const fx = fixture();
|
|
144
|
-
mkdirSync(fx.link);
|
|
145
|
-
writeFileSync(path.join(fx.link, "user.txt"), "keep\n");
|
|
146
|
-
await assert.rejects(
|
|
147
|
-
createTargetBridge(fx).run(async (bridge) => bridge.ensure()),
|
|
148
|
-
/not a symbolic link/i,
|
|
149
|
-
);
|
|
150
|
-
assert.equal(readFileSync(path.join(fx.link, "user.txt"), "utf8"), "keep\n");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("rejecting a real target does not create the shared cache destination", () => {
|
|
154
|
-
const root = mkdtempSync(path.join(os.tmpdir(), "rightkit-target-bridge-pure-"));
|
|
155
|
-
const target = path.join(root, "shared", "target");
|
|
156
|
-
const link = path.join(root, "app", "src-tauri", "target");
|
|
157
|
-
mkdirSync(link, { recursive: true });
|
|
158
|
-
|
|
159
|
-
assert.throws(() => createTargetBridge({ link, target }).ensure(), /not a symbolic link/i);
|
|
160
|
-
assert.equal(existsSync(target), false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("broker-managed: a real target directory is left untouched instead of treated as corruption", async () => {
|
|
164
|
-
const fx = fixture();
|
|
165
|
-
mkdirSync(fx.link);
|
|
166
|
-
writeFileSync(path.join(fx.link, "user.txt"), "keep\n");
|
|
167
|
-
const result = await createTargetBridge({ ...fx, brokerManaged: true }).run(async (bridge) => bridge.ensure());
|
|
168
|
-
assert.equal(result.brokerManaged, true);
|
|
169
|
-
assert.equal(result.created, false);
|
|
170
|
-
// Nothing was removed, replaced, or relinked: the real directory and its
|
|
171
|
-
// content are exactly as they were, and no shared-cache destination was
|
|
172
|
-
// fabricated in its place.
|
|
173
|
-
assert.equal(readFileSync(path.join(fx.link, "user.txt"), "utf8"), "keep\n");
|
|
174
|
-
assert.equal(lstatSync(fx.link).isSymbolicLink(), false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("broker-managed: release() is a safe no-op after leaving a real target untouched", () => {
|
|
178
|
-
const fx = fixture();
|
|
179
|
-
mkdirSync(fx.link);
|
|
180
|
-
writeFileSync(path.join(fx.link, "user.txt"), "keep\n");
|
|
181
|
-
const bridge = createTargetBridge({ ...fx, brokerManaged: true });
|
|
182
|
-
bridge.ensure();
|
|
183
|
-
assert.equal(bridge.release(), false);
|
|
184
|
-
assert.equal(readFileSync(path.join(fx.link, "user.txt"), "utf8"), "keep\n");
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("broker-managed has no effect on the non-broker path: a symlink bridge still behaves exactly as before", async () => {
|
|
188
|
-
const fx = fixture();
|
|
189
|
-
const result = await createTargetBridge({ ...fx, brokerManaged: true }).run(async (bridge) => {
|
|
190
|
-
const ensured = bridge.ensure();
|
|
191
|
-
assert.equal(lstatSync(fx.link).isSymbolicLink(), true);
|
|
192
|
-
return ensured;
|
|
193
|
-
});
|
|
194
|
-
assert.equal(result.created, true);
|
|
195
|
-
assert.equal(result.brokerManaged, undefined);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("cleanup is idempotent and reports process errors without masking a build failure", async () => {
|
|
199
|
-
const fx = fixture();
|
|
200
|
-
const bridge = createTargetBridge(fx);
|
|
201
|
-
bridge.ensure();
|
|
202
|
-
assert.equal(bridge.release(), true);
|
|
203
|
-
assert.equal(bridge.release(), false);
|
|
204
|
-
|
|
205
|
-
const cleanupFailure = new Error("unlink denied");
|
|
206
|
-
const failing = createTargetBridge({ ...fx, unlink: () => { throw cleanupFailure; } });
|
|
207
|
-
const buildFailure = new Error("build aborted");
|
|
208
|
-
await assert.rejects(
|
|
209
|
-
failing.run(async () => {
|
|
210
|
-
failing.ensure();
|
|
211
|
-
throw buildFailure;
|
|
212
|
-
}),
|
|
213
|
-
(error) => error === buildFailure && error.cleanupError === cleanupFailure,
|
|
214
|
-
);
|
|
215
|
-
assert.equal(lstatSync(fx.link).isSymbolicLink(), true);
|
|
216
|
-
unlinkSync(fx.link);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("a stale link from an earlier fingerprint is repointed, not a hard stop", () => {
|
|
220
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "bridge-stale-"));
|
|
221
|
-
const ownedRoot = path.join(dir, "cache", "heardright");
|
|
222
|
-
const oldTarget = path.join(ownedRoot, "aaaaaaaa");
|
|
223
|
-
const newTarget = path.join(ownedRoot, "bbbbbbbb");
|
|
224
|
-
mkdirSync(oldTarget, { recursive: true });
|
|
225
|
-
const link = path.join(dir, "target");
|
|
226
|
-
symlinkSync(oldTarget, link, "junction");
|
|
227
|
-
|
|
228
|
-
const bridge = createTargetBridge({ link, target: newTarget, ownedRoot });
|
|
229
|
-
const result = bridge.ensure();
|
|
230
|
-
|
|
231
|
-
assert.equal(result.created, true);
|
|
232
|
-
assert.equal(realpathSync(link), realpathSync(newTarget));
|
|
233
|
-
// The directory the stale link pointed at must survive: it is cache, and the
|
|
234
|
-
// link was only ever a pointer to it.
|
|
235
|
-
assert.equal(existsSync(oldTarget), true);
|
|
236
|
-
rmSync(dir, { recursive: true, force: true });
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("a link pointing outside the owned root is still refused", () => {
|
|
240
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "bridge-foreign-"));
|
|
241
|
-
const ownedRoot = path.join(dir, "cache", "heardright");
|
|
242
|
-
const foreign = path.join(dir, "somewhere-else");
|
|
243
|
-
const newTarget = path.join(ownedRoot, "bbbbbbbb");
|
|
244
|
-
mkdirSync(foreign, { recursive: true });
|
|
245
|
-
const link = path.join(dir, "target");
|
|
246
|
-
symlinkSync(foreign, link, "junction");
|
|
247
|
-
|
|
248
|
-
const bridge = createTargetBridge({ link, target: newTarget, ownedRoot });
|
|
249
|
-
assert.throws(() => bridge.ensure(), /refusing to replace it/);
|
|
250
|
-
assert.equal(existsSync(newTarget), false);
|
|
251
|
-
assert.equal(realpathSync(link), realpathSync(foreign));
|
|
252
|
-
rmSync(dir, { recursive: true, force: true });
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test("a dangling link whose cache entry was pruned is reclaimed", () => {
|
|
256
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), "bridge-dangling-"));
|
|
257
|
-
const ownedRoot = path.join(dir, "cache", "heardright");
|
|
258
|
-
const pruned = path.join(ownedRoot, "aaaaaaaa");
|
|
259
|
-
const newTarget = path.join(ownedRoot, "bbbbbbbb");
|
|
260
|
-
mkdirSync(pruned, { recursive: true });
|
|
261
|
-
const link = path.join(dir, "target");
|
|
262
|
-
symlinkSync(pruned, link, "junction");
|
|
263
|
-
rmSync(pruned, { recursive: true, force: true });
|
|
264
|
-
|
|
265
|
-
const bridge = createTargetBridge({ link, target: newTarget, ownedRoot });
|
|
266
|
-
assert.equal(bridge.ensure().created, true);
|
|
267
|
-
assert.equal(realpathSync(link), realpathSync(newTarget));
|
|
268
|
-
rmSync(dir, { recursive: true, force: true });
|
|
269
|
-
});
|