Skip to content

fix(plan): rewrite node_modules/.bin/*.cmd to powershell on Windows#345

Merged
branchseer merged 1 commit intomainfrom
fix/prefer-ps1-over-cmd-shim
Apr 21, 2026
Merged

fix(plan): rewrite node_modules/.bin/*.cmd to powershell on Windows#345
branchseer merged 1 commit intomainfrom
fix/prefer-ps1-over-cmd-shim

Conversation

@fengmk2
Copy link
Copy Markdown
Member

@fengmk2 fengmk2 commented Apr 18, 2026

Summary

Running a node_modules/.bin/*.cmd shim on Windows triggers cmd.exe's "Terminate batch job (Y/N)?" prompt on Ctrl+C, which corrupts the terminal. At plan time, when which() resolves such a shim inside the workspace and a sibling .ps1 exists, rewrite the invocation to run the .ps1 directly through PowerShell — the spawn layer stays untouched and Ctrl+C propagates cleanly.

Closes voidzero-dev/vite-plus#1176

What the rewrite does

program_path: <abs path to pwsh.exe or powershell.exe>
args:         [-NoProfile, -NoLogo, -ExecutionPolicy, Bypass, -File,
               <.ps1 path, relative to task cwd>, ...original_args]

The .ps1 path is stored relative to the task's cwd (with / separators), so SpawnFingerprint.args is portable across machines — no absolute paths leak into cache keys. PowerShell resolves -File <relative> against its own working directory (the task's cwd) and lands on the correct file.

When it fires

Only when all of the following hold:

  1. Extension is .cmd (case-insensitive) — cmd-shim never emits .bat.
  2. Path lives inside the workspace root — global shims like %AppData%\npm\node_modules\.bin\foo.cmd are left alone.
  3. Path's last two components are .bin / node_modules (case-insensitive).
  4. A sibling .ps1 exists.
  5. pwsh.exe or powershell.exe is on PATH.

Any miss and the original .cmd is kept — behavior matches pre-PR.

Tests

  • 6 cross-platform unit tests in vite_task_plan::ps1_shim::tests cover the pure rewrite logic: workspace root, hoisted monorepo subpackage (.. traversal), missing sibling, non-.cmd extensions, .cmd outside .bin, .cmd outside the workspace.
  • Windows-only plan snapshot in plan_snapshots/fixtures/windows_cmd_shim_rewrite/ — a pnpm workspace with a sub-package. Two plan cases (dev_in_subpackage via cwd, dev_filter_from_root via --filter) produce byte-identical snapshots, proving the cache key doesn't depend on how the user navigates to the task. The harness gained a windows_only: bool flag on SnapshotsFile; redact_snapshot gained a pass that maps pwsh / powershell and their absolute host paths to <powershell> so the snapshot doesn't pin a specific runner image.

Test plan

  • cargo clippy --workspace --all-targets + cargo test --workspace green on macOS
  • Windows CI green
  • Manual on Windows: https://github.com/Curtion/report-vite-plus-1, Ctrl+C during vp run dev leaves terminal clean
  • Manual: confirm .cmd fallback on a Windows box with PATH stripped of PowerShell
  • Manual: confirm a globally installed .cmd outside the workspace is not rewritten

Note

Medium Risk
Changes Windows command planning and spawn fingerprints by rewriting certain resolved executables and arguments, which could affect task execution and cache keys on that platform. Scope is constrained to .cmd shims under workspace node_modules/.bin with a .ps1 sibling and a detectable PowerShell host.

Overview
On Windows, planning now rewrites resolved node_modules/.bin/*.cmd shims inside the workspace to run the sibling .ps1 via pwsh.exe/powershell.exe (adding a fixed PowerShell arg prefix and recording the .ps1 path relative to the task cwd), so Ctrl+C no longer triggers cmd.exe's "Terminate batch job" prompt and cache fingerprints reflect the actual invocation.

This adds a dedicated ps1_shim module (using pathdiff) and applies it to both normal spawn commands and synthetic plan requests. Snapshot test infrastructure is updated with per-fixture platform filtering plus PowerShell path/name redaction, and a Windows-only plan snapshot fixture is added to validate stable, portable cache keys across invocation styles.

Reviewed by Cursor Bugbot for commit 09b4dc1. Configure here.

@fengmk2 fengmk2 self-assigned this Apr 18, 2026
fengmk2 added a commit that referenced this pull request Apr 18, 2026
Address review feedback on #345:
- Extract the 5 fixed PowerShell prefix flags to a `const POWERSHELL_PREFIX`
- Build `new_args` via iterator chain instead of manual Vec pushes
- Drop the no-op `let _ = (&program_path, &args);` on non-Windows
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
@fengmk2

This comment was marked as resolved.

fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
Comment thread crates/vite_task_plan/src/plan.rs Outdated
@fengmk2

This comment was marked as resolved.

fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
cursor[bot]

This comment was marked as resolved.

@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 18, 2026

@cursor review

fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 18, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 4f14ebe. Configure here.

@fengmk2 fengmk2 changed the title fix(plan): prefer .ps1 over .cmd shim on Windows fix(spawn): prefer .ps1 over .cmd shim on Windows Apr 18, 2026
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 18, 2026

before

image

after

image

fengmk2 added a commit that referenced this pull request Apr 18, 2026
Address review feedback on #345:
- Extract the 5 fixed PowerShell prefix flags to a `const POWERSHELL_PREFIX`
- Build `new_args` via iterator chain instead of manual Vec pushes
- Drop the no-op `let _ = (&program_path, &args);` on non-Windows
@fengmk2 fengmk2 force-pushed the fix/prefer-ps1-over-cmd-shim branch from 5ce6c62 to 3e1fc38 Compare April 18, 2026 14:25
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 18, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3e1fc38. Configure here.

@fengmk2 fengmk2 changed the title fix(spawn): prefer .ps1 over .cmd shim on Windows fix: route node_modules/.bin .cmd shims through PowerShell on Windows Apr 18, 2026
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 18, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 26021ca. Configure here.

@fengmk2 fengmk2 changed the title fix: route node_modules/.bin .cmd shims through PowerShell on Windows refactor: prefer .ps1 over .cmd shim on Windows Apr 18, 2026
@fengmk2 fengmk2 changed the title refactor: prefer .ps1 over .cmd shim on Windows fix: prefer .ps1 over .cmd shim on Windows Apr 18, 2026
Comment thread crates/vite_task_plan/src/ps1_shim.rs Outdated
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 19, 2026

@cursor review

@fengmk2 fengmk2 marked this pull request as ready for review April 19, 2026 07:32
@fengmk2 fengmk2 force-pushed the fix/prefer-ps1-over-cmd-shim branch from ea43983 to 24f2a8f Compare April 20, 2026 06:27
@fengmk2 fengmk2 changed the title fix: prefer .ps1 over .cmd shim on Windows fix(plan): rewrite node_modules/.bin/*.cmd to powershell on Windows Apr 20, 2026
@fengmk2 fengmk2 changed the title fix(plan): rewrite node_modules/.bin/*.cmd to powershell on Windows fix(plan): rewrite node_modules/.bin/*.cmd to powershell on Windows Apr 20, 2026
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 20, 2026

@cursor review

@fengmk2 fengmk2 force-pushed the fix/prefer-ps1-over-cmd-shim branch from 24f2a8f to 892af62 Compare April 20, 2026 06:32
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 24f2a8f. Configure here.

@fengmk2 fengmk2 marked this pull request as ready for review April 20, 2026 06:41
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 20, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 20, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 20, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
Comment thread crates/vite_task_plan/src/ps1_shim.rs Outdated
Comment thread crates/vite_task_plan/tests/plan_snapshots/main.rs Outdated
@fengmk2 fengmk2 force-pushed the fix/prefer-ps1-over-cmd-shim branch 2 times, most recently from 6c3d40c to a23f585 Compare April 21, 2026 01:30
Running a `node_modules/.bin/*.cmd` shim on Windows triggers `cmd.exe`'s
"Terminate batch job (Y/N)?" prompt on Ctrl+C, which corrupts the
terminal (backspace prints ^H, Ctrl+C prints ^C, input sluggish).

At plan time, when `which()` resolves a `.cmd` shim inside the
workspace and a sibling `.ps1` exists, rewrite the invocation to run
the `.ps1` directly through PowerShell. The spawn layer stays
untouched — it runs whatever the plan records, 1-for-1. Ctrl+C then
propagates cleanly without the intermediate `cmd.exe` hop.

The rewrite records:

    program_path: <abs path to pwsh.exe or powershell.exe>
    args:         [-NoProfile, -NoLogo, -ExecutionPolicy, Bypass, -File,
                   <.ps1 path relative to task cwd>, ...original_args]

The `.ps1` path is stored relative to the task's cwd (with `/`
separators), so `SpawnFingerprint.args` is portable across machines —
no absolute paths leak into cache keys. PowerShell resolves
`-File <relative>` against its own working directory (the task's
cwd) and lands on the correct file.

The rewrite fires only when all of the following hold:
  1. Extension is `.cmd` (case-insensitive) — cmd-shim never emits `.bat`.
  2. Path lives inside the workspace root — global shims like
     `%AppData%\npm\node_modules\.bin\foo.cmd` are left alone.
  3. Path's last two components are `.bin` / `node_modules`
     (case-insensitive).
  4. A sibling `.ps1` exists.
  5. `pwsh.exe` or `powershell.exe` is on PATH.

If any miss, the original `.cmd` is kept — behavior matches pre-PR.

Tests
-----
- 6 cross-platform unit tests in `vite_task_plan::ps1_shim::tests`
  cover the pure rewrite logic (root cwd, hoisted monorepo subpackage
  with `..` traversal, missing sibling, non-.cmd extensions, outside
  `.bin`, outside workspace).
- Windows-only plan snapshot in
  `plan_snapshots/fixtures/windows_cmd_shim_rewrite/` — a pnpm
  workspace with a sub-package. Two plan cases (`dev_in_subpackage`
  via cwd, `dev_filter_from_root` via --filter) produce byte-identical
  snapshots, proving the cache key doesn't depend on how the user
  navigates to the task. The harness gained a `windows_only: bool`
  flag on `SnapshotsFile`; `redact_snapshot` gained a pass that maps
  `pwsh` / `powershell` and their absolute host paths to
  `<powershell>` so the snapshot doesn't pin a specific runner image.

Closes voidzero-dev/vite-plus#1176
@fengmk2 fengmk2 force-pushed the fix/prefer-ps1-over-cmd-shim branch from a23f585 to 09b4dc1 Compare April 21, 2026 01:33
@fengmk2
Copy link
Copy Markdown
Member Author

fengmk2 commented Apr 21, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 09b4dc1. Configure here.

@branchseer branchseer merged commit c45e5e7 into main Apr 21, 2026
10 checks passed
@branchseer branchseer deleted the fix/prefer-ps1-over-cmd-shim branch April 21, 2026 02:17
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Pulls in voidzero-dev/vite-task#345 which prefers .ps1 shims over .cmd
on Windows to avoid the "Terminate batch job (Y/N)?" prompt and terminal
corruption on Ctrl+C during `vp run dev`.

Closes #1176
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up the cache-portability fix for voidzero-dev/vite-task#345
(PowerShell rewrite moved from plan layer to spawn layer).
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
Picks up voidzero-dev/vite-task#345 fix for missing `which` dep on
Windows after the module move.
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Apr 21, 2026
…inal corruption (#1414)

## Summary

Running a `node_modules/.bin/*.cmd` shim on Windows — what `vp run
<script>` does when resolving `vite`, `tsc`, etc. — triggers `cmd.exe`'s
*"Terminate batch job (Y/N)?"* prompt on Ctrl+C. That prompt leaves the
terminal in a corrupt state: backspace prints `^H`, Ctrl+C prints `^C`,
input becomes sluggish.

Pulls in voidzero-dev/vite-task#345 (merged as `c45e5e72`), which
teaches the plan layer to rewrite the invocation at plan time:

```
program_path: <abs path to pwsh.exe or powershell.exe>
args:         [-NoProfile, -NoLogo, -ExecutionPolicy, Bypass, -File,
               <.ps1 path relative to task cwd>, ...original_args]
```

PowerShell resolves `-File <relative>` against its own working directory
(= the task's cwd) and lands on the correct `.ps1`, so Ctrl+C propagates
cleanly without the `cmd.exe` hop. The `.ps1` path is **cwd-relative**
so `SpawnFingerprint.args` stays portable — no absolute paths leak into
cache keys.

The rewrite only fires when **all** of these hold: extension is `.cmd`,
path lives inside the workspace root, the two last path components are
`.bin` / `node_modules` (case-insensitive), a sibling `.ps1` exists, and
`pwsh.exe` / `powershell.exe` is on PATH. Any miss keeps the original
`.cmd` path — behavior matches pre-merge.

Also includes a previously-pushed CI fix: the musl test job disables
`crt-static` so `fspy_preload_unix`'s cdylib build doesn't fail after
voidzero-dev/vite-task#344 made that crate an unconditional build-dep.

Closes #1176

## Test plan

- [x] `cargo check --workspace` clean on macOS
- [x] vite-task CI green on the merged PR (includes new Windows-only
plan snapshot verifying the rewrite end-to-end across `cwd` and
`--filter` invocations)
- [ ] vite-plus CI green
- [ ] Manual on Windows: reproduce with
https://github.com/Curtion/report-vite-plus-1; Ctrl+C during `vp run
dev` should leave the terminal clean
- [ ] Manual: confirm `.cmd` fallback on a Windows box with PATH
stripped of PowerShell
- [ ] Manual: confirm a globally installed `.cmd` outside the workspace
is *not* rewritten
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pressing Ctrl+C after vp run dev, pnpm run dev, or npm run dev leaves the terminal in a broken state on Windows

2 participants