Skip to content

feat: add --dry-run-code flag to exit non-zero when there is work to do#8063

Merged
jdx merged 3 commits intomainfrom
feat/dry-run-code
Feb 8, 2026
Merged

feat: add --dry-run-code flag to exit non-zero when there is work to do#8063
jdx merged 3 commits intomainfrom
feat/dry-run-code

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 8, 2026

Summary

  • Adds a --dry-run-code flag to install, upgrade, prune, uninstall, and use commands
  • When used, behaves like --dry-run (shows what would happen without making changes) but additionally exits with code 1 when there are pending operations
  • Exits 0 when everything is already up-to-date
  • Enables scripting patterns like:
    if ! mise install --dry-run-code --quiet; then
      echo "Installing stuff using mise"
      mise install
    fi

Closes #8060

Test plan

  • mise run test:e2e test_install_dry_run — install dry-run tests including --dry-run-code
  • mise run test:e2e test_use_dry_run — use dry-run tests including --dry-run-code
  • cargo test — all 461 unit tests pass (including CLI flag sort test)
  • mise run lint-fix — all linters pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes CLI exit-code behavior for multiple core commands, which may affect automation that relies on previous dry-run semantics, though the changes are localized and covered by updated e2e tests.

Overview
Adds a new --dry-run-code flag to install, use, upgrade, prune, and uninstall that behaves like --dry-run but exits with status 1 when there is work to do (and 0 when no changes are needed).

Refactors these commands to treat --dry-run and --dry-run-code consistently via shared is_dry_run() checks, skipping side effects (config writes, deletions, shim rebuilds) while optionally computing whether any operations would occur before deciding to exit non-zero.

Updates generated CLI docs/manpages, the mise.usage.kdl spec, Fig completions, and expands e2e coverage (including a new assert_fail_contains) to validate the new exit-code semantics.

Written by Cursor Bugbot for commit 2a305c7. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings February 8, 2026 19:17
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances mise's command-line utility and integration with JavaScript ecosystems. It introduces the --dry-run-code flag, providing a programmatic way for scripts to determine if mise commands would perform any actions, thus enabling more sophisticated automation. Concurrently, mise gains the ability to automatically detect and manage node, npm, pnpm, and yarn versions directly from package.json files, leveraging standard fields like devEngines and packageManager for a more seamless developer experience.

Highlights

  • New --dry-run-code Flag: Introduced for install, upgrade, prune, uninstall, and use commands. This flag behaves like --dry-run but exits with a non-zero code (1) if there are pending operations, and zero (0) if everything is already up-to-date.
  • Enhanced package.json Support: mise now recognizes package.json as an idiomatic version file for node, npm, pnpm, yarn, and bun. It parses devEngines.runtime, devEngines.packageManager, and packageManager fields to determine tool versions.
  • Improved Scripting Capabilities: The --dry-run-code flag enables more robust scripting patterns, allowing users to conditionally execute commands based on whether mise has work to do.
  • New package_json Module: A dedicated module was added to handle the parsing and extraction of version information from package.json files, including a simplify_semver utility.
  • E2E Test Coverage: New end-to-end tests were added to validate the functionality of both the --dry-run-code flag and the package.json version detection.
Changelog
  • e2e/assert.sh
    • Added assert_fail_contains helper for e2e tests.
  • e2e/cli/test_install_dry_run
    • Added tests for install --dry-run-code to verify correct exit codes based on installation status.
  • e2e/cli/test_use_dry_run
    • Added tests for use --dry-run-code to verify correct exit codes for pending configuration changes.
  • e2e/core/test_package_json
    • New e2e test file to validate package.json parsing for node and pnpm versions.
  • registry/npm.toml
    • Updated to include package.json as an idiomatic version file.
  • registry/pnpm.toml
    • Updated to include package.json as an idiomatic version file.
  • registry/yarn.toml
    • Updated to include package.json as an idiomatic version file.
  • src/backend/mod.rs
    • Modified parse_idiomatic_file to delegate package.json parsing to the new package_json module.
  • src/cli/install.rs
    • Implemented --dry-run-code flag logic, including exit code handling for pending installations.
  • src/cli/prune.rs
    • Implemented --dry-run-code flag logic, including exit code handling for pending pruning operations.
  • src/cli/uninstall.rs
    • Implemented --dry-run-code flag logic, including exit code handling for pending uninstallation operations.
  • src/cli/upgrade.rs
    • Implemented --dry-run-code flag logic, including exit code handling for pending upgrade operations.
  • src/cli/use.rs
    • Implemented --dry-run-code flag logic, including exit code handling for pending use command changes.
  • src/config/config_file/idiomatic_version.rs
    • Improved error handling for idiomatic file parsing, logging trace messages for skipped files.
  • src/main.rs
    • Added package_json module to the crate root.
  • src/package_json.rs
    • New module containing PackageJson struct, deserialize_one_or_first helper, runtime_version, package_manager_version methods, and simplify_semver function for parsing package.json content.
  • src/plugins/core/bun.rs
    • Added package.json to idiomatic filenames and updated parse_idiomatic_file to extract bun versions from it.
  • src/plugins/core/node.rs
    • Added package.json to idiomatic filenames and updated parse_idiomatic_file to extract node versions from it.
