Skip to content

feat(npm): apply install_before to transitive dependencies#8851

Merged
jdx merged 13 commits intojdx:mainfrom
risu729:feat/npm-transitive-install-before
Apr 4, 2026
Merged

feat(npm): apply install_before to transitive dependencies#8851
jdx merged 13 commits intojdx:mainfrom
risu729:feat/npm-transitive-install-before

Conversation

@risu729
Copy link
Copy Markdown
Contributor

@risu729 risu729 commented Apr 2, 2026

Summary

  • validate the selected top-level npm: package version against the effective install_before cutoff and fail if it was released on or after that cutoff
  • forward the same cutoff into transitive dependency resolution during install for all npm backend package managers

Details

  • exact npm:<pkg>@<version> installs no longer bypass install_before; they now fail closed when the selected version is newer than the cutoff
  • transitive dependency age gating uses each package manager's native CLI form:
    • npm: --before <timestamp>
    • bun: --minimum-release-age <seconds>
    • pnpm: --config.minimumReleaseAge=<minutes>
  • mise now forwards those package-manager-specific flags without a runtime version pre-check
  • npm intentionally stays on --before, not --min-release-age, because npm implements min-release-age by translating it into before, and mise already has an exact timestamp cutoff

Tests / docs

  • add unit coverage for cutoff precedence, transitive CLI arg generation, and exact-version cutoff failures
  • extend npm backend e2e coverage to assert the package-manager-specific transitive age-gating args
  • update npm backend docs and install_before docs; docs/tips-and-tricks.md now points to the npm backend docs for package-manager-specific details
  • rerun mise run render

Question For Maintainer

I changed exact npm:<pkg>@<version> installs to fail closed when the selected top-level version is newer than install_before, instead of letting exact top-level versions through and only gating transitives. Is that the behavior you want merged, or would you prefer exact top-level versions to bypass the cutoff and apply it only to transitive dependencies?

Notes

  • mise run render completed with the existing cache write warnings under /home/risu/.cache/mise/... due to the read-only environment
  • I also attempted focused cargo test verification, but this environment was unreliable about returning final harness completion after compiler subprocesses exited
  • I manually tested older package manager versions with mise x; older pnpm and bun accepted the forwarded flags, while older npm still broke on --before, but this change intentionally removes the preflight version gate instead of enforcing runtime minimums in mise

Copy link
Copy Markdown
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 extends the install_before setting to apply to transitive dependencies when using the npm backend. It introduces logic to detect the version of the active package manager (npm, bun, or pnpm) and append the corresponding CLI flags for release-age filtering during installation. The PR includes updated documentation, E2E tests, and a refactor of the effective_before_date resolution logic. Feedback suggests optimizing performance by caching the probed package manager version to avoid repeated binary executions.

Comment thread src/backend/npm.rs Outdated
Comment on lines +312 to +316
async fn probe_package_manager_version(
&self,
config: &Arc<Config>,
package_manager: NpmPackageManager,
) -> Result<String> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Executing the package manager binary to probe its version for every single package installation is inefficient, especially when installing multiple npm: tools in a single session. Since NPMBackend instances are typically reused or cached within the Toolset, consider caching the probed version in a field on the NPMBackend struct (e.g., using a TokioMutex<Option<String>> or OnceCell) to avoid redundant process executions.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Yeah I think this is worthwhile

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we can fully remove the version check? I think it's enough to document this in the docs to use the version support flags.
pnpm and bun don't fail with unknown flags; npm does, but I think we don't need to care about versions 7 years old.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 2, 2026

Greptile Summary

This PR extends the install_before supply-chain guard to cover transitive npm dependencies, not just top-level version selection. It adds a before_date: Option<Timestamp> field to InstallContext, extracts the effective cutoff once in install_single_tool (via the new effective_before_date helper in tool_request.rs), then passes it through to each package manager's install invocation as a native CLI flag:

  • npm--before <ISO-8601 timestamp>
  • bun--minimum-release-age <seconds>
  • pnpm--config.minimumReleaseAge=<minutes>

I verified the units against authoritative documentation:

  • bun --minimum-release-age accepts seconds — the code passes elapsed_seconds_ceil (seconds). ✅
  • pnpm minimumReleaseAge accepts a bare integer in minutes (confirmed from pnpm 10.16 docs). The code computes seconds.div_ceil(60) and passes an integer. ✅
  • npm --before accepts an ISO-8601 date string. The code passes before_date.to_string() which produces \"2024-01-02T03:04:05Z\". ✅

The version numbers cited in npm.md (bun ≥ 1.3.0 from October 2025, pnpm ≥ 10.16.0 from September 2025) are accurate.

Minor notes:

  • effective_before_date is called twice per install (once in install_single_tool, once inside tr.resolve()), but the second call is a no-op because resolve_options.before_date is already set.
  • Docs correctly distinguish "most backends skip pinned versions" from "npm fails closed for pinned versions newer than the cutoff."

