How? The project uses npm, but you prefer pnpm. You keep typing pnpm commands as always β FNPM intercepts them, runs the install with pnpm on your machine, and keeps the project's original package-lock.json in sync for the team:
# One-time setup: tell FNPM you want to work with pnpm
fnpm setup pnpm
source .fnpm/setup.sh
# Type pnpm commands like you always do
pnpm add express
# FNPM intercepts the command:
# β installs with pnpm (your local workflow)
# β π Syncing target lockfile: package-lock.json
# β β Target lockfile updated: package-lock.json (the team's lockfile)Works with npm, yarn, pnpm, bun, and deno β everyone on the team uses whatever they like, and the project's lockfile never breaks.
And because every install goes through FNPM, it audits packages before they touch your disk: it flags suspicious preinstall/postinstall scripts and detects malicious patterns in the code you're about to download.
β Like FNPM? Give us a star on GitHub! β
curl -fsSL https://raw.githubusercontent.com/ideascoldigital/fnpm/main/install.sh | bashOr download a binary from GitHub Releases, or build from source:
git clone https://github.com/ideascoldigital/fnpm.git
cd fnpm
make install# Interactive setup (or: fnpm setup pnpm, fnpm setup yarn, etc.)
fnpm
# Then use the same commands regardless of package manager
fnpm install # Install dependencies
fnpm add lodash # Add a package
fnpm add -D typescript # Add a dev dependency
fnpm remove lodash # Remove a package
fnpm run build # Run scripts
fnpm dlx create-react-app my-app # Execute commands (like npx)
# Check your environment
fnpm doctorFNPM detects the project's existing lockfile during setup and keeps it as the source of truth for the team:
- You install with your preferred package manager (its own lockfile is created locally).
- After every
install,add, orremove, FNPM updates the project's original lockfile. - The team's lockfile stays consistent; you keep your workflow.
How messy is your project's package manager situation? fnpm doctor calculates a drama score (0β100%) by checking for conflicting signals:
- Multiple lockfiles living together (
package-lock.json+yarn.lock+ ...) - Dockerfile using a different package manager than your lockfiles
- CI/CD pipelines demanding yet another one
fnpm doctor
π° Three's a crowd! Multiple lockfiles detected!
β οΈ Docker wants pnpm! +20 drama points
β οΈ CI demands yarn! +20 drama points
π΄ 75% - DRAMA ALERT! π¨
This is fine. Everything is fine. (It's not fine.)Fix it automatically β pick one lockfile to keep and remove the rest:
fnpm doctor --fix # interactive: choose which lockfile survives
fnpm doctor --fix --keep pnpm # keep pnpm's lockfile, remove the othersDon't want to type fnpm? Hooks intercept direct package manager commands and redirect them:
fnpm setup pnpm
source .fnpm/setup.sh # add to your shell profile for permanent activation
pnpm add express # β fnpm add express (lockfile sync + security audit included)
yarn add lodash # β fnpm add lodashManage hooks with fnpm hooks status|create|remove, or skip them entirely with fnpm setup --no-hooks npm (useful for CI/CD). Details in HOOKS.md.
Stop letting a package's API leak all over your codebase. fnpm adapt scans how your project actually uses a package (AST-based) and generates a port (interface with only the members you use) plus an adapter (implementation backed by the package):
fnpm adapt axios
π Scanning project for usage of 'axios'
Default export members: get, post
Named exports: isAxiosError
π§± Anti-corruption layer created:
src/adapters/axios/axios.port.ts # interface β reshape it toward your domain
src/adapters/axios/axios.adapter.ts # implementation backed by axios
src/adapters/axios/index.tsYour code then depends on the port instead of the package β swapping axios later means writing a new adapter, not touching every call site. TypeScript projects get a typed port; JavaScript projects get the adapter object. There's also a lighter option: fnpm add <pkg> --adapter generates a simple re-export barrel at install time.
Want a second opinion on the generated layer? With Ollama running locally, add --ai and a local model suggests domain-oriented names and cohesion improvements β advisory only, it never blocks anything, and nothing leaves your machine:
fnpm adapt axios --ai
π€ Asking qwen2.5-coder (http://localhost:11434) for an advisory review...
AI suggestions (advisory only):
- Rename AxiosPort to UserApiPort β it's only used to fetch/save users
- Replace axios config types with your own request options typeConfigure in .fnpm/config.json (all optional; set enabled: true to review on every adapt without --ai):
{
"ai": {
"enabled": false,
"provider": "ollama",
"url": "http://localhost:11434",
"model": "qwen2.5-coder",
"timeout_seconds": 120
}
}Every package (and its dependency tree) is scanned before installation:
- Install scripts β flags suspicious
preinstall/install/postinstallcommands (curl, rm -rf, credential access like~/.sshor~/.aws) - Source code β deep JavaScript analysis for
eval(),Function(), base64 obfuscation,exec()/spawn(), with precise file:line reporting - Transitive dependencies β recursive scan of the whole tree with configurable depth
fnpm add malicious-package
β οΈ HIGH RISK PACKAGES:
β’ evil-dependency - β CRITICAL
β eval: Executes arbitrary code
β ~/.ssh: Accesses SSH keys
? Found 1 high-risk package(s) in dependency tree. Continue anyway? (y/N)Configure in .fnpm/config.json:
{
"security_audit": true,
"transitive_scan_depth": 2
}transitive_scan_depth: 0 disables transitive scanning, 1 scans direct dependencies, 2 (default) goes one level deeper, up to 5. Skip the audit for a trusted package with --no-audit (not recommended).
Full security documentation β Β· Transitive scanning guide β
| Command | Description |
|---|---|
fnpm |
Interactive setup wizard |
fnpm setup <pm> |
Setup with specific package manager (npm/yarn/pnpm/bun/deno) |
fnpm install |
Install dependencies |
fnpm add <pkg> |
Add package (-D for dev dependency) |
fnpm remove <pkg> |
Remove package |
fnpm adapt <pkg> [--ai] |
Generate anti-corruption layer (port + adapter); --ai adds local Ollama review |
fnpm run <script> |
Run package script |
fnpm dlx <cmd> |
Execute command (like npx) |
fnpm doctor |
Run diagnostics + drama score detection |
fnpm doctor --fix [--keep <pm>] |
Remove conflicting lockfiles, keep one |
fnpm hooks status|create|remove |
Manage hooks |
fnpm --version / fnpm --help |
Version / help |
Requires Rust 1.70.0+ and Git.
git clone https://github.com/ideascoldigital/fnpm.git
cd fnpm
make setup
make dev # format, lint, testOther targets: make build, make test, make fmt, make clippy, make install.
src/
βββ main.rs # CLI entry point
βββ lib.rs # Main library
βββ config.rs # Configuration management
βββ detector.rs # Package manager detection
βββ doctor.rs # System diagnostics
βββ hooks.rs # Hook system
βββ security.rs # Security scanner
βββ package_manager.rs # Package manager trait
βββ package_managers/ # npm, yarn, pnpm, bun, deno implementations
Contributions welcome! See CONTRIBUTING.md. Standard flow: fork, branch, make dev, open a PR.
More docs: llms.txt Β· Hooks Β· Testing Β· CI/CD Β· Cross-Platform Β· Windows
FNPM originally stood for "F*ck NPM" β born from the frustration of lockfile conflicts and package manager wars inside teams. If that's not your style, feel free to read it as "Friendly NPM" or "Flexible NPM": the tool exists precisely so nobody has to fight about package managers anymore.
MIT β see LICENSE.
If FNPM helps you or your team: β star the repo, π report issues, or π contribute.