diff --git a/docs/specs/dor-cli.md b/docs/specs/dor-cli.md index e3dcb827e..17af3e45f 100644 --- a/docs/specs/dor-cli.md +++ b/docs/specs/dor-cli.md @@ -10,6 +10,8 @@ **Both launchers must set `ELECTRON_RUN_AS_NODE=1` themselves** before `exec "$DORMOUSE_NODE" "$DORMOUSE_CLI_JS"`, or under VS Code `dor` silently does nothing and **exits 0** (rationale). **Dormouse-launched terminals must rely on injected env, never on a globally installed Node**; each launcher's `PATH`-`node` fallback is for developer/manual use. **Both launchers must return the CLI's exit status** (`dor/test/launcher.test.mjs`, including `dor.cmd` on Windows). +**Must supply the current directory as `PWD` from the Windows launcher**, replacing stale inherited values. + Public PTY env: - `DORMOUSE_NODE` — Node runtime the launcher execs; `process.execPath` under VS Code; standalone on Windows: `docs/specs/standalone.md` -> "Windows node subsystem". @@ -33,7 +35,7 @@ The CLI also reads `DORMOUSE_AGENT_BROWSER_BIN` and `DORMOUSE_PLAYWRIGHT_BIN`, t **On Windows the `PATH` prepend must survive Git Bash / MSYS login:** the PTY core strips `ORIGINAL_PATH` from the child env on win32, so a login shell cannot rebuild a pre-prepend `PATH` from it (rationale). No-op for cmd.exe / PowerShell, which never read it. -**A caller cwd must leave the MSYS drive form before it goes on the wire.** Git Bash exports `PWD` as a POSIX path (`/c/Users/…`) that win32 `path.resolve` would mangle into `C:\c\Users\…` and match no Surface. +**Must convert MSYS drive paths to native Windows paths before sending a caller cwd.** Source of truth: `scripts/stage-dor-cli.mjs`, `dor/bin/dor`, `dor/bin/dor.cmd`, `withoutInheritedMsysOriginalPath` in `standalone/sidecar/pty-core.js`, `callerWorkingDirectory` in `dor/src/commands/shared.ts`. diff --git a/dor/bin/dor.cmd b/dor/bin/dor.cmd index d35134627..6b6e81b0c 100644 --- a/dor/bin/dor.cmd +++ b/dor/bin/dor.cmd @@ -1,5 +1,8 @@ @echo off setlocal +rem PowerShell and cmd do not maintain an inherited POSIX PWD. +rem The CLI reads it for relative paths and new terminals. +set "PWD=%CD%" if "%DORMOUSE_NODE%"=="" goto fallback if "%DORMOUSE_CLI_JS%"=="" goto fallback rem Under VS Code, DORMOUSE_NODE is the editor's Electron binary; it only diff --git a/dor/test/launcher.test.mjs b/dor/test/launcher.test.mjs index 9fb04d461..5b6cd485c 100644 --- a/dor/test/launcher.test.mjs +++ b/dor/test/launcher.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { join } from 'node:path'; +import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; // The staged launchers, run exactly as a terminal runs `dor`. DORMOUSE_CLI_JS @@ -13,7 +13,7 @@ const binDir = fileURLToPath(new URL('../bin/', import.meta.url)); const isWindows = process.platform === 'win32'; async function withStubCli(run) { - const dir = await mkdtemp(join(tmpdir(), 'dor-launcher-')); + const dir = await mkdtemp(join(tmpdir(), 'dor launcher-')); const cliJs = join(dir, 'cli.mjs'); await writeFile(cliJs, 'process.exit(Number(process.argv[2]));\n'); try { @@ -23,12 +23,13 @@ async function withStubCli(run) { } } -function runLauncher(cliJs, code) { - const env = { ...process.env, DORMOUSE_NODE: process.execPath, DORMOUSE_CLI_JS: cliJs }; +function runLauncher(cliJs, code, options = {}) { + const env = { ...process.env, DORMOUSE_NODE: process.execPath, DORMOUSE_CLI_JS: cliJs, ...options.env }; if (isWindows) { // Node refuses to spawn a .cmd directly; go through cmd.exe as a shell does. return spawnSync('cmd.exe', ['/d', '/s', '/c', `""${join(binDir, 'dor.cmd')}" ${code}"`], { env, + cwd: options.cwd, windowsVerbatimArguments: true, }); } @@ -44,3 +45,16 @@ for (const code of [0, 7]) { }); }); } + +for (const inheritedPwd of ['C:\\stale\\parent', undefined]) { + test('dor.cmd supplies the caller directory despite ' + (inheritedPwd ? 'stale' : 'missing') + ' PWD', { skip: !isWindows }, async () => { + await withStubCli(async (cliJs) => { + await writeFile(cliJs, 'console.log(JSON.stringify({cwd: process.cwd(), pwd: process.env.PWD}));\n'); + const cwd = dirname(cliJs); + const result = runLauncher(cliJs, 0, { cwd, env: { PWD: inheritedPwd } }); + assert.equal(result.status, 0, String(result.stderr)); + const actual = JSON.parse(String(result.stdout)); + assert.equal(actual.pwd.toLowerCase(), actual.cwd.toLowerCase()); + }); + }); +}