Schniz/fnm
π Fast and simple Node.js version manager, built in Rust
Healthy across the board
weakest axiscopyleft license (GPL-3.0) β review compatibility
Has a license, tests, and CI β clean foundation to fork and modify.
Documented and popular β useful reference codebase to read through.
No critical CVEs, sane security posture β runnable as-is.
- βLast commit 3w ago
- β23+ active contributors
- βDistributed ownership (top contributor 49% of recent commits)
Show all 7 evidence items βShow less
- βGPL-3.0 licensed
- βCI configured
- βTests present
- β GPL-3.0 is copyleft β check downstream compatibility
What would change the summary?
- βUse as dependency Concerns β Mixed if: relicense under MIT/Apache-2.0 (rare for established libs)
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README β live-updates from the latest cached analysis.
[](https://repopilot.app/r/schniz/fnm)Paste at the top of your README.md β renders inline like a shields.io badge.
βΈPreview social card (1200Γ630)
This card auto-renders when someone shares https://repopilot.app/r/schniz/fnm on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: Schniz/fnm
Generated by RepoPilot Β· 2026-05-09 Β· Source
π€Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale β STOP and ask the user to regenerate it before proceeding. - Treat the AI Β· unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Schniz/fnm shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything β but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
π―Verdict
GO β Healthy across the board
- Last commit 3w ago
- 23+ active contributors
- Distributed ownership (top contributor 49% of recent commits)
- GPL-3.0 licensed
- CI configured
- Tests present
- β GPL-3.0 is copyleft β check downstream compatibility
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
β Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live Schniz/fnm
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale β regenerate it at
repopilot.app/r/Schniz/fnm.
What it runs against: a local clone of Schniz/fnm β the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in Schniz/fnm | Confirms the artifact applies here, not a fork |
| 2 | License is still GPL-3.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | Last commit β€ 49 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Schniz/fnm. If you don't
# have one yet, run these first:
#
# git clone https://github.com/Schniz/fnm.git
# cd fnm
#
# Then paste this script. Every check is read-only β no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of Schniz/fnm and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Schniz/fnm(\\.git)?\\b" \\
&& ok "origin remote is Schniz/fnm" \\
|| miss "origin remote is not Schniz/fnm (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(GPL-3\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"GPL-3\\.0\"" package.json 2>/dev/null) \\
&& ok "license is GPL-3.0" \\
|| miss "license drift β was GPL-3.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 49 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~19d)"
else
miss "last commit was $days_since_last days ago β artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) β safe to trust"
else
echo "artifact has $fail stale claim(s) β regenerate at https://repopilot.app/r/Schniz/fnm"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
β‘TL;DR
fnm is a blazingly-fast Node.js version manager written in Rust that replaces nvm with a single-file binary, supporting .node-version and .nvmrc files. It provides instant startup, cross-platform compatibility (macOS, Windows, Linux), and handles version switching with native performance instead of shell scripts. Primarily a Rust binary (135KB of code in src/) with build script (build.rs for Windows resources). Shell integration helpers in .ci/ (install.sh, get_shell_profile.sh). E2E tests in TypeScript (e2e/*.test.ts with snapshots). Changeset-driven releases (.changeset/) follow a monorepo-style versioning workflow despite being a single package.
π₯Who it's for
Node.js developers and DevOps engineers who need to switch between multiple Node versions across projects, frustrated by nvm's shell-script slowness and installation complexity. Also attracts contributors interested in Rust CLI tooling.
π±Maturity & risk
Production-ready and actively maintained. Version 1.39.0 indicates stable release; changelog and recent changesets (.changeset/brave-timers-move.md, .changeset/witty-pans-crash.md) show ongoing work. CI/CD pipelines (rust.yml, release.yml) are well-established, and E2E test snapshots exist (e2e/snapshots/). Last activity visible in changesets suggests active development.
Low risk for a version manager. Single maintainer (Gal Schlezinger) is a minor concern for long-term sustainability. Dependency count is moderate (~20 main crates); reqwest for HTTP and semver parsing are stable. No visible breaking changes in recent changesets. Windows support adds complexity (junctions, CSV parsing in Cargo.toml windows target), but tested via CI.
Active areas of work
Active release preparation: 6 pending changesets (brave-timers-move, cuddly-cars-ring, fair-carrots-greet, green-clouds-argue, warm-apes-fold, witty-pans-crash) suggest features/fixes queued for next version. Recent workflows (release.yml, installation_script.yml) indicate CI/CD refinement and automated releases to Cargo and package managers (Homebrew, Winget, Scoop).
πGet running
git clone https://github.com/Schniz/fnm.git
cd fnm
cargo build --release
./target/release/fnm --version
Or for development:
cargo build
cargo test
Daily commands:
# Development build
cargo build
# Run CLI directly
cargo run -- --help
# Run tests
cargo test
# Run E2E tests (requires Node.js/npm)
cd e2e && npm install && npm test
# Release build (optimized)
cargo build --release
πΊοΈMap of the codebase
- Cargo.toml: Defines all dependencies (reqwest, node-semver, clap, thiserror) and platform-specific logic (Windows junctions, CSV parsing)
- src/main.rs: Entry point with CLI argument parsing using Clap; routes commands like install, use, list, env
- .ci/install.sh: Shell installation script executed by curl piping; detects OS, downloads binary, injects shell config
- e2e/: TypeScript-based end-to-end tests validating real-world workflows (basic.test.ts, aliases.test.ts, corepack.test.ts) with Jest snapshots
- build.rs: Cargo build script for embedding Windows resources (icon, version metadata) via embed-resource crate
- .github/workflows/rust.yml: CI pipeline running cargo test, clippy, and building release binaries for multiple platforms
- .changeset/: Changelog entries (brave-timers-move.md, etc.) drive versioning; config.json defines release process
π οΈHow to make changes
Adding a new command: Edit src/main.rs and add a subcommand using Clap derive macros (see existing install, use, list patterns). Fixing version resolution logic: See src/version_matcher.rs and node-semver integration. Improving shell integration: Modify .ci/install.sh and shell-specific loaders in .ci/get_shell_profile.sh. Adding E2E tests: Create .ts files in e2e/ directory; snapshots auto-generate in e2e/__snapshots__/. Windows-specific changes: Update the [target.'cfg(windows)'.dependencies] section in Cargo.toml.
πͺ€Traps & gotchas
- Shell detection: install.sh infers shell from
$SHELLenv var; some systems (e.g., non-login shells) may not set it correctly. 2. Symlink vs junction: Windows uses junction symlinks (via junction crate) instead of NTFS symlinks; may fail on FAT32 or older Windows. 3. PATH ordering: Shell init must inject fnm's shims before system Node; incorrect load order causes version confusion. 4. xz2 decompression: Required for .tar.xz binaries; some minimal systems lack xz; flate2 fallback exists for .tar.gz. 5. Corepack integration: E2E test for corepack.test.ts suggests special handling; relies on Node's internal corepack feature which varies by Node version.
π‘Concepts to learn
- Semantic Versioning (semver) resolution β fnm uses the node-semver crate to match version queries (e.g., '16.x' or '>=14.0') to available Node releases; understanding semver ranges is critical for version selection logic
- Binary extraction and decompression (tar, xz2, zip) β fnm downloads Node binaries in .tar.xz, .tar.gz, and .zip formats; the build.rs and tar/xz2 dependencies handle decompressionβcrucial for cross-platform portability
- Shell profile injection and sourcing β fnm modifies ~/.bashrc, ~/.zshrc, or ~/.config/fish/conf.d/fnm.fish to inject shims into PATH; understanding shell init sequences is essential for debugging version-switching failures
- File system symlinks and junctions β fnm creates symlinks (Unix) or NTFS junctions (Windows) to point to the active Node version; the junction crate on Windows replaces Unix symlink calls for compatibility
- HTTP caching and ETag/Last-Modified headers β fnm uses reqwest to download Node binaries efficiently; understanding conditional GET requests (ETags) helps optimize repeated downloads and mirrors
- Environment variable sourcing and shell initialization β fnm's 'env' command outputs shell initialization code; Clap's env feature in Cargo.toml allows CLI arguments to be overridden by environment variables, enabling CI/CD configuration
- Rustls and native root certificates β fnm uses reqwest with 'rustls-tls-native-roots' to avoid OpenSSL dependencies while respecting system CA certificates; critical for secure binary downloads in restrictive networks
πRelated repos
nvm-sh/nvmβ The original shell-based Node version manager; fnm reimplements its functionality in Rust for speed and simplicityvolta-rs/voltaβ Alternative Rust-based Node version manager with project-level version pinning via package.json; overlapping but different focus (Volta emphasizes reproducibility)nodejs/nodeβ The Node.js runtime itself; fnm downloads and manages official Node releasesHomebrew/homebrew-coreβ Package manager distributing fnm on macOS/Linux; users install fnm via 'brew install fnm'microsoft/winget-cliβ Windows package manager supporting fnm installation via 'winget install Schniz.fnm'; part of fnm's release pipeline
πͺPR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add E2E tests for shell profile detection and initialization
The repo has a .ci/get_shell_profile.sh script and shell integration is critical for fnm's functionality, but there are no e2e tests specifically for shell initialization edge cases (multiple shells, missing profiles, corrupted rc files, zsh/bash/fish compatibility). This would prevent regressions in a critical user-facing feature.
- [ ] Create
e2e/shell-initialization.test.tsto test shell detection across bash, zsh, and fish - [ ] Add test cases for missing shell rc files, corrupted profiles, and rc file merging behavior
- [ ] Reference
.ci/get_shell_profile.shlogic in test assertions - [ ] Add snapshot tests in
e2e/__snapshots__/shell-initialization.test.ts.snap
Add Rust unit tests for platform-specific Windows linking logic
The repo has [target.'cfg(windows)'.dependencies] including junction crate for symlink handling, but there are no visible Rust unit tests in the source tree for Windows-specific path/junction handling. This is risky given the complexity of cross-platform file system operations.
- [ ] Examine
src/directory structure for Windows-specific code usingjunctionorcsvdependencies - [ ] Create
src/path/windows_test.rsorsrc/symlink_test.rswith unit tests for junction creation and symlink resolution - [ ] Add tests for edge cases: missing paths, permission errors, existing junctions
- [ ] Run tests with
cargo test --target x86_64-pc-windows-msvcin CI
Document and add E2E tests for .changeset workflow with version bumping
The repo uses Changesets for versioning (.changeset/ directory with config and multiple change files), but there's no E2E test validating the changelog generation, version bumping, and package.json/Cargo.toml synchronization. This prevents accidental version mismatches between Rust and npm distributions.
- [ ] Create
e2e/versioning.test.tsto test the changeset workflow end-to-end - [ ] Add tests verifying Cargo.toml version matches release tags after changeset application
- [ ] Validate CHANGELOG.md is updated correctly with changeset entries from
.changeset/directory - [ ] Document the changeset workflow in
docs/(e.g.,docs/contributing.mdor update existing docs)
πΏGood first issues
- idea: Add documentation for the .node-version file format (similar to .nvmrc) in docs/configuration.md; the README mentions support but doesn't explain precedence or syntax rules
- idea: Extend E2E test coverage for Windows-specific behavior (junction symlinks, Winget integration); currently tests focus on install.sh and Homebrew
- idea: Create a troubleshooting guide in docs/ for common issues: shell detection failures, PATH ordering, permission errors on install; reference real error messages from thiserror definitions in src/
βTop contributors
Click to expand
Top contributors
- @renovate[bot] β 49 commits
- @Schniz β 19 commits
- @github-actions[bot] β 7 commits
- @SunsetTechuila β 3 commits
- @mo8it β 3 commits
πRecent commits
Click to expand
Recent commits
a53186dβ Add random number to multishell symlinks (#1536) (joliss)b24c183β docs: add AGENTS guidance for e2e and changesets (#1529) (Schniz)b3d190bβ feat: add possible values to the arch help docs. (#1533) (theborowski)5e14a5dβ chore(deps): update rust crate tar to v0.4.45 [security] (#1534) (renovate[bot])bfb1860β Pin shell in installer-generated env setup (#1525) (Schniz)dc8c54aβ Clarify interactive install prompt source (#1523) (Schniz)168e51fβ feat: support x64-glibc-217 (#1361) (kaaax0815)d2555b4β Version Packages (#1444) (github-actions[bot])d3747afβ fix use-on-cd shell hook robustness (#1517) (Schniz)10c68cbβ feat(env): apply initial use during env --use-on-cd (#1516) (Schniz)
πSecurity observations
The fnm project demonstrates generally good security practices with modern dependencies and a focus on using Rust (a memory-safe language). However, there are several areas for improvement: the automatic installation script pattern poses user risk if the download source is compromised, platform-specific dependencies need better documentation, and there is no visible vulnerability disclosure policy. The dependency versions are relatively recent, but automated scanning mechanisms should be implemented. Overall security posture is solid for a developer tool, but hardening the installation process and adding security governance would significantly improve the score.
- Low Β· Outdated or Potentially Vulnerable Dependencies β
Cargo.toml - dependencies section. Several dependencies are pinned to specific versions. While the project uses relatively recent versions (as of the snapshot), there is no evidence of automated dependency scanning or update mechanisms. The reqwest library with multiple TLS features enabled increases the attack surface. Fix: Implement automated dependency scanning using tools like cargo-audit or Dependabot. Regularly audit dependencies for known vulnerabilities and update when security patches are available. - Low Β· Broad TLS Feature Set β
Cargo.toml - reqwest dependency configuration. The reqwest dependency includes multiple TLS implementations ('rustls-tls', 'rustls-tls-native-roots', 'brotli'). While generally secure, this expands the dependency tree and potential attack surface unnecessarily. Fix: Review if all TLS features are necessary. Consider using a minimal TLS configuration that meets actual requirements. Document why multiple TLS implementations are needed. - Low Β· Installation Script Execution Risk β
README.md and .ci/install.sh. The README snippet references an automatic installation script (./.ci/install.sh) that uses curl piped directly to shell execution. This is a common attack vector if the download source is compromised (MITM attack). Fix: Implement checksum verification for downloaded binaries. Use HTTPS with certificate pinning where possible. Document the security model clearly to users. Consider providing pre-built signed binaries. - Low Β· Windows-Specific Dependencies Without Clear Security Review β
Cargo.toml - target cfg(windows) dependencies. Windows-specific dependencies like 'csv' and 'junction' are conditionally included but lack clear documentation about their security status and why they are necessary. Fix: Document the purpose of each platform-specific dependency. Ensure they are actively maintained and reviewed for security vulnerabilities. - Low Β· Embedded Resources in Windows Build β
Cargo.toml - build-dependencies, build.rs. The embed-resource crate is used in build dependencies for Windows. While generally safe, embedding resources can potentially lead to code injection if not handled carefully. Fix: Review the build.rs file to ensure embedded resources are properly validated and sanitized. Document what resources are being embedded. - Informational Β· No Evidence of Security Policy or Vulnerability Disclosure Process β
Repository root. No SECURITY.md or vulnerability disclosure policy is visible in the provided file structure, which is a best practice for open-source projects. Fix: Create a SECURITY.md file documenting the vulnerability disclosure process. Specify how security issues should be reported and the expected response timeline.
LLM-derived; treat as a starting point, not a security audit.
πWhere to read next
- Open issues β current backlog
- Recent PRs β what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals β see the live page for receipts. Re-run on a new commit to refresh.