Confidence Score: 5/5

Safe to merge; all logic is correct and the only new finding is a P2 test-coverage gap for the legacy-bun test cases.

I verified the units for all three package managers against official documentation (bun: seconds, pnpm: bare-integer minutes, npm: ISO-8601 timestamp) — all correct. The effective_before_date priority chain is sound and fully unit-tested. The only new finding is a P2 suggestion about the global MISE_INSTALL_BEFORE=1d affecting untested legacy-bun tests. Pre-existing concerns from prior review threads (pnpm unit format, elapsed_seconds_ceil panic path, /tmp log isolation) are either confirmed correct or already flagged elsewhere.

e2e/backend/test_npm_package_manager — tests 4 and 5 implicitly rely on bun silently accepting or ignoring --minimum-release-age; no assertion guards them.

Important Files Changed

Filename Overview
src/backend/npm.rs Adds build_transitive_release_age_args and elapsed_seconds_ceil; correctly converts the before_date cutoff to seconds (bun), minutes (pnpm), and ISO-8601 string (npm); unit tests cover all three paths.
src/toolset/tool_request.rs Adds effective_before_date with correct three-level priority (CLI opts → per-tool option → global setting) and comprehensive unit tests for each branch.
src/install_context.rs Adds before_date: Option field to InstallContext; straightforward struct addition, no issues.
src/toolset/toolset_install.rs Extracts before_date via effective_before_date before calling tr.resolve() and threads it into InstallContext; double-call of effective_before_date is redundant but harmless.
src/cli/install_into.rs Correctly plumbs before_date into InstallContext for the install-into subcommand; handles the tvr is None branch correctly.
e2e/backend/test_npm_package_manager Sets MISE_INSTALL_BEFORE=1d globally and adds debug-log assertions for per-package-manager flags; legacy bun tests 4 and 5 silently rely on bun accepting or ignoring --minimum-release-age without explicit assertions.
docs/dev-tools/backends/npm.md New section accurately documents the forwarded flags and minimum package-manager versions; version numbers confirmed correct against upstream changelogs.
settings.toml Updated install_before description to reflect most-backends wording and npm-specific transitive behavior; no issues.
docs/tips-and-tricks.md Adds a pointer to the npm backend docs for package-manager-specific transitive age-gating; accurate and helpful.

Sequence Diagram

sequenceDiagram
    participant CLI as mise install
    participant IS as install_single_tool
    participant EBD as effective_before_date
    participant TR as ToolRequest::resolve
    participant BE as NPMBackend::install_version_
    participant PM as Package Manager

    CLI->>IS: InstallOptions with resolve_options
    IS->>EBD: effective_before_date(tr, resolve_options)
    Note over EBD: Priority order: CLI opts > tool option > global setting
    EBD-->>IS: before_date: Option<Timestamp>
    IS->>IS: resolve_options.before_date = before_date
    IS->>TR: tr.resolve(config, resolve_options)
    TR->>EBD: effective_before_date again (no-op, before_date already set)
    EBD-->>TR: same before_date
    TR-->>IS: ToolVersion (version filtered by cutoff)
    IS->>BE: InstallContext with before_date
    BE->>BE: build_transitive_release_age_args(pm, before_date, now)
    Note over BE: npm: --before timestamp, bun: --minimum-release-age seconds, pnpm: --config.minimumReleaseAge=minutes
    BE->>PM: install command with age-gate flag
Loading

Greploops — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.
Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

Reviews (7): Last reviewed commit: "Merge branch 'main' into feat/npm-transi..." | Re-trigger Greptile

Comment thread src/backend/npm.rs
Comment thread src/backend/npm.rs Outdated
Comment thread src/backend/npm.rs
Comment thread e2e/backend/test_npm_package_manager
Comment thread src/backend/npm.rs Outdated
@risu729
Copy link
Copy Markdown
Contributor Author

risu729 commented Apr 2, 2026

@jdx mise i npm:package@20260401 --before 20260331 will start to fail with this PR. Should we exclude the root package from verification?
npm doesn't support (npm/cli#8979), but pnpm and bun support the exclusion.

@jdx jdx merged commit a3695f6 into jdx:main Apr 4, 2026
35 checks passed
@risu729 risu729 deleted the feat/npm-transitive-install-before branch April 4, 2026 13:43
jdx pushed a commit that referenced this pull request Apr 5, 2026
### 🚀 Features

- **(ci)** auto-convert external PRs to draft mode by @jdx in
[#8896](#8896)
- **(deps)** add `depends` field for user-specified tool dependencies by
@cprecioso in [#8776](#8776)
- **(dotnet)** support runtime-only installs by @fragon10 in
[#8524](#8524)
- **(npm)** apply install_before to transitive dependencies by @risu729
in [#8851](#8851)
- **(task)** allow passing arguments to task dependencies via
{{usage.*}} templates by @jdx in
[#8893](#8893)
- add options field to BackendListVersionsCtx by @esteve in
[#8875](#8875)

### 🐛 Bug Fixes

- **(backend)** filter PEP 440 .dev versions in fuzzy version matching
by @richardthe3rd in [#8849](#8849)
- **(ci)** update COPR BuildRequires rust version to match MSRV 1.88 by
@jdx in [#8911](#8911)
- **(ci)** add Ruby build dependencies to e2e Docker image by @jdx in
[#8910](#8910)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8912](#8912)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8914](#8914)
- **(ci)** use Node 24 LTS for corepack e2e test by @jdx in
[#8915](#8915)
- **(ci)** add libxml2 and pkg-config to e2e Docker image by @jdx in
[#8917](#8917)
- **(ci)** add libxml2-dev to e2e image and disable Swift SPM tests by
@jdx in [#8918](#8918)
- **(docs)** use sans-serif font for badges by @jdx in
[#8887](#8887)
- **(env)** parse --env=VALUE and -E=VALUE flag forms correctly by @jdx
in [#8889](#8889)
- **(exec)** use i64::from() for seccomp syscall numbers to survive
autofix by @jdx in [#8882](#8882)
- **(github)** preserve tool options like filter_bins when version
specified via CLI by @jdx in
[#8888](#8888)
- **(github)** use alias-specific options when tool_alias has its own
config by @jdx in [#8892](#8892)
- **(install)** add locked_verify_provenance setting and detect github
attestations at lock time by @jdx in
[#8901](#8901)
- **(lock)** prune stale version entries during filtered `mise lock
<tool>` runs by @altendky in
[#8599](#8599)
- **(python)** use lockfile URL for precompiled installs by @hehaoqian
in [#8750](#8750)
- **(release)** verify all build targets succeed before releasing by
@jdx in [#8886](#8886)
- **(ruby)** support build revisions for precompiled binaries in
mise.lock by @jdx in [#8900](#8900)
- **(swift)** fall back to Ubuntu 24.04 for unsupported Ubuntu versions
by @jdx in [#8916](#8916)
- **(zsh)** avoid duplicate trust warning after cd by @timothysparg in
[#8898](#8898)
- update flake.lock and add fix for rust-bindgen to default.nix by
@esteve in [#8874](#8874)
- when direnv diff is empty, do not try to parse it by @yaleman in
[#8857](#8857)
- skip trust check for plain .tool-versions in task list by @dportalesr
in [#8876](#8876)

### 🚜 Refactor

- **(go)** rename go_* settings to go.* namespace by @jdbruijn in
[#8598](#8598)

### 📚 Documentation

- **(tasks)** clarify task_config.includes behavior by @risu729 in
[#8905](#8905)

### 🧪 Testing

- **(ci)** run e2e tests inside Docker containers by @jdx in
[#8899](#8899)

### 📦️ Dependency Updates

- bump ubi from 0.8 to 0.9 by @jdx in
[#8906](#8906)
- bump zip from 3 to 8 by @jdx in
[#8908](#8908)
- update lockfile deps (hold back rattler) by @jdx in
[#8909](#8909)
- update bun.lock by @jdx in
[#8913](#8913)

### 📦 Registry

- add turso
([github:tursodatabase/turso-cli](https://github.com/tursodatabase/turso-cli))
by @kenn in [#8884](#8884)
- remove carp test by @jdx in
[#8894](#8894)

### Chore

- **(ci)** add workflow to warn PRs modifying vendored aqua-registry by
@jdx in [#8897](#8897)
- **(ci)** use github.token for draft conversion in auto-draft workflow
by @jdx in [#8903](#8903)
- remove deprecated settings older than 12 months by @jdx in
[#8904](#8904)

### New Contributors

- @dportalesr made their first contribution in
[#8876](#8876)
- @timothysparg made their first contribution in
[#8898](#8898)
- @hehaoqian made their first contribution in
[#8750](#8750)
- @jdbruijn made their first contribution in
[#8598](#8598)
- @cprecioso made their first contribution in
[#8776](#8776)
- @yaleman made their first contribution in
[#8857](#8857)
- @kenn made their first contribution in
[#8884](#8884)
- @fragon10 made their first contribution in
[#8524](#8524)

## 📦 Aqua Registry Updates

#### New Packages (6)

- [`ahkohd/oyo`](https://github.com/ahkohd/oyo)
- [`bellicose100xp/jiq`](https://github.com/bellicose100xp/jiq)
- [`kurama/dealve-tui`](https://github.com/kurama/dealve-tui)
- [`micahkepe/jsongrep`](https://github.com/micahkepe/jsongrep)
- [`textfuel/lazyjira`](https://github.com/textfuel/lazyjira)
- [`ubugeeei/vize`](https://github.com/ubugeeei/vize)

#### Updated Packages (1)

- [`sigstore/cosign`](https://github.com/sigstore/cosign)
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