From 1e4a2036b22270370438cd053141dbd18b7cd962 Mon Sep 17 00:00:00 2001 From: Ame Date: Mon, 22 Jun 2026 23:05:41 +0800 Subject: [PATCH] fix(workspaces): ship Windows .cmd twins for the alice CLI shims (issue #364 P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace CLI shims (alice / alice-workspace / alice-uta / traderhub) are extensionless Node scripts — they rely on a Unix shebang. Windows has no shebang concept: it resolves executables on PATH by extension (PATHEXT), and an extensionless file has no association, so every invocation popped a "How do you want to open this file?" dialog inside the workspace PTY. Add a `.cmd` twin per export. cliBinPath() prepends the whole bin dir to PATH and build.files already ships `src/workspaces/cli/**`, so the twins are picked up by PATHEXT with no deploy change. Each `.cmd` invokes its OWN sibling shim (`@node "%~dp0"`), because the shim self-detects its export from argv[1] — pointing at the wrong shim would route to the wrong gateway export. Shims stay byte-identical (the existing drift test still guards that); the .cmd content lives outside them. macOS/Linux ignore .cmd entirely. issue #364's other half (bootstrap.sh exit 127 on Windows) was already resolved by #363, which eliminated bash for built-in templates (bootstrap.mjs on bundled Node + dugite git) rather than fixing the Git-Bash path handling. Reported-by: @lvysssss Closes #364 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/workspaces/cli/bin/alice-uta.cmd | 1 + src/workspaces/cli/bin/alice-workspace.cmd | 1 + src/workspaces/cli/bin/alice.cmd | 1 + src/workspaces/cli/bin/traderhub.cmd | 1 + src/workspaces/cli/shim.spec.ts | 15 +++++++++++++++ 5 files changed, 19 insertions(+) create mode 100644 src/workspaces/cli/bin/alice-uta.cmd create mode 100644 src/workspaces/cli/bin/alice-workspace.cmd create mode 100644 src/workspaces/cli/bin/alice.cmd create mode 100644 src/workspaces/cli/bin/traderhub.cmd diff --git a/src/workspaces/cli/bin/alice-uta.cmd b/src/workspaces/cli/bin/alice-uta.cmd new file mode 100644 index 00000000..4b24cd08 --- /dev/null +++ b/src/workspaces/cli/bin/alice-uta.cmd @@ -0,0 +1 @@ +@node "%~dp0alice-uta" %* diff --git a/src/workspaces/cli/bin/alice-workspace.cmd b/src/workspaces/cli/bin/alice-workspace.cmd new file mode 100644 index 00000000..3604fa04 --- /dev/null +++ b/src/workspaces/cli/bin/alice-workspace.cmd @@ -0,0 +1 @@ +@node "%~dp0alice-workspace" %* diff --git a/src/workspaces/cli/bin/alice.cmd b/src/workspaces/cli/bin/alice.cmd new file mode 100644 index 00000000..62a397e8 --- /dev/null +++ b/src/workspaces/cli/bin/alice.cmd @@ -0,0 +1 @@ +@node "%~dp0alice" %* diff --git a/src/workspaces/cli/bin/traderhub.cmd b/src/workspaces/cli/bin/traderhub.cmd new file mode 100644 index 00000000..8ed8d6cc --- /dev/null +++ b/src/workspaces/cli/bin/traderhub.cmd @@ -0,0 +1 @@ +@node "%~dp0traderhub" %* diff --git a/src/workspaces/cli/shim.spec.ts b/src/workspaces/cli/shim.spec.ts index 3f4b53c2..a25e3592 100644 --- a/src/workspaces/cli/shim.spec.ts +++ b/src/workspaces/cli/shim.spec.ts @@ -27,4 +27,19 @@ describe('CLI shim copies', () => { expect(src).toContain('process.argv[1]') // derives BIN from how it was invoked expect(src).toContain('exportKey') // routes to the per-export gateway path }) + + // Windows has no shebang concept — it resolves executables on PATH by + // extension (PATHEXT). The extensionless shims trigger a "how do you want to + // open this file?" association dialog on every invocation. A `.cmd` twin per + // export fixes it (ANG / issue #364). Each MUST invoke its OWN shim, because + // the shim self-detects its export from argv[1] — a `.cmd` pointing at the + // wrong shim would route to the wrong gateway export. + it('every export ships a Windows `.cmd` twin that runs its own shim', () => { + for (const name of EXPORT_BINARIES) { + const cmd = read(`${name}.cmd`).toString('utf8') + expect(cmd, `${name}.cmd should run node on its sibling shim`) + .toContain(`@node "%~dp0${name}"`) + expect(cmd, `${name}.cmd should forward args`).toContain('%*') + } + }) })