mcpman 0.4.0 → 0.7.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 +219 -0
- package/dist/{chunk-RMMEBP2J.js → chunk-NS6HV723.js} +61 -60
- package/dist/chunk-YZNTMR6O.js +88 -0
- package/dist/{client-detector-UAP2EYZA.js → client-detector-CY7WPF3K.js} +1 -1
- package/dist/index.cjs +3466 -962
- package/dist/index.js +3225 -853
- package/dist/lockfile-RBA7HB24.js +25 -0
- package/package.json +3 -10
package/README.md
CHANGED
|
@@ -39,6 +39,12 @@ mcpman install @modelcontextprotocol/server-filesystem
|
|
|
39
39
|
- **Config sync** — keep server configs consistent across all your AI clients; `--remove` cleans extras
|
|
40
40
|
- **Security audit** — scan servers for vulnerabilities with trust scoring; `--fix` auto-updates vulnerable packages
|
|
41
41
|
- **Auto-update** — get notified when server updates are available
|
|
42
|
+
- **Plugin system** — extend mcpman with npm-based plugins for custom registries (e.g. Ollama, HuggingFace)
|
|
43
|
+
- **Export/Import** — portable JSON bundles for full config migration across machines
|
|
44
|
+
- **Server testing** — validate MCP servers respond to JSON-RPC initialize + tools/list
|
|
45
|
+
- **Log streaming** — stream stdout/stderr from servers in real time
|
|
46
|
+
- **Profiles** — save/restore named server configurations for quick switching
|
|
47
|
+
- **Self-upgrade** — update mcpman itself with a single command
|
|
42
48
|
- **Interactive prompts** — guided installation with env var configuration
|
|
43
49
|
- **No extra daemon** — pure CLI, works anywhere Node ≥ 20 runs
|
|
44
50
|
|
|
@@ -153,6 +159,207 @@ mcpman update my-server # update specific server
|
|
|
153
159
|
mcpman update --check # check only, don't apply
|
|
154
160
|
```
|
|
155
161
|
|
|
162
|
+
### `config <set|get|list|reset>`
|
|
163
|
+
|
|
164
|
+
Manage persistent CLI configuration at `~/.mcpman/config.json`.
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
mcpman config set defaultClient cursor
|
|
168
|
+
mcpman config get defaultClient
|
|
169
|
+
mcpman config list
|
|
170
|
+
mcpman config reset
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Keys: `defaultClient`, `updateCheckInterval`, `preferredRegistry`, `vaultTimeout`, `plugins`.
|
|
174
|
+
|
|
175
|
+
### `search <query>`
|
|
176
|
+
|
|
177
|
+
Search for MCP servers on npm or Smithery registry.
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
mcpman search filesystem
|
|
181
|
+
mcpman search brave --registry smithery
|
|
182
|
+
mcpman search tools --all # include plugin registries
|
|
183
|
+
mcpman search tools --limit 10
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Options:**
|
|
187
|
+
- `--registry <npm|smithery>` — registry to search (default: npm)
|
|
188
|
+
- `--limit <n>` — max results (default: 20, max: 100)
|
|
189
|
+
- `--all` — include plugin registries in results
|
|
190
|
+
|
|
191
|
+
### `info <server>`
|
|
192
|
+
|
|
193
|
+
Show detailed information about an MCP server package.
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
mcpman info @modelcontextprotocol/server-filesystem
|
|
197
|
+
mcpman info my-server --json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `run <server>`
|
|
201
|
+
|
|
202
|
+
Launch an MCP server with vault secrets auto-injected into the process environment.
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
mcpman run my-server
|
|
206
|
+
mcpman run my-server --env API_KEY=sk-...
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `upgrade`
|
|
210
|
+
|
|
211
|
+
Upgrade mcpman itself to the latest version from npm.
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
mcpman upgrade # check and install latest
|
|
215
|
+
mcpman upgrade --check # only check, don't install
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `test [server]`
|
|
219
|
+
|
|
220
|
+
Validate MCP server connectivity by sending JSON-RPC `initialize` + `tools/list`.
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
mcpman test my-server # test a specific server
|
|
224
|
+
mcpman test --all # test all installed servers
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Reports pass/fail, response time, and discovered tools for each server.
|
|
228
|
+
|
|
229
|
+
### `logs <server>`
|
|
230
|
+
|
|
231
|
+
Stream stdout/stderr from an MCP server process in real time.
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
mcpman logs my-server # stream logs (Ctrl+C to stop)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Vault secrets are auto-injected into the server environment.
|
|
238
|
+
|
|
239
|
+
### `profiles <create|switch|list|delete>`
|
|
240
|
+
|
|
241
|
+
Manage named server configuration profiles for quick switching.
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
mcpman profiles create dev # snapshot current servers as "dev"
|
|
245
|
+
mcpman profiles create prod -d "Production config"
|
|
246
|
+
mcpman profiles list # show all profiles
|
|
247
|
+
mcpman profiles switch dev # apply "dev" profile to lockfile
|
|
248
|
+
mcpman profiles delete old # remove a profile
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
After switching, run `mcpman sync` to apply the profile to all clients.
|
|
252
|
+
|
|
253
|
+
### `plugin <add|remove|list>`
|
|
254
|
+
|
|
255
|
+
Manage mcpman plugins for custom registries.
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
mcpman plugin add mcpman-plugin-ollama # install plugin
|
|
259
|
+
mcpman plugin remove mcpman-plugin-ollama # uninstall plugin
|
|
260
|
+
mcpman plugin list # show installed plugins
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Plugins are npm packages that export a `McpmanPlugin` interface with `name`, `prefix`, and `resolve()`. Once installed, their prefix (e.g. `ollama:`) works with `mcpman install ollama:my-model`.
|
|
264
|
+
|
|
265
|
+
### `export [output-file]`
|
|
266
|
+
|
|
267
|
+
Export mcpman config, lockfile, vault, and plugins to a portable JSON file.
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
mcpman export # default: mcpman-export.json
|
|
271
|
+
mcpman export backup.json
|
|
272
|
+
mcpman export --no-vault # exclude encrypted vault
|
|
273
|
+
mcpman export --no-plugins # exclude plugin list
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `import <file>`
|
|
277
|
+
|
|
278
|
+
Restore mcpman config, lockfile, vault, and plugins from an export bundle.
|
|
279
|
+
|
|
280
|
+
```sh
|
|
281
|
+
mcpman import mcpman-export.json
|
|
282
|
+
mcpman import backup.json --yes # skip confirmation
|
|
283
|
+
mcpman import backup.json --dry-run # preview without applying
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### `create [name]`
|
|
287
|
+
|
|
288
|
+
Scaffold a new MCP server project with working boilerplate.
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
mcpman create my-server # interactive prompts
|
|
292
|
+
mcpman create my-server --yes # accept defaults
|
|
293
|
+
mcpman create my-server --runtime python # Python template
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Generates `package.json` (with `mcp` field), `src/index.ts`, and `tsconfig.json` for Node; or `pyproject.toml` and `main.py` for Python. Both templates implement the MCP protocol with a sample `hello` tool ready to run.
|
|
297
|
+
|
|
298
|
+
### `link [dir]`
|
|
299
|
+
|
|
300
|
+
Register a local MCP server directory with AI clients — like `npm link` but for MCP.
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
mcpman link . # link current directory
|
|
304
|
+
mcpman link ./path/to/server # link specific directory
|
|
305
|
+
mcpman link . --client cursor # link to specific client only
|
|
306
|
+
mcpman link . --name my-override # override detected server name
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Reads `package.json` or `pyproject.toml` to detect name, version, and entry point. Adds a lockfile entry with `source: "local"` and registers the absolute path in client configs. No file copying — edits are picked up immediately.
|
|
310
|
+
|
|
311
|
+
### `watch <server>`
|
|
312
|
+
|
|
313
|
+
Watch a local MCP server's source files and auto-restart on changes — like nodemon, built into mcpman.
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
mcpman watch my-server # watch with defaults
|
|
317
|
+
mcpman watch my-server --dir ./src # override watch directory
|
|
318
|
+
mcpman watch my-server --ext ts,js # watch specific extensions
|
|
319
|
+
mcpman watch my-server --delay 500 # set debounce delay (ms)
|
|
320
|
+
mcpman watch my-server --clear # clear terminal on restart
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Uses Node.js built-in `fs.watch` (no chokidar). Debounces 300ms by default. Ignores `node_modules/`, `dist/`, `.git/`, `__pycache__/`. Vault secrets are injected same as `mcpman run`.
|
|
324
|
+
|
|
325
|
+
### `registry <list|add|remove|set-default>`
|
|
326
|
+
|
|
327
|
+
Manage custom registry URLs for MCP server resolution.
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
mcpman registry list # show all registries
|
|
331
|
+
mcpman registry add corp https://mcp.corp.com/api # add custom registry
|
|
332
|
+
mcpman registry remove corp # remove custom registry
|
|
333
|
+
mcpman registry set-default smithery # change default registry
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Built-in registries (npm, smithery) are always present and cannot be removed. Custom registries are stored in `~/.mcpman/config.json`.
|
|
337
|
+
|
|
338
|
+
### `completions <bash|zsh|fish|install>`
|
|
339
|
+
|
|
340
|
+
Generate shell completion scripts for tab-completion of commands and server names.
|
|
341
|
+
|
|
342
|
+
```sh
|
|
343
|
+
mcpman completions bash # output bash completion script
|
|
344
|
+
mcpman completions zsh # output zsh completion script
|
|
345
|
+
mcpman completions fish # output fish completion script
|
|
346
|
+
mcpman completions install # auto-detect shell and install
|
|
347
|
+
source <(mcpman completions bash) # enable completions in current session
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Completes: subcommands, server names (from lockfile), client types (`--client`), and runtimes (`--runtime`). Server names are resolved dynamically at completion time so they stay fresh.
|
|
351
|
+
|
|
352
|
+
### `why <server>`
|
|
353
|
+
|
|
354
|
+
Show why a server is installed — source, clients, profiles, env vars.
|
|
355
|
+
|
|
356
|
+
```sh
|
|
357
|
+
mcpman why my-server # full provenance output
|
|
358
|
+
mcpman why my-server --json # JSON output for scripting
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Displays: source (npm/smithery/github/local), resolved URL, version, installed timestamp, which clients have it registered, which named profiles include it, and required env var names. Detects orphaned servers (in client config but not in lockfile) and suggests `mcpman sync --remove`.
|
|
362
|
+
|
|
156
363
|
---
|
|
157
364
|
|
|
158
365
|
## Comparison
|
|
@@ -168,8 +375,20 @@ mcpman update --check # check only, don't apply
|
|
|
168
375
|
| CI/CD | GitHub Actions | None | None |
|
|
169
376
|
| Auto-update | Version check + notify | None | None |
|
|
170
377
|
| Registry sources | npm + Smithery + GitHub | Smithery only | npm only |
|
|
378
|
+
| Plugin system | npm-based custom registries | None | None |
|
|
379
|
+
| Export/Import | Full config portability | None | None |
|
|
380
|
+
| Server testing | JSON-RPC validation | None | None |
|
|
381
|
+
| Log streaming | Real-time stdout/stderr | None | None |
|
|
382
|
+
| Profiles | Named config switching | None | None |
|
|
383
|
+
| Self-upgrade | Built-in CLI updater | None | None |
|
|
171
384
|
| Interactive setup | Yes | Partial | No |
|
|
172
385
|
| Project-scoped | Yes (`init`) | No | No |
|
|
386
|
+
| Server scaffolding | `create` (Node + Python) | None | None |
|
|
387
|
+
| Local dev linking | `link` (like npm link) | None | None |
|
|
388
|
+
| File watching | `watch` (auto-restart) | None | None |
|
|
389
|
+
| Custom registries | `registry` CRUD | None | None |
|
|
390
|
+
| Shell completions | bash + zsh + fish | None | None |
|
|
391
|
+
| Provenance query | `why` (clients + profiles) | None | None |
|
|
173
392
|
|
|
174
393
|
---
|
|
175
394
|
|
|
@@ -1,8 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/utils/paths.ts
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
function getHomedir() {
|
|
7
|
+
return os.homedir();
|
|
8
|
+
}
|
|
9
|
+
function getMcpmanDir() {
|
|
10
|
+
return path.join(os.homedir(), ".mcpman");
|
|
11
|
+
}
|
|
12
|
+
function getConfigPath() {
|
|
13
|
+
return path.join(getMcpmanDir(), "config.json");
|
|
14
|
+
}
|
|
15
|
+
function getPluginDir() {
|
|
16
|
+
return path.join(getMcpmanDir(), "plugins");
|
|
17
|
+
}
|
|
18
|
+
function getProfilesDir() {
|
|
19
|
+
return path.join(getMcpmanDir(), "profiles");
|
|
20
|
+
}
|
|
21
|
+
function getAppDataDir() {
|
|
22
|
+
const home = getHomedir();
|
|
23
|
+
if (process.platform === "darwin") {
|
|
24
|
+
return path.join(home, "Library", "Application Support");
|
|
25
|
+
}
|
|
26
|
+
if (process.platform === "win32") {
|
|
27
|
+
return process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
28
|
+
}
|
|
29
|
+
return process.env.XDG_CONFIG_HOME ?? path.join(home, ".config");
|
|
30
|
+
}
|
|
31
|
+
function resolveConfigPath(client) {
|
|
32
|
+
const appData = getAppDataDir();
|
|
33
|
+
const home = getHomedir();
|
|
34
|
+
switch (client) {
|
|
35
|
+
case "claude-desktop":
|
|
36
|
+
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
37
|
+
case "cursor":
|
|
38
|
+
return path.join(appData, "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json");
|
|
39
|
+
case "windsurf":
|
|
40
|
+
return path.join(
|
|
41
|
+
appData,
|
|
42
|
+
"Windsurf",
|
|
43
|
+
"User",
|
|
44
|
+
"globalStorage",
|
|
45
|
+
"windsurf.mcpConfigJson",
|
|
46
|
+
"mcp.json"
|
|
47
|
+
);
|
|
48
|
+
case "vscode":
|
|
49
|
+
if (process.platform === "darwin") {
|
|
50
|
+
return path.join(appData, "Code", "User", "settings.json");
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === "win32") {
|
|
53
|
+
return path.join(appData, "Code", "User", "settings.json");
|
|
54
|
+
}
|
|
55
|
+
return path.join(home, ".config", "Code", "User", "settings.json");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
3
59
|
// src/clients/base-client-handler.ts
|
|
4
60
|
import fs from "fs";
|
|
5
|
-
import
|
|
61
|
+
import path2 from "path";
|
|
6
62
|
|
|
7
63
|
// src/clients/types.ts
|
|
8
64
|
var ConfigParseError = class extends Error {
|
|
@@ -24,7 +80,7 @@ var ConfigWriteError = class extends Error {
|
|
|
24
80
|
async function atomicWrite(filePath, content) {
|
|
25
81
|
const tmpPath = `${filePath}.tmp`;
|
|
26
82
|
try {
|
|
27
|
-
await fs.promises.mkdir(
|
|
83
|
+
await fs.promises.mkdir(path2.dirname(filePath), { recursive: true });
|
|
28
84
|
await fs.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
29
85
|
await fs.promises.rename(tmpPath, filePath);
|
|
30
86
|
} catch (err) {
|
|
@@ -45,7 +101,7 @@ async function pathExists(p) {
|
|
|
45
101
|
}
|
|
46
102
|
var BaseClientHandler = class {
|
|
47
103
|
async isInstalled() {
|
|
48
|
-
const dir =
|
|
104
|
+
const dir = path2.dirname(this.getConfigPath());
|
|
49
105
|
return pathExists(dir);
|
|
50
106
|
}
|
|
51
107
|
/** Read raw JSON from disk, return empty object if file missing */
|
|
@@ -99,63 +155,6 @@ var BaseClientHandler = class {
|
|
|
99
155
|
}
|
|
100
156
|
};
|
|
101
157
|
|
|
102
|
-
// src/utils/paths.ts
|
|
103
|
-
import os from "os";
|
|
104
|
-
import path2 from "path";
|
|
105
|
-
function getHomedir() {
|
|
106
|
-
return os.homedir();
|
|
107
|
-
}
|
|
108
|
-
function getMcpmanDir() {
|
|
109
|
-
return path2.join(os.homedir(), ".mcpman");
|
|
110
|
-
}
|
|
111
|
-
function getConfigPath() {
|
|
112
|
-
return path2.join(getMcpmanDir(), "config.json");
|
|
113
|
-
}
|
|
114
|
-
function getAppDataDir() {
|
|
115
|
-
const home = getHomedir();
|
|
116
|
-
if (process.platform === "darwin") {
|
|
117
|
-
return path2.join(home, "Library", "Application Support");
|
|
118
|
-
}
|
|
119
|
-
if (process.platform === "win32") {
|
|
120
|
-
return process.env.APPDATA ?? path2.join(home, "AppData", "Roaming");
|
|
121
|
-
}
|
|
122
|
-
return process.env.XDG_CONFIG_HOME ?? path2.join(home, ".config");
|
|
123
|
-
}
|
|
124
|
-
function resolveConfigPath(client) {
|
|
125
|
-
const appData = getAppDataDir();
|
|
126
|
-
const home = getHomedir();
|
|
127
|
-
switch (client) {
|
|
128
|
-
case "claude-desktop":
|
|
129
|
-
return path2.join(appData, "Claude", "claude_desktop_config.json");
|
|
130
|
-
case "cursor":
|
|
131
|
-
return path2.join(
|
|
132
|
-
appData,
|
|
133
|
-
"Cursor",
|
|
134
|
-
"User",
|
|
135
|
-
"globalStorage",
|
|
136
|
-
"cursor.mcp",
|
|
137
|
-
"mcp.json"
|
|
138
|
-
);
|
|
139
|
-
case "windsurf":
|
|
140
|
-
return path2.join(
|
|
141
|
-
appData,
|
|
142
|
-
"Windsurf",
|
|
143
|
-
"User",
|
|
144
|
-
"globalStorage",
|
|
145
|
-
"windsurf.mcpConfigJson",
|
|
146
|
-
"mcp.json"
|
|
147
|
-
);
|
|
148
|
-
case "vscode":
|
|
149
|
-
if (process.platform === "darwin") {
|
|
150
|
-
return path2.join(appData, "Code", "User", "settings.json");
|
|
151
|
-
}
|
|
152
|
-
if (process.platform === "win32") {
|
|
153
|
-
return path2.join(appData, "Code", "User", "settings.json");
|
|
154
|
-
}
|
|
155
|
-
return path2.join(home, ".config", "Code", "User", "settings.json");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
158
|
// src/clients/claude-desktop.ts
|
|
160
159
|
var ClaudeDesktopHandler = class extends BaseClientHandler {
|
|
161
160
|
type = "claude-desktop";
|
|
@@ -230,6 +229,8 @@ async function getInstalledClients() {
|
|
|
230
229
|
|
|
231
230
|
export {
|
|
232
231
|
getConfigPath,
|
|
232
|
+
getPluginDir,
|
|
233
|
+
getProfilesDir,
|
|
233
234
|
getAllClientTypes,
|
|
234
235
|
getClient,
|
|
235
236
|
getInstalledClients
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/lockfile.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var LOCKFILE_NAME = "mcpman.lock";
|
|
8
|
+
function findLockfile() {
|
|
9
|
+
let dir = process.cwd();
|
|
10
|
+
while (true) {
|
|
11
|
+
const candidate = path.join(dir, LOCKFILE_NAME);
|
|
12
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
13
|
+
const parent = path.dirname(dir);
|
|
14
|
+
if (parent === dir) break;
|
|
15
|
+
dir = parent;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function getGlobalLockfilePath() {
|
|
20
|
+
return path.join(os.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
21
|
+
}
|
|
22
|
+
function resolveLockfilePath() {
|
|
23
|
+
return findLockfile() ?? getGlobalLockfilePath();
|
|
24
|
+
}
|
|
25
|
+
function readLockfile(filePath) {
|
|
26
|
+
const target = filePath ?? resolveLockfilePath();
|
|
27
|
+
if (!fs.existsSync(target)) {
|
|
28
|
+
return { lockfileVersion: 1, servers: {} };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = fs.readFileSync(target, "utf-8");
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
} catch {
|
|
34
|
+
return { lockfileVersion: 1, servers: {} };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function serialize(data) {
|
|
38
|
+
const sorted = {
|
|
39
|
+
lockfileVersion: data.lockfileVersion,
|
|
40
|
+
servers: Object.fromEntries(
|
|
41
|
+
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
42
|
+
)
|
|
43
|
+
};
|
|
44
|
+
return `${JSON.stringify(sorted, null, 2)}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function writeLockfile(data, filePath) {
|
|
48
|
+
const target = filePath ?? resolveLockfilePath();
|
|
49
|
+
const dir = path.dirname(target);
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const tmp = `${target}.tmp`;
|
|
54
|
+
fs.writeFileSync(tmp, serialize(data), "utf-8");
|
|
55
|
+
fs.renameSync(tmp, target);
|
|
56
|
+
}
|
|
57
|
+
function addEntry(name, entry, filePath) {
|
|
58
|
+
const data = readLockfile(filePath);
|
|
59
|
+
data.servers[name] = entry;
|
|
60
|
+
writeLockfile(data, filePath);
|
|
61
|
+
}
|
|
62
|
+
function removeEntry(name, filePath) {
|
|
63
|
+
const data = readLockfile(filePath);
|
|
64
|
+
if (name in data.servers) {
|
|
65
|
+
delete data.servers[name];
|
|
66
|
+
writeLockfile(data, filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getLockedVersion(name, filePath) {
|
|
70
|
+
const data = readLockfile(filePath);
|
|
71
|
+
return data.servers[name]?.version;
|
|
72
|
+
}
|
|
73
|
+
function createEmptyLockfile(filePath) {
|
|
74
|
+
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
LOCKFILE_NAME,
|
|
79
|
+
findLockfile,
|
|
80
|
+
getGlobalLockfilePath,
|
|
81
|
+
resolveLockfilePath,
|
|
82
|
+
readLockfile,
|
|
83
|
+
writeLockfile,
|
|
84
|
+
addEntry,
|
|
85
|
+
removeEntry,
|
|
86
|
+
getLockedVersion,
|
|
87
|
+
createEmptyLockfile
|
|
88
|
+
};
|