Activity
  • The pull request introduces a new command-line flag --dry-run-code to several mise commands.
  • It adds support for parsing package.json files to automatically detect tool versions for node, npm, pnpm, yarn, and bun.
  • Extensive end-to-end tests have been added to cover the new --dry-run-code functionality and package.json parsing.
  • All unit tests and linters passed, ensuring code quality and stability.
  • The author used an AI tool, Claude Code, to assist in generating this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a --dry-run-code flag to several CLI commands to support scripting (exit non-zero when changes are needed), and extends idiomatic version-file detection to support package.json for Node/Bun and JS package managers.

Changes:

  • Add --dry-run-code to install, upgrade, prune, uninstall, and use, causing exit code 1 when pending operations exist in dry-run.
  • Add support for reading tool versions from package.json (devEngines / packageManager) via a new package_json module and backend parsing updates.
  • Extend e2e assertions/tests to validate new exit-code behavior and package.json idiomatic-version support.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/plugins/core/node.rs Treat package.json as an idiomatic file for Node and parse version from it.
src/plugins/core/bun.rs Treat package.json as an idiomatic file for Bun and parse version from it.
src/package_json.rs New parser/helpers for extracting tool versions from package.json.
src/main.rs Wire in new package_json module.
src/config/config_file/idiomatic_version.rs Change idiomatic parsing to skip parse errors instead of failing.
src/cli/use.rs Add --dry-run-code and exit(1) when use would change config.
src/cli/upgrade.rs Add --dry-run-code and exit(1) in dry-run path.
src/cli/uninstall.rs Add --dry-run-code and exit(1) when uninstall work exists.
src/cli/prune.rs Add --dry-run-code and exit(1) when prune work exists.
src/cli/install.rs Add --dry-run-code and exit(1) when installs are needed.
src/backend/mod.rs Default package.json parsing for package-manager backends.
registry/yarn.toml Enable package.json as idiomatic version file for yarn.
registry/pnpm.toml Enable package.json as idiomatic version file for pnpm.
registry/npm.toml Enable package.json as idiomatic version file for npm.
e2e/core/test_package_json New e2e test ensuring package.json idiomatic versions work.
e2e/cli/test_use_dry_run Add e2e coverage for mise use --dry-run-code.
e2e/cli/test_install_dry_run Add e2e coverage for mise install --dry-run-code.
e2e/assert.sh Add assert_fail_contains helper used by new tests.
Comments suppressed due to low confidence (1)

src/cli/upgrade.rs:1

  • --dry-run-code currently exits with code 1 unconditionally in the dry-run path, even when there are no upgrades/removals/config updates to perform. Track whether there is actually any work (e.g., !to_remove.is_empty() || !to_upgrade.is_empty() and/or any config update) and only exit(1) when that is true; otherwise exit 0.
use std::sync::Arc;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +38
let version = match plugin.parse_idiomatic_file(&path).await {
Ok(v) => v,
Err(e) => {
trace!("skipping {} for {}: {e}", plugin.id(), path.display());
continue;
}
};
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This change swallows all errors from parse_idiomatic_file for every plugin (including IO errors, permission issues, and malformed file formats), which can silently hide real problems and make failures hard to debug. Prefer only treating a 'no version found' condition as non-fatal (e.g., have parse_idiomatic_file return Ok(\"\") when no version is present in package.json, or introduce a specific error type/marker and only continue for that case while propagating other errors).

Suggested change
let version = match plugin.parse_idiomatic_file(&path).await {
Ok(v) => v,
Err(e) => {
trace!("skipping {} for {}: {e}", plugin.id(), path.display());
continue;
}
};
let version = plugin.parse_idiomatic_file(&path).await?;
if version.trim().is_empty() {
trace!(
"no idiomatic version found for {} in {}",
plugin.id(),
path.display()
);
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +90
let pm_field = self.package_manager.as_deref()?;
let (name, rest) = pm_field.split_once('@')?;
if name != tool_name {
return None;
}
// Strip +sha... suffix
let version = rest.split('+').next().unwrap_or(rest).trim();
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

Parsing packageManager with split_once('@') breaks for scoped package manager identifiers (the string can start with @..., and there may be multiple @ characters). Use a last-split approach (e.g., split on the final @) so scoped names like @yarnpkg/cli-dist@4.1.0 are handled correctly, and then compare the full name against tool_name.

Copilot uses AI. Check for mistakes.
}

// Upper-bound operators don't indicate a version to install
if input.starts_with('<') || input.starts_with("<=") {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

input.starts_with('<') already matches the <= case, making the || input.starts_with(\"<=\") redundant and slightly confusing. Consider simplifying this conditional (or checking \"<=\" first if the intent is to be explicit) to make the logic clearer.

Suggested change
if input.starts_with('<') || input.starts_with("<=") {
if input.starts_with('<') {

Copilot uses AI. Check for mistakes.
Adds a `--dry-run-code` flag to install, upgrade, prune, uninstall,
and use commands. When used, it behaves like `--dry-run` but exits
with code 1 when there are pending operations, enabling scripting
patterns like:

    if ! mise install --dry-run-code --quiet; then
      echo "Installing stuff using mise"
      mise install
    fi

Closes #8060

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces two significant features. First, it adds a --dry-run-code flag to the install, upgrade, prune, uninstall, and use commands. This new flag enhances scripting capabilities by providing a non-zero exit code when there are pending operations, while still performing a dry run. The implementation is consistent and well-integrated across all affected commands.

Second, it adds support for using package.json as an idiomatic version file for node, bun, and various JavaScript package managers. This includes parsing devEngines and packageManager fields to determine tool versions. The logic for parsing these files has also been made more resilient, which is a great improvement to avoid cascading failures.

The changes are accompanied by thorough E2E tests that cover the new functionality. Overall, the code quality is excellent, and the new features are valuable additions. I have no specific recommendations for changes.

- upgrade: exit 1 when outdated, exit 0 when up to date
- prune: exit 1 when prunable, exit 0 when nothing to prune
- uninstall: exit 1 when tools to uninstall, exit 0 when not installed
- install (no args): exit 1 when config has missing tools, exit 0 when all installed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the feat/dry-run-code branch from 1b8b045 to e8e45a4 Compare February 8, 2026 19:23
@github-actions
Copy link

github-actions bot commented Feb 8, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 x -- echo 21.4 ± 0.5 20.2 23.1 1.00
mise x -- echo 22.5 ± 0.8 21.1 28.6 1.05 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 env 20.7 ± 0.7 19.7 28.5 1.00
mise env 21.5 ± 0.5 20.7 23.5 1.04 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 hook-env 21.2 ± 0.3 20.4 22.7 1.00
mise hook-env 22.4 ± 0.7 21.4 25.7 1.06 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.7 ls 19.6 ± 0.6 18.5 25.7 1.00
mise ls 20.3 ± 0.4 19.5 21.8 1.04 ± 0.04

xtasks/test/perf

Command mise-2026.2.7 mise Variance
install (cached) 120ms 119ms +0%
ls (cached) 72ms 73ms -1%
bin-paths (cached) 75ms 78ms -3%
task-ls (cached) 540ms 542ms +0%

@jdx
Copy link
Owner Author

jdx commented Feb 8, 2026

bugbot run

Copy link

@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

@jdx jdx merged commit 0fb325f into main Feb 8, 2026
36 checks passed
@jdx jdx deleted the feat/dry-run-code branch February 8, 2026 20:25
mise-en-dev added a commit that referenced this pull request Feb 9, 2026
### 🚀 Features

- **(node)** support package.json as idiomatic version file by @jdx in
[#8059](#8059)
- **(ruby)** graduate precompiled ruby from experimental (gradual
rollout) by @jdx in [#8052](#8052)
- add --dry-run-code flag to exit non-zero when there is work to do by
@jdx in [#8063](#8063)

### 🐛 Bug Fixes

- **(core)** respect MISE_ARCH override in bun and erlang plugins by
@jdx in [#8062](#8062)
- **(hooks)** resolve 12 community-reported hooks issues by @jdx in
[#8058](#8058)
- accept key=value format in set/add subcommands by @jdx in
[#8053](#8053)

### 📚 Documentation

- bump action versions in GitHub Actions examples by @muzimuzhi in
[#8065](#8065)
- add opengraph meta tags by @jdx in
[#8066](#8066)

### 📦️ Dependency Updates

- upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in
[#8057](#8057)

### 📦 Registry

- add quicktype (npm:quicktype) by @zdunecki in
[#8054](#8054)
- use inline table for test definitions by @jdx in
[#8056](#8056)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
…do (jdx#8063)

## Summary

- Adds a `--dry-run-code` flag to `install`, `upgrade`, `prune`,
`uninstall`, and `use` commands
- When used, behaves like `--dry-run` (shows what would happen without
making changes) but additionally exits with code 1 when there are
pending operations
- Exits 0 when everything is already up-to-date
- Enables scripting patterns like:
  ```bash
  if ! mise install --dry-run-code --quiet; then
    echo "Installing stuff using mise"
    mise install
  fi
  ```

Closes jdx#8060

## Test plan

- [x] `mise run test:e2e test_install_dry_run` — install dry-run tests
including `--dry-run-code`
- [x] `mise run test:e2e test_use_dry_run` — use dry-run tests including
`--dry-run-code`
- [x] `cargo test` — all 461 unit tests pass (including CLI flag sort
test)
- [x] `mise run lint-fix` — all linters pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes CLI exit-code behavior for multiple core commands, which may
affect automation that relies on previous dry-run semantics, though the
changes are localized and covered by updated e2e tests.
> 
> **Overview**
> Adds a new `--dry-run-code` flag to `install`, `use`, `upgrade`,
`prune`, and `uninstall` that behaves like `--dry-run` but exits with
status `1` when there is work to do (and `0` when no changes are
needed).
> 
> Refactors these commands to treat `--dry-run` and `--dry-run-code`
consistently via shared `is_dry_run()` checks, skipping side effects
(config writes, deletions, shim rebuilds) while optionally computing
whether any operations would occur before deciding to exit non-zero.
> 
> Updates generated CLI docs/manpages, the `mise.usage.kdl` spec, Fig
completions, and expands e2e coverage (including a new
`assert_fail_contains`) to validate the new exit-code semantics.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2a305c7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 Features

- **(node)** support package.json as idiomatic version file by @jdx in
[jdx#8059](jdx#8059)
- **(ruby)** graduate precompiled ruby from experimental (gradual
rollout) by @jdx in [jdx#8052](jdx#8052)
- add --dry-run-code flag to exit non-zero when there is work to do by
@jdx in [jdx#8063](jdx#8063)

### 🐛 Bug Fixes

- **(core)** respect MISE_ARCH override in bun and erlang plugins by
@jdx in [jdx#8062](jdx#8062)
- **(hooks)** resolve 12 community-reported hooks issues by @jdx in
[jdx#8058](jdx#8058)
- accept key=value format in set/add subcommands by @jdx in
[jdx#8053](jdx#8053)

### 📚 Documentation

- bump action versions in GitHub Actions examples by @muzimuzhi in
[jdx#8065](jdx#8065)
- add opengraph meta tags by @jdx in
[jdx#8066](jdx#8066)

### 📦️ Dependency Updates

- upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in
[jdx#8057](jdx#8057)

### 📦 Registry

- add quicktype (npm:quicktype) by @zdunecki in
[jdx#8054](jdx#8054)
- use inline table for test definitions by @jdx in
[jdx#8056](jdx#8056)
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.

2 participants