pkgmgr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/bin/lib.js +47 -0
- package/bin/lib.test.js +128 -0
- package/bin/pkgmgr-bun.js +3 -0
- package/bin/pkgmgr-pnpm.js +3 -0
- package/bin/pkgmgr-yarn.js +3 -0
- package/bin/pkgmgr.js +3 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# pkgmgr
|
|
2
|
+
|
|
3
|
+
A minimal CLI that forwards commands to whichever package manager you're already using.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
You're writing a script, a tool, or documentation that needs to run package manager commands. But different users use different package managers (npm, pnpm, yarn, bun), and you don't want to:
|
|
8
|
+
|
|
9
|
+
- Hardcode a specific package manager
|
|
10
|
+
- Ask users to modify your script
|
|
11
|
+
- Set up complex workspace configurations
|
|
12
|
+
|
|
13
|
+
## What pkgmgr Does
|
|
14
|
+
|
|
15
|
+
pkgmgr detects which package manager invoked the current process and forwards your command to it verbatim. It's a best-effort user-intent propagation tool.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pkgmgr install # runs: <detected-pm> install
|
|
19
|
+
pkgmgr add react # runs: <detected-pm> add react
|
|
20
|
+
pkgmgr remove lodash # runs: <detected-pm> remove lodash
|
|
21
|
+
pkgmgr run build # runs: <detected-pm> run build
|
|
22
|
+
pkgmgr exec vitest # runs: <detected-pm> exec vitest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Available Binaries
|
|
26
|
+
|
|
27
|
+
| Binary | Fallback |
|
|
28
|
+
|--------|----------|
|
|
29
|
+
| `pkgmgr` | npm |
|
|
30
|
+
| `pkgmgr-bun` | bun |
|
|
31
|
+
| `pkgmgr-pnpm` | pnpm |
|
|
32
|
+
| `pkgmgr-yarn` | yarn |
|
|
33
|
+
|
|
34
|
+
All binaries use the same detection logic. The only difference is which package manager is used when detection fails (no `PKGMGR` env var and no `npm_config_user_agent`).
|
|
35
|
+
|
|
36
|
+
## Detection Logic
|
|
37
|
+
|
|
38
|
+
pkgmgr determines which package manager to use in this order:
|
|
39
|
+
|
|
40
|
+
1. **`PKGMGR` environment variable** — If set to a supported value, use it
|
|
41
|
+
2. **`npm_config_user_agent`** — Parse the first token (e.g., `pnpm/8.0.0` → `pnpm`)
|
|
42
|
+
3. **Fallback** — Use `npm`
|
|
43
|
+
|
|
44
|
+
This is heuristic detection, not verification. pkgmgr trusts the environment.
|
|
45
|
+
|
|
46
|
+
## Supported Package Managers
|
|
47
|
+
|
|
48
|
+
- npm
|
|
49
|
+
- pnpm
|
|
50
|
+
- yarn
|
|
51
|
+
- bun
|
|
52
|
+
|
|
53
|
+
## Overriding Detection
|
|
54
|
+
|
|
55
|
+
Set the `PKGMGR` environment variable to force a specific package manager:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
PKGMGR=pnpm pkgmgr add zod
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage Examples
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Install dependencies (default command if no args)
|
|
65
|
+
pkgmgr
|
|
66
|
+
|
|
67
|
+
# Add a package
|
|
68
|
+
pkgmgr add express
|
|
69
|
+
|
|
70
|
+
# Remove a package
|
|
71
|
+
pkgmgr remove lodash
|
|
72
|
+
|
|
73
|
+
# Run a script
|
|
74
|
+
pkgmgr run test
|
|
75
|
+
|
|
76
|
+
# Run with arguments
|
|
77
|
+
pkgmgr run build -- --watch
|
|
78
|
+
|
|
79
|
+
# Execute a binary
|
|
80
|
+
pkgmgr exec tsc --version
|
|
81
|
+
|
|
82
|
+
# Override detection
|
|
83
|
+
PKGMGR=yarn pkgmgr add react
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## When to Use This
|
|
87
|
+
|
|
88
|
+
**Good fit:**
|
|
89
|
+
|
|
90
|
+
- Scripts that should work regardless of user's package manager choice
|
|
91
|
+
- Documentation that doesn't want to prescribe a specific tool
|
|
92
|
+
- Tools that need to install dependencies as part of setup
|
|
93
|
+
|
|
94
|
+
**Not a good fit:**
|
|
95
|
+
|
|
96
|
+
- CI pipelines where you control the environment (just use your package manager directly)
|
|
97
|
+
- Situations requiring specific package manager features
|
|
98
|
+
- Contexts where command translation between package managers is needed
|
|
99
|
+
|
|
100
|
+
## Known Limitations
|
|
101
|
+
|
|
102
|
+
- **No command translation** — `pkgmgr add` is passed verbatim; if you're using npm, you may need `pkgmgr install` instead. This tool does not normalize commands between package managers.
|
|
103
|
+
- **Corepack** — pkgmgr does not interact with Corepack. If Corepack intercepts your package manager, that's between you and Corepack.
|
|
104
|
+
- **CI environments** — Detection relies on `npm_config_user_agent`, which may not be set in all CI contexts. Use `PKGMGR` explicitly if needed.
|
|
105
|
+
- **Wrapper scripts** — If your package manager is wrapped or aliased, detection may not work as expected.
|
|
106
|
+
- **Nested installs** — If you're running pkgmgr from within another package manager's lifecycle script, detection reflects the outer package manager.
|
|
107
|
+
- **No lockfile inspection** — pkgmgr does not look at lockfiles to determine package manager. It trusts the runtime environment only.
|
|
108
|
+
|
|
109
|
+
## Why Not npx?
|
|
110
|
+
|
|
111
|
+
Running `npx pkgmgr add react` defeats the purpose. npx is part of npm, so it sets `npm_config_user_agent` to npm. pkgmgr would detect npm and run `npm add react`—regardless of what package manager you actually use.
|
|
112
|
+
|
|
113
|
+
The same applies to other package runners:
|
|
114
|
+
- `npx pkgmgr ...` → detects npm
|
|
115
|
+
- `pnpm dlx pkgmgr ...` → detects pnpm
|
|
116
|
+
- `yarn dlx pkgmgr ...` → detects yarn
|
|
117
|
+
- `bunx pkgmgr ...` → detects bun
|
|
118
|
+
|
|
119
|
+
pkgmgr is meant to be **installed as a dependency** in your project. When users run your scripts through their package manager (e.g., `pnpm run setup`), pkgmgr detects the invoking package manager from within that context.
|
|
120
|
+
|
|
121
|
+
## Installation
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install pkgmgr
|
|
125
|
+
# or
|
|
126
|
+
pnpm add pkgmgr
|
|
127
|
+
# or
|
|
128
|
+
yarn add pkgmgr
|
|
129
|
+
# or
|
|
130
|
+
bun add pkgmgr
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
package/bin/lib.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const SUPPORTED = new Set(['npm', 'pnpm', 'yarn', 'bun'])
|
|
2
|
+
|
|
3
|
+
export function detectPackageManager(fallback = 'npm') {
|
|
4
|
+
// 1. Explicit override via environment variable
|
|
5
|
+
const envOverride = process.env.PKGMGR
|
|
6
|
+
if (envOverride && SUPPORTED.has(envOverride)) {
|
|
7
|
+
return envOverride
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 2. Infer from npm_config_user_agent (set by package managers when running scripts)
|
|
11
|
+
// Format: "npm/10.2.0 node/v20.10.0 darwin arm64"
|
|
12
|
+
const userAgent = process.env.npm_config_user_agent
|
|
13
|
+
if (userAgent) {
|
|
14
|
+
const firstToken = userAgent.split(' ')[0] // e.g. "npm/10.2.0"
|
|
15
|
+
const name = firstToken?.split('/')[0]
|
|
16
|
+
if (name && SUPPORTED.has(name)) {
|
|
17
|
+
return name
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 3. Fall back to specified default
|
|
22
|
+
return fallback
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function run(fallback = 'npm') {
|
|
26
|
+
const pm = detectPackageManager(fallback)
|
|
27
|
+
const args = process.argv.slice(2)
|
|
28
|
+
|
|
29
|
+
// Default to 'install' if no arguments provided
|
|
30
|
+
const command = args.length > 0 ? args : ['install']
|
|
31
|
+
|
|
32
|
+
const { spawn } = await import('node:child_process')
|
|
33
|
+
|
|
34
|
+
const child = spawn(pm, command, {
|
|
35
|
+
stdio: 'inherit',
|
|
36
|
+
shell: process.platform === 'win32',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
console.error(`pkgmgr: failed to execute ${pm}: ${err.message}`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
child.on('close', (code) => {
|
|
45
|
+
process.exit(code ?? 0)
|
|
46
|
+
})
|
|
47
|
+
}
|
package/bin/lib.test.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { detectPackageManager } from './lib.js'
|
|
4
|
+
|
|
5
|
+
function setupEnv(overrides = {}) {
|
|
6
|
+
const original = { ...process.env }
|
|
7
|
+
|
|
8
|
+
delete process.env.PKGMGR
|
|
9
|
+
delete process.env.npm_config_user_agent
|
|
10
|
+
|
|
11
|
+
Object.assign(process.env, overrides)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
[Symbol.dispose]() {
|
|
15
|
+
process.env = original
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// PKGMGR environment variable
|
|
21
|
+
|
|
22
|
+
test('uses PKGMGR=npm when set', () => {
|
|
23
|
+
using _env = setupEnv({ PKGMGR: 'npm' })
|
|
24
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('uses PKGMGR=pnpm when set', () => {
|
|
28
|
+
using _env = setupEnv({ PKGMGR: 'pnpm' })
|
|
29
|
+
assert.strictEqual(detectPackageManager(), 'pnpm')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('uses PKGMGR=yarn when set', () => {
|
|
33
|
+
using _env = setupEnv({ PKGMGR: 'yarn' })
|
|
34
|
+
assert.strictEqual(detectPackageManager(), 'yarn')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('uses PKGMGR=bun when set', () => {
|
|
38
|
+
using _env = setupEnv({ PKGMGR: 'bun' })
|
|
39
|
+
assert.strictEqual(detectPackageManager(), 'bun')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('ignores unsupported PKGMGR value and falls back to npm', () => {
|
|
43
|
+
using _env = setupEnv({ PKGMGR: 'unsupported' })
|
|
44
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('PKGMGR takes priority over npm_config_user_agent', () => {
|
|
48
|
+
using _env = setupEnv({
|
|
49
|
+
PKGMGR: 'yarn',
|
|
50
|
+
npm_config_user_agent: 'pnpm/8.0.0 node/v20.0.0',
|
|
51
|
+
})
|
|
52
|
+
assert.strictEqual(detectPackageManager(), 'yarn')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// npm_config_user_agent detection
|
|
56
|
+
|
|
57
|
+
test('detects npm from user agent', () => {
|
|
58
|
+
using _env = setupEnv({
|
|
59
|
+
npm_config_user_agent: 'npm/10.2.0 node/v20.10.0 darwin arm64',
|
|
60
|
+
})
|
|
61
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('detects pnpm from user agent', () => {
|
|
65
|
+
using _env = setupEnv({
|
|
66
|
+
npm_config_user_agent: 'pnpm/8.15.0 npm/? node/v20.10.0',
|
|
67
|
+
})
|
|
68
|
+
assert.strictEqual(detectPackageManager(), 'pnpm')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('detects yarn from user agent', () => {
|
|
72
|
+
using _env = setupEnv({
|
|
73
|
+
npm_config_user_agent: 'yarn/4.0.0 npm/? node/v20.10.0',
|
|
74
|
+
})
|
|
75
|
+
assert.strictEqual(detectPackageManager(), 'yarn')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('detects bun from user agent', () => {
|
|
79
|
+
using _env = setupEnv({ npm_config_user_agent: 'bun/1.0.0 node/v20.10.0' })
|
|
80
|
+
assert.strictEqual(detectPackageManager(), 'bun')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('falls back when user agent has unsupported package manager', () => {
|
|
84
|
+
using _env = setupEnv({ npm_config_user_agent: 'unknown/1.0.0 node/v20.0.0' })
|
|
85
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('falls back when user agent is malformed', () => {
|
|
89
|
+
using _env = setupEnv({ npm_config_user_agent: 'malformed-string' })
|
|
90
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('falls back when user agent is empty', () => {
|
|
94
|
+
using _env = setupEnv({ npm_config_user_agent: '' })
|
|
95
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Fallback behavior
|
|
99
|
+
|
|
100
|
+
test('uses npm as default fallback when no env is set', () => {
|
|
101
|
+
using _env = setupEnv()
|
|
102
|
+
assert.strictEqual(detectPackageManager(), 'npm')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('uses pnpm fallback when specified and no env is set', () => {
|
|
106
|
+
using _env = setupEnv()
|
|
107
|
+
assert.strictEqual(detectPackageManager('pnpm'), 'pnpm')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('uses bun fallback when specified and no env is set', () => {
|
|
111
|
+
using _env = setupEnv()
|
|
112
|
+
assert.strictEqual(detectPackageManager('bun'), 'bun')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('uses yarn fallback when specified and no env is set', () => {
|
|
116
|
+
using _env = setupEnv()
|
|
117
|
+
assert.strictEqual(detectPackageManager('yarn'), 'yarn')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('custom fallback is ignored when PKGMGR is set', () => {
|
|
121
|
+
using _env = setupEnv({ PKGMGR: 'npm' })
|
|
122
|
+
assert.strictEqual(detectPackageManager('pnpm'), 'npm')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('custom fallback is ignored when user agent is present', () => {
|
|
126
|
+
using _env = setupEnv({ npm_config_user_agent: 'yarn/4.0.0 node/v20.0.0' })
|
|
127
|
+
assert.strictEqual(detectPackageManager('pnpm'), 'yarn')
|
|
128
|
+
})
|
package/bin/pkgmgr.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pkgmgr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Forward commands to the package manager you're already using",
|
|
5
|
+
"bin": {
|
|
6
|
+
"pkgmgr": "./bin/pkgmgr.js",
|
|
7
|
+
"pkgmgr-bun": "./bin/pkgmgr-bun.js",
|
|
8
|
+
"pkgmgr-pnpm": "./bin/pkgmgr-pnpm.js",
|
|
9
|
+
"pkgmgr-yarn": "./bin/pkgmgr-yarn.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test bin/lib.test.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"npm",
|
|
22
|
+
"pnpm",
|
|
23
|
+
"yarn",
|
|
24
|
+
"bun",
|
|
25
|
+
"package-manager",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com/)",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/kentcdodds/pkgmgr.git"
|
|
34
|
+
}
|
|
35
|
+
}
|