Repository navigation
feat(generate): write Windows launchers beside generated stubs - #11888
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughTool and task stub generation now creates or removes eligible Windows ChangesWindows stub launchers
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Generation as stub generation
participant Eligibility as launcher eligibility
participant Launcher as .cmd launcher
participant Mise as mise command
Generation->>Eligibility: Check platform metadata and stub name
Eligibility-->>Generation: Return launcher decision
Generation->>Launcher: Write or remove generated .cmd file
Launcher->>Mise: Forward command and arguments
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/dev-tools/tool-stubs.md`:
- Around line 307-313: Update the launcher-generation documentation to state
that eligible stubs receive a sibling launcher unless the stub name already ends
in .cmd, .bat, or .exe; retain the existing Windows-platform eligibility rules
and extension-less stub behavior.
In `@e2e-win/tool_stub.Tests.ps1`:
- Around line 56-58: Make the “gives the same result through mise tool-stub
directly” test self-contained by creating and generating jqstub in its own setup
or shared BeforeAll block before invoking mise tool-stub. Preserve the existing
direct invocation and expected jq-1.7.1* assertion, without relying on the
preceding It block.
- Around line 3-10: Update the suite’s BeforeAll/AfterAll setup to save the
original MISE_TRUSTED_CONFIG_PATHS value before overriding it, then restore that
saved value during cleanup; if it was null, remove the environment variable
instead of assigning null. Keep the existing location restoration unchanged.
In `@src/cli/generate/tool_stub.rs`:
- Around line 150-156: Update write_windows_launcher to remove the managed
sibling .cmd launcher when wants_windows_launcher returns false, while
preserving the existing early return for invalid filenames. Add a transition
test that generates a Windows-capable stub, rewrites the same stub as Unix-only,
and verifies the launcher is removed.
- Around line 157-164: Update the Windows launcher content generated in the
tool-stub path to reference the invoked batch file via `%~dpn0` instead of
interpolating `{name}`, ensuring percent-delimited stub names are not expanded
by cmd.exe. Add a Windows-specific test covering a stub name containing `%` and
verify the generated launcher invokes the extensionless path correctly.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 6c10aaa8-8188-47b1-b4a7-96bd10802ff8
📒 Files selected for processing (4)
docs/dev-tools/tool-stubs.mde2e-win/tool_stub.Tests.ps1e2e/generate/test_generate_tool_stubsrc/cli/generate/tool_stub.rs
2dd4ff9 to
6cb9b0b
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e-win/tool_stub.Tests.ps1`:
- Line 37: Update the expected launcher content assertion in the tool-stub test
to match the generator’s fixed path behavior: expect `%~dpn0` instead of
`%~dp0plainstub` while preserving the rest of the command pattern.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 406eaa3c-15eb-47fe-ab6f-1d5510780d2f
📒 Files selected for processing (4)
docs/dev-tools/tool-stubs.mde2e-win/tool_stub.Tests.ps1e2e/generate/test_generate_tool_stubsrc/cli/generate/tool_stub.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/dev-tools/tool-stubs.md
- src/cli/generate/tool_stub.rs
5dbdcfe to
8df8e32
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
e2e/generate/test_generate_task_stubs (1)
36-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover launcher cleanup and handwritten launcher preservation.
The test creates
legacy-bin/legacy.cmdat Line 50 before it migrateslegacy-bin/legacyinto a directory at Line 69. Add an assertion that a matching generated launcher is removed. Add a separate case that replaces the launcher with user content and asserts that generation preserves it. This validates the cleanup contract insrc/cli/generate/task_stubs.rs:135-146.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/generate/test_generate_task_stubs` around lines 36 - 43, Extend the task-stubs generation test around the legacy launcher migration: after creating legacy-bin/legacy.cmd and migrating legacy-bin/legacy into a directory, assert the matching generated launcher is removed; then replace that launcher with handwritten content, rerun generation, and assert the content remains unchanged. Keep the existing generated nested-launcher assertions intact and cover the cleanup behavior implemented by task-stubs generation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/tips-and-tricks.md`:
- Line 77: Update the Windows launcher example in the documentation to use the
PowerShell-valid path .\bin\test.cmd, or explicitly identify the command as
intended for cmd.exe while retaining the existing behavior description.
In `@src/cli/generate/mod.rs`:
- Around line 77-83: Update cmd_quote to escape cmd.exe metacharacters,
including ampersands, and percent signs using batch-file escaping so
%NAME%-style path components are not expanded even when quoted. Preserve
existing whitespace quoting and add coverage for paths containing & and
%PATH%-like components.
- Around line 72-74: The generated launcher contract is missing ownership
validation, allowing unrelated or user-authored batch files to be overwritten or
accepted. In src/cli/generate/mod.rs lines 72-74, update windows_launcher_body
to include a stable generated-launcher marker; in src/cli/generate/task_stubs.rs
lines 76-78, make overwrite logic require that marker and return an error
otherwise; in src/cli/generate/task_stubs.rs lines 326-334, require both the
marker and a .cmd path when validating generated launchers.
---
Nitpick comments:
In `@e2e/generate/test_generate_task_stubs`:
- Around line 36-43: Extend the task-stubs generation test around the legacy
launcher migration: after creating legacy-bin/legacy.cmd and migrating
legacy-bin/legacy into a directory, assert the matching generated launcher is
removed; then replace that launcher with handwritten content, rerun generation,
and assert the content remains unchanged. Keep the existing generated
nested-launcher assertions intact and cover the cleanup behavior implemented by
task-stubs generation.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 865676a0-2d2d-436f-b2e4-92ba3521a011
📒 Files selected for processing (6)
docs/tips-and-tricks.mde2e-win/task_stubs.Tests.ps1e2e/generate/test_generate_task_stubssrc/cli/generate/mod.rssrc/cli/generate/task_stubs.rssrc/cli/generate/tool_stub.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/cli/generate/tool_stub.rs
f20c42b to
67804ea
Compare
67804ea to
acb63ca
Compare
Greptile SummaryThe PR adds Windows
Confidence Score: 5/5The PR appears safe to merge. The current code validates tool-stub launcher ownership before modifying either file and removes migrated task launchers using the stable ownership marker, so no blocking failure remains from the prior findings. Important Files Changed
Reviews (4): Last reviewed commit: "feat(generate): write Windows launchers ..." | Re-trigger Greptile |
acb63ca to
cdfb17d
Compare
cdfb17d to
1c5bfb6
Compare
f4f21d6 to
03bcf94
Compare
Both kinds of stub mise generates are shebang scripts, which Windows cannot execute, and nothing else was written beside them -- so `mise generate tool-stub` and `mise generate task-stubs` produced files that cannot be run on Windows even though the features themselves work there. Only the launch path was missing. Write a `<name>.cmd` next to each stub. The extension-less stub stays, so Git Bash and Cygwin keep using the shebang -- the same two-file shape shims already take on Windows. Tool stubs decide from their own contents, not the host OS: a stub is authored once and committed, and the Windows user who runs it cannot regenerate it, so a host check would never fire for the case worth fixing. One is written when the stub names a windows platform or names none at all, and not when it ships only for other platforms. Task stubs have no platform restriction and always get one. A launcher that is no longer warranted is removed, and only when it matches what mise would have written, so a hand-written one is left alone. task-stubs also had to learn to recognise its own launchers: the stub-tree validator refuses to replace a directory containing anything it did not write, so without that, regenerating a nested tree would fail.
03bcf94 to
0cda500
Compare
Both kinds of stub mise generates are shebang scripts, which Windows cannot execute. Nothing else was written beside them, so on Windows they could be generated but never run.
Problem
Measured on v2026.8.2, windows-x64:
In both cases the directory holds only the shebang file.
file::make_executableis a no-op on Windows, so the generators emit nothing platform-specific.The features themselves work there.
mise tool-stub <file>parses and runs a stub correctly on Windows — I got as far as a 404 from a deliberately fake URL, so everything up to the download happened. Only the launch path was missing.This looks unimplemented rather than declined:
[platforms.windows-x64]in the docs,--platform-bin windows-x64:tool.exein the CLIchmod +x ./bin/my-tool/./bin/teste2e-wincoverage for either stub kindThe task-stub case is the sharper one: tips-and-tricks recommends
mise generate task-stubsprecisely so "contributors can run project tasks without installing mise first", and then./bin/testdoes not run for a contributor on Windows.What this changes
A
.cmdlauncher written beside each generated stub, mirroring what the stub itself does:The second line is an ownership marker; the quoting on the task-stub line is unconditional. Both
came out of review — see "What review changed".
%*forwards the argument text with quoting intact, and cmd returns the last command's exit code when the script ends. The extension-less stub is still written, so Git Bash and Cygwin keep working through the shebang — the same two-file shapeshims.rsalready uses on Windows.The naming rule and body shape live in
generate/mod.rsso both generators share them; what differs is only the command each launcher runs.%~dpn0for tool stubs, and why%~dpn0is the launcher's own drive+path+name with the extension dropped — exactly the stub beside it — so the stub's name is never written into the batch. A batch file drops%xwhenxis not a parameter, so a stub nameda%bwould otherwise have had its path computed asaband silently missed:(A name containing a full
%VAR%pair is a different matter:cmd.exeexpands it in the path it is handed, so such a launcher cannot be reached at all, whatever it contains. That is a Windows constraint, not something the launcher can work around.)Task-stub launchers cannot use this trick — a nested task's stub is
bin/build/allwhile the task isbuild:all— so those embed the task name, and--mise-binis embedded as given.Which tool stubs get one
Decided from the stub's contents, not the host OS:
[platforms.windows-*]entrytool = "python", or a bareurl)Generating only when running on Windows would miss the case that matters. A stub is authored once and committed; the Windows user who runs it is not its author and cannot regenerate it — they do not have the original
--url/--version. Generating unconditionally would instead drop a.cmdinto projects that ship for Linux only. Reading the stub avoids both.Task stubs have no platform restriction — a task runs wherever mise does — so they always get one.
The platform check parses each key with
Platform::parserather than matching the string, sowindows-arm64counts and a futurewindows-x64-<qualifier>keeps counting. There is a unit test for the arm64 case precisely because awindows-x64substring check would pass everything else and still be wrong there.Stale launchers are removed
If a tool stub stops declaring Windows, or a task stub is migrated away, the launcher goes with it — otherwise it keeps running against something that no longer exists. Only a file matching what mise would have written is removed, so a hand-written
.cmdis left alone.One thing this had to teach the task-stub validator
validate_generated_stub_treerefuses to replace a stub directory containing anything it did not write. Dropping.cmdfiles into a nested stub tree would therefore have broken regeneration — the secondmise generate task-stubswould bail. Launchers are now recognised as generated (is_generated_launcher, shaped like the existingis_generated_task_stub), and the e2e regenerates a nested tree specifically to cover it.They are deliberately not counted towards the tree's file count: a directory holding nothing but launchers has no stubs left and should still be reported as empty.
A
.cmdsitting at a launcher path that is not ours now stops the run withcannot write Windows launcher because … is not a generated launcher.bin/deploy.cmdis a plausible thing to have written by hand next to abin/deploystub, and silently replacing it would be the worst outcome. The check runs during validation rather than at the write, so it fails before anything is written instead of leaving a half-generatedbin/.What review changed
%was expanded inside the launcher, including inside quotes.--mise-bin 'C:\%FOO%\mise.exe'produced a launcher that ranC:\INJECTED\mise.exe— measured against a realcmd.exe. A literal%has to be written%%, and that is whatcmd_quotenow does, for the task name as well as the path: the name is interpolated into the same command line, so a%or&in it breaks the launcher exactly the way one in the path does.The quoting is also unconditional now rather than applied only when a metacharacter is present. Unquoted, a path containing
&runs the text before it as a command ('…\cmdtest\a' is not recognized); inside"…"cmd stops treating& | < > ^ ( )as syntax. A quoted bare name still resolves throughPATH— verified — so the quotes never cost anything, and one rule is easier to keep correct than a predicate over cmd's metacharacter set.Ownership is a marker, not a shape.
is_generated_launcheroriginally recognised our launchers by their shape:@echo off, then a line ending in%*. That also matches a batch file someone wrote by hand, and the consequence of a false positive is deleting it. Every generated body now carriesrem generated by miseas its second line, and ownership rests on that. A unit test asserts the marker is present in whatever the generators produce, not only in one hand-written example.And then two places were not using that marker. Both came out of a later review, and both were inconsistencies inside this PR rather than new ideas:
tool-stubwrote its launcher unconditionally. The removal branch three lines below it already checked the existing contents before deleting, with a comment saying a launcher the user wrote is left alone — while the write above it clobbered one.task-stubsalready had the guard (validate_launcher_path);tool-stubnow has the same one, and it runs before the stub is written, so a launcher that is not ours stops the run rather than leaving a regenerated stub whose launcher was silently skipped. On Windows the stub is not executable on its own, so the.cmdis what actually runs — skipping it is the worse half of the failure.task-stubsremoved launchers by comparing bodies against the ones this run would produce. A launcher body embeds--mise-binand the task name, so a run that changed either would not recognise what it wrote last time and would leave<task>.cmdbehind, still runnable and still pointing at the old mise. Measured: a stale"C:\old\mise.exe" run buildisfalseunder the comparison andtrueunder the marker.is_generated_launchernow lives ingenerate/mod.rsbeside the marker so both generators share one predicate, and it lost the" run "requirement it used to carry — that would have excludedtool-stub's own launcher (mise tool-stub "%~dpn0").Tests
Unit — the shared helpers (
windows_launcher_path,windows_launcher_body,cmd_quote) and the tool-stub platform rule, includingwindows-arm64-only and the executable-extension guard. In their own modules at the end of their files so they compile and run on every platform.is_generated_launcheris tested in both directions, since a false positive here deletes a file mise did not write: four bodies that differ from each other and from anything a current run would produce (a quoted--mise-bin, atool-stublauncher, a different binary entirely) are recognised, while the controls are not — including the one someone would plausibly hand-write for the same purpose (@echo off+mise run hello %*, no marker) and a launcher with an extra line appended, which is not ours to delete either.e2e (unix) — both existing generate tests gain both directions: a launcher appears for eligible stubs and does not for a unix-only tool stub. Without the negative case the positive one would pass just as well if launchers were written unconditionally. The task-stub test also regenerates a nested tree, which is what catches the validator interaction above.
e2e-win (new) —
tool_stub.Tests.ps1andtask_stubs.Tests.ps1, the first stub coverage on Windows. Both actually run something through the launcher (.\jqstub.cmd --version,.\bin\hello.cmd) rather than only asserting a file exists.For context on that side of the suite: Windows e2e has 37 tests against 831 on Unix. This adds two files to it.
Not in this PR
mise generate bootstrap. It writes a bash script whose whole purpose is running before mise is installed, so a.cmdthat callsmisecannot stand in for it. That gap was feat(generate): add --windows to write a Windows bootstrap launcher #11919, which has since merged and emits a Windows form of the bootstrap script, so the two halves now pair on Windows. This branch is rebased onto that, and the tips-and-tricks paragraph is rewritten to describe both rather than to warn that the bootstrap half has no Windows form — that caveat is no longer true.mise generate git-pre-commit. Not a gap: git ships its ownshon Windows and runs hooks with it..ps1wrapper..ps1is not in the defaultPATHEXT, somystubwould not resolve to it.windows_shim_mode(exe/file,mise-shim.exe) is a separate design.Summary by CodeRabbit
New Features
.cmdlaunchers are now generated for eligible tool and task stubs.Documentation
Tests