@rightkit/release 0.2.61 → 0.2.63

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/release-state.mjs CHANGED
@@ -46,14 +46,16 @@ export function commandOutputPortable(cmd, args, { cwd, env = process.env } = {}
46
46
  return result.stdout.trim();
47
47
  }
48
48
 
49
- export function watchProgress(paths, onProgress) {
49
+ export function watchProgress(paths, onProgress, { maxFallbackWatchers = 128, watchFactory = watch, readDirectory = readdirSync } = {}) {
50
+ if (!Number.isInteger(maxFallbackWatchers) || maxFallbackWatchers < 1) throw new TypeError("maxFallbackWatchers must be a positive integer");
50
51
  const watchers = [];
52
+ const fallback = { remaining: maxFallbackWatchers };
51
53
  for (const candidate of paths) {
52
54
  if (!candidate || !existsSync(candidate)) continue;
53
55
  try {
54
- watchers.push(watch(candidate, { recursive: true }, onProgress));
56
+ watchers.push(watchFactory(candidate, { recursive: true }, onProgress));
55
57
  } catch {
56
- try { watchers.push(watch(candidate, onProgress)); } catch { /* output still counts as progress */ }
58
+ watchDirectoryTree(candidate, onProgress, watchers, fallback, { watchFactory, readDirectory });
57
59
  }
58
60
  }
59
61
  return () => {
@@ -61,6 +63,23 @@ export function watchProgress(paths, onProgress) {
61
63
  };
62
64
  }
63
65
 
66
+ function watchDirectoryTree(root, onProgress, watchers, fallback, { watchFactory = watch, readDirectory = readdirSync } = {}) {
67
+ const pending = [root];
68
+ const visited = new Set();
69
+ while (pending.length) {
70
+ const directory = pending.pop();
71
+ if (visited.has(directory)) continue;
72
+ visited.add(directory);
73
+ if (fallback.remaining <= 0) return;
74
+ try { watchers.push(watchFactory(directory, onProgress)); fallback.remaining -= 1; } catch { /* output still counts as progress */ }
75
+ let entries;
76
+ try { entries = readDirectory(directory, { withFileTypes: true }); } catch { continue; }
77
+ for (const entry of entries) {
78
+ if (entry.isDirectory()) pending.push(path.join(directory, entry.name));
79
+ }
80
+ }
81
+ }
82
+
64
83
  export function releaseEnvironment({ root, cacheRoot, platform, architecture, app, cacheKey, kind = "release", appRoot, mode = "legacy", env = process.env }) {
65
84
  if (kind !== "release" && kind !== "test") throw new Error(`invalid target kind: ${kind}`);
66
85
  if (mode !== "legacy" && mode !== "shared") throw new Error(`invalid cache mode: ${mode}`);
@@ -75,6 +94,11 @@ export function releaseEnvironment({ root, cacheRoot, platform, architecture, ap
75
94
  SCCACHE_CACHE_SIZE: `${policy.sccacheMaxBytes / 1024 ** 3}G`,
76
95
  SCCACHE_BASEDIRS: [path.resolve(root), path.resolve(appRoot)].join(path.delimiter),
77
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",
78
102
  RIGHT_RELEASE_CACHE_OWNER: "rightkit-v2",
79
103
  };
80
104
  }
@@ -23,20 +23,59 @@ test("progress watcher observes writes in nested Cargo target directories", asyn
23
23
  const root = mkdtempSync(path.join(os.tmpdir(), "right-release-watch-"));
24
24
  const nested = path.join(root, "release", "build", "openssl");
25
25
  mkdirSync(nested, { recursive: true });
26
+ const readyFile = path.join(root, "watch-ready");
27
+ const nestedFile = path.join(nested, "object.lib");
28
+ let resolveReady;
26
29
  let resolveProgress;
30
+ const ready = new Promise((resolve) => { resolveReady = resolve; });
27
31
  const progress = new Promise((resolve) => { resolveProgress = resolve; });
28
- const close = watchProgress([root], resolveProgress);
32
+ const close = watchProgress([root], (_eventType, filename) => {
33
+ const eventName = String(filename ?? "");
34
+ if (eventName === path.basename(readyFile)) resolveReady();
35
+ if (eventName.endsWith(path.basename(nestedFile))) resolveProgress();
36
+ });
29
37
  try {
30
- await writeFile(path.join(nested, "object.lib"), "progress");
31
- await Promise.race([
32
- progress,
33
- new Promise((_, reject) => setTimeout(() => reject(new Error("nested progress event missing")), 2_000)),
34
- ]);
38
+ await new Promise((resolve) => setTimeout(resolve, 50));
39
+ await writeFile(readyFile, "ready");
40
+ await waitForEvent(ready, "watcher readiness event missing");
41
+ await writeFile(nestedFile, "progress");
42
+ await waitForEvent(progress, "nested progress event missing");
43
+ } finally {
44
+ close();
45
+ }
46
+ });
47
+
48
+ test("fallback progress watchers cap open handles when recursive watching is unavailable", () => {
49
+ const root = mkdtempSync(path.join(os.tmpdir(), "right-release-watch-cap-"));
50
+ for (let index = 0; index < 10; index += 1) mkdirSync(path.join(root, `dir-${index}`));
51
+ const opened = [];
52
+ const close = watchProgress([root], () => {}, {
53
+ maxFallbackWatchers: 3,
54
+ watchFactory: (directory, options) => {
55
+ if (options?.recursive) throw new Error("recursive watch unsupported");
56
+ opened.push(directory);
57
+ return { close: () => {} };
58
+ },
59
+ });
60
+ try {
61
+ assert.equal(opened.length, 3);
35
62
  } finally {
36
63
  close();
37
64
  }
38
65
  });
39
66
 
67
+ async function waitForEvent(event, message) {
68
+ let timer;
69
+ try {
70
+ await Promise.race([
71
+ event,
72
+ new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), 5_000); }),
73
+ ]);
74
+ } finally {
75
+ clearTimeout(timer);
76
+ }
77
+ }
78
+
40
79
  test("portable command capture resolves Windows command shims", { skip: process.platform !== "win32" }, () => {
41
80
  assert.equal(commandOutputPortable("pnpm", ["--version"]), expectedPnpmVersion);
42
81
  });
@@ -66,6 +105,9 @@ test("shared release environment is isolated from the app vault", () => {
66
105
  assert.match(env.CARGO_HOME, /RightSuite[\\/]release[\\/]cargo-home$/);
67
106
  assert.equal(env.RIGHT_RELEASE_CACHE_OWNER, "rightkit-v2");
68
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");
69
111
  });
70
112
 
71
113
  test("shared release environment honors the cache policy override", () => {
package/release.mjs CHANGED
@@ -12,6 +12,7 @@ import { assertPrimaryReleaseCheckout, dirtyBuildInputs, resolveReleaseBuildInpu
12
12
  import { collectPreflight, formatPreflight, preflightFailures } from "./preflight.mjs";
13
13
  import { patchTauriBundleType } from "./tauri-bundle-marker.mjs";
14
14
  import { verifyNsisEmbeddedBinary } from "./nsis-payload.mjs";
15
+ import { terminateProcessTree } from "./heavy-command.mjs";
15
16
 
16
17
  const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
17
18
  const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
@@ -29,6 +30,7 @@ const RIGHTKIT_ALLOWED = buildAllowedVersions(
29
30
  const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo);
30
31
  const FORBIDDEN_RIGHTKIT_SPEC = /^(?:git|file|link|workspace):|github|github\.com/i;
31
32
  const WINDOWS_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-nsis-v1";
33
+ const DEFAULT_NOTARIZATION_TIMEOUT_MS = 30 * 60 * 1000;
32
34
 
33
35
  const args = process.argv.slice(2);
34
36
  const opts = {
@@ -344,7 +346,18 @@ async function runPackageScript(pm, script, cwd) {
344
346
 
345
347
  async function runCommand(command, root) {
346
348
  const cwd = path.resolve(root, command.cwd ?? ".");
347
- await run(command.cmd, command.args ?? [], cwd, await commandEnv(command, root), command);
349
+ await run(command.cmd, command.args ?? [], cwd, await commandEnv(command, root), {
350
+ ...command,
351
+ timeoutMs: commandTimeoutMs(command),
352
+ });
353
+ }
354
+
355
+ function commandTimeoutMs(command) {
356
+ if (Object.hasOwn(command, "timeoutMs")) return command.timeoutMs;
357
+ const tokens = [command.cmd, ...(command.args ?? [])].map((value) => String(value));
358
+ return command.notarize === true || command.notarization === true || tokens.some((value) => /notari[sz]/i.test(value))
359
+ ? DEFAULT_NOTARIZATION_TIMEOUT_MS
360
+ : undefined;
348
361
  }
349
362
 
350
363
  async function validateRightKitPackageContract(root, appName) {
@@ -624,18 +637,5 @@ function processCommandLine(pid) {
624
637
  }
625
638
 
626
639
  function killProcessTree(pid) {
627
- if (!pid) return;
628
- if (process.platform === "win32") {
629
- spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
630
- return;
631
- }
632
- const children = spawnSync("pgrep", ["-P", String(pid)], { encoding: "utf8" });
633
- for (const child of (children.stdout ?? "").split(/\s+/).filter(Boolean)) {
634
- killProcessTree(Number(child));
635
- }
636
- try {
637
- process.kill(pid, "SIGKILL");
638
- } catch {
639
- // already gone
640
- }
640
+ terminateProcessTree(pid);
641
641
  }
package/release.test.mjs CHANGED
@@ -16,7 +16,7 @@ function git(cwd, ...args) {
16
16
  return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
17
17
  }
18
18
 
19
- function fixture({ signed = true, publish = false, signingContract = "windows-raw-exe-authenticode-before-nsis-v1", prePackageFiles = ["raw.exe"], packageJson, cargoManifest, cargoConfig, repoCargoConfig, siblingCargoManifest, buildInputs = defaultBuildInputs, platform = "win" } = {}) {
19
+ function fixture({ signed = true, publish = false, signingContract = "windows-raw-exe-authenticode-before-nsis-v1", prePackageFiles = ["raw.exe"], packageJson, packageCommand, cargoManifest, cargoConfig, repoCargoConfig, siblingCargoManifest, buildInputs = defaultBuildInputs, platform = "win" } = {}) {
20
20
  const fixtureRoot = mkdtempSync(path.join(os.tmpdir(), "right-release-test-"));
21
21
  const dir = repoCargoConfig ? path.join(fixtureRoot, "apps", "fixture") : fixtureRoot;
22
22
  mkdirSync(dir, { recursive: true });
@@ -49,7 +49,7 @@ function fixture({ signed = true, publish = false, signingContract = "windows-ra
49
49
  [platform]: {
50
50
  ...(buildInputs ? { buildInputs } : {}),
51
51
  signed,
52
- package: { cmd: "node", args: ["-e", "process.exit(0)"] },
52
+ package: packageCommand ?? { cmd: "node", args: ["-e", "process.exit(0)"] },
53
53
  ...(publish ? { publish: { cmd: "node", args: ["publish-update.mjs"] } } : {}),
54
54
  artifacts: [],
55
55
  ...(platform === "win" ? { signingContract, prePackage: { cmd: "node", args: ["-e", "process.exit(0)"] }, sign: { prePackageFiles, files: ["fixture.exe"] } } : {}),
@@ -159,6 +159,28 @@ test("accepts a tier-neutral internal build", () => {
159
159
  assert.doesNotMatch(result.stdout, /RIGHT_RELEASE_TIER=/);
160
160
  });
161
161
 
162
+ test("notarization commands default to a bounded 30-minute timeout without changing explicit overrides", () => {
163
+ const defaultResult = runRaw(fixture({
164
+ platform: "mac",
165
+ packageCommand: { cmd: "node", args: ["-e", "process.exit(0)", "--notarize"] },
166
+ }), "--platform", "mac", "--dry-run");
167
+ assert.equal(defaultResult.status, 0, defaultResult.stderr);
168
+ assert.match(defaultResult.stdout, /timeout=1800000ms/);
169
+
170
+ const explicitResult = runRaw(fixture({
171
+ platform: "mac",
172
+ packageCommand: { cmd: "node", args: ["-e", "process.exit(0)", "--notarize"], timeoutMs: 1234 },
173
+ }), "--platform", "mac", "--dry-run");
174
+ assert.equal(explicitResult.status, 0, explicitResult.stderr);
175
+ assert.match(explicitResult.stdout, /timeout=1234ms/);
176
+ });
177
+
178
+ test("release worker reuses owned process-tree termination for Windows and remote command descendants", () => {
179
+ const source = readFileSync(release, "utf8");
180
+ assert.match(source, /import \{ terminateProcessTree \} from "\.\/heavy-command\.mjs"/);
181
+ assert.match(source, /function killProcessTree\(pid\) \{\s*terminateProcessTree\(pid\);\s*\}/s);
182
+ });
183
+
162
184
  test("accepts patch and exposes it to the signed package command", () => {
163
185
  const result = run(fixture(), "--tier=patch");
164
186
  assert.equal(result.status, 0, result.stderr);
@@ -10,6 +10,7 @@ import {
10
10
  assertPublishedRightKitCargoDependencies,
11
11
  cargoArguments,
12
12
  cargoExecutable,
13
+ isolatedCargoMetadataEnv,
13
14
  validateRightKitCargoContract,
14
15
  } from "./cargo-contract.mjs";
15
16
  import { assertAsrAdapterPair } from "./asr-artifact-adoption.mjs";
@@ -126,7 +127,7 @@ function readCargoManifestContract(manifestPath, label) {
126
127
  {
127
128
  cwd: path.dirname(manifestPath),
128
129
  encoding: "utf8",
129
- env: { ...process.env, CARGO_HOME: isolatedCargoHome },
130
+ env: isolatedCargoMetadataEnv(isolatedCargoHome),
130
131
  windowsHide: true,
131
132
  },
132
133
  );
@@ -462,15 +463,16 @@ test("RightKit exposes one current version manifest", () => {
462
463
  assert.deepEqual(versions.stagedNpm, {
463
464
  "@rightkit/ax": "0.2.0",
464
465
  "@rightkit/git": "0.2.0",
466
+ "@rightkit/hooks": "0.1.0",
465
467
  "@rightkit/legal": "0.3.0",
466
468
  "@rightkit/legal-ui": "0.1.1",
467
469
  "@rightkit/license": "0.1.6",
468
- "@rightkit/release": "0.2.61",
470
+ "@rightkit/release": "0.2.63",
469
471
  "@rightkit/qa": "0.2.0",
470
472
  });
471
473
  assert.deepEqual(versions.legacyNpm, {
472
474
  "@rightkit/legal-ui": ["0.1.0"],
473
- "@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"],
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", "0.2.62"],
474
476
  "@rightkit/qa": ["0.1.0"],
475
477
  });
476
478
  assert.ok(
@@ -496,12 +498,26 @@ test("RightKit exposes one current version manifest", () => {
496
498
  getCurrentCargoVersionContract();
497
499
  });
498
500
 
499
- test("Cargo metadata uses native rustup on Windows", () => {
500
- assert.equal(cargoExecutable("win32"), "rustup");
501
- assert.deepEqual(cargoArguments(["metadata"], "win32"), ["run", "stable", "cargo", "metadata"]);
501
+ test("Cargo metadata uses managed Cargo directly on Windows", () => {
502
+ assert.equal(cargoExecutable("win32"), "cargo");
503
+ assert.deepEqual(cargoArguments(["metadata"], "win32"), ["metadata"]);
502
504
  assert.equal(cargoExecutable("darwin"), "cargo");
503
505
  });
504
506
 
507
+ test("Cargo metadata delegates controlled storage to managed RightKit", () => {
508
+ const ambient = {
509
+ CARGO_HOME: "/ambient/cargo",
510
+ CARGO_TARGET_DIR: "/ambient/target",
511
+ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock",
512
+ };
513
+ assert.deepEqual(isolatedCargoMetadataEnv("/isolated/cargo", ambient), {
514
+ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock",
515
+ });
516
+ assert.deepEqual(isolatedCargoMetadataEnv("/isolated/cargo", { CARGO_HOME: "/ambient/cargo" }), {
517
+ CARGO_HOME: "/isolated/cargo",
518
+ });
519
+ });
520
+
505
521
  test("license v2 public vector is identical at every portable consumer boundary", () => {
506
522
  const canonical = readFileSync(
507
523
  path.join(workspace, "tools/rightkit/crates/rightkit-license/test-vectors/license-v2.json"),
@@ -15,10 +15,11 @@
15
15
  "stagedNpm": {
16
16
  "@rightkit/ax": "0.2.0",
17
17
  "@rightkit/git": "0.2.0",
18
+ "@rightkit/hooks": "0.1.0",
18
19
  "@rightkit/legal": "0.3.0",
19
20
  "@rightkit/legal-ui": "0.1.1",
20
21
  "@rightkit/license": "0.1.6",
21
- "@rightkit/release": "0.2.61",
22
+ "@rightkit/release": "0.2.63",
22
23
  "@rightkit/qa": "0.2.0"
23
24
  },
24
25
  "legacyNpm": {
@@ -42,7 +43,9 @@
42
43
  "0.2.53",
43
44
  "0.2.54",
44
45
  "0.2.55",
45
- "0.2.56"
46
+ "0.2.56",
47
+ "0.2.61",
48
+ "0.2.62"
46
49
  ],
47
50
  "@rightkit/qa": [
48
51
  "0.1.0"
@@ -0,0 +1,32 @@
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.18.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
+ }
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, platform === "win32" ? "junction" : "dir");
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, platform === "win32" ? "junction" : "dir");
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}`);
@@ -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);