fix: v2.5.6 — Windows panes without sh, UI build without node on PATH

- Windows panes: a WinGet tmux runs native programs and there is no `sh`, so
  every pane's `sh -c` wrapper died at once ("Command failed: tmux set-option
  … mouse on"). paneArgv runs the CLI itself on win32, and a Terminal gets
  tmux's own default shell (newSession drops the `--` for an empty argv).
- Plugin UI build: client deps install with --ignore-scripts and tsc/vite run
  by the running node instead of `npm run build`. On Windows esbuild's
  postinstall went through cmd.exe with no `node` on the server's PATH, both
  installs failed, and Update now kept the previous UI. A test keeps the two
  build steps equal to client/package.json's build script.
- Update all: Atomics.wait instead of a `sleep` binary Windows lacks.
This commit is contained in:
2026-10-06 23:17:42 +07:00
parent 5fe5d51b1b
commit fcf8d8010a
13 changed files with 112 additions and 39 deletions
+34 -11
View File
@@ -455,14 +455,18 @@ describe("buildClient dependency install", () => {
return root;
}
// Records every npm invocation; `fails` names the subcommands that exit non-zero.
// A successful "run build" writes the dist/ the real Vite build would produce.
// Records every invocation — npm's as its args, node's as `node <script> …`;
// `fails` names the npm subcommands that exit non-zero. A successful vite
// build writes the dist/ the real one would produce.
function fakeExec(calls, fails = []) {
return (_cmd, args, cwd) => {
return (cmd, args, cwd) => {
if (cmd === "node") {
calls.push(`node ${path.basename(args[0])} ${args.slice(1).join(" ")}`);
if (args[1] === "build") fs.mkdirSync(path.join(cwd, "dist"), { recursive: true });
return true;
}
calls.push(args.join(" "));
if (fails.includes(args[0])) return false;
if (args[0] === "run") fs.mkdirSync(path.join(cwd, "dist"), { recursive: true });
return true;
return !fails.includes(args[0]);
};
}
@@ -489,10 +493,12 @@ describe("buildClient dependency install", () => {
const result = buildClient({ rt: RT, root: fakeRoot(), exec: fakeExec(calls, ["ci"]) });
assert.equal(result, "built");
assert.deepEqual(
calls.map((c) => c.split(" ")[0]),
["ci", "install", "run"]
);
assert.deepEqual(calls, [
"ci --no-audit --no-fund --include=dev --ignore-scripts",
"install --no-audit --no-fund --include=dev --ignore-scripts",
"node tsc -b",
"node vite.js build",
]);
});
it("installs devDependencies explicitly, because NODE_ENV=production omits them", () => {
@@ -521,11 +527,28 @@ describe("buildClient dependency install", () => {
assert.equal(result, "failed");
assert.equal(
calls.some((c) => c.startsWith("run")),
calls.some((c) => c.startsWith("node")),
false,
"the build must not run on a broken dependency tree"
);
});
it("builds with the running node, no lifecycle script and no PATH lookup (Windows)", () => {
// On Windows a script or a .bin shim runs through cmd.exe and needs `node`
// on PATH; the server's PATH lacked it, so esbuild's postinstall failed and
// every update kept the previous UI. The build here must equal the
// client's own "build" script, run without npm.
assert.equal(
JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "client", "package.json")))
.scripts.build,
"tsc -b && vite build"
);
resetRuntime();
const calls = [];
buildClient({ rt: RT, root: fakeRoot(), exec: fakeExec(calls) });
assert.deepEqual(calls.slice(-2), ["node tsc -b", "node vite.js build"]);
assert.ok(calls.every((c) => !c.startsWith("run")));
});
});
describe("supersededBy — an old cache directory must not act", () => {