@wwkit/opm 1.0.1

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.
Files changed (62) hide show
  1. package/README.md +381 -0
  2. package/bin/index.js +9 -0
  3. package/docs/managers/apt.md +45 -0
  4. package/docs/managers/brew.md +92 -0
  5. package/docs/managers/bun.md +50 -0
  6. package/docs/managers/cft.md +57 -0
  7. package/docs/managers/composer.md +96 -0
  8. package/docs/managers/config.md +32 -0
  9. package/docs/managers/dnf.md +46 -0
  10. package/docs/managers/node.md +49 -0
  11. package/docs/managers/npm.md +100 -0
  12. package/docs/managers/nvm.md +34 -0
  13. package/docs/managers/php.md +48 -0
  14. package/docs/managers/phpenv.md +34 -0
  15. package/docs/managers/pip.md +98 -0
  16. package/docs/managers/python.md +53 -0
  17. package/docs/managers/uv.md +27 -0
  18. package/docs/opm.html +570 -0
  19. package/docs/packages/opencode.md +41 -0
  20. package/package.json +51 -0
  21. package/scripts/postinstall.js +7 -0
  22. package/src/binaries/base.js +508 -0
  23. package/src/binaries/cft/index.js +35 -0
  24. package/src/binaries/depchecker.js +172 -0
  25. package/src/binaries/exefinder.js +110 -0
  26. package/src/binaries/platform.js +17 -0
  27. package/src/cli/doc.js +335 -0
  28. package/src/cli/groups/config.js +78 -0
  29. package/src/cli/groups/ensure.js +96 -0
  30. package/src/cli/groups/homebrew.js +73 -0
  31. package/src/cli/groups/nvm.js +73 -0
  32. package/src/cli/groups/package.js +514 -0
  33. package/src/cli/groups/phpenv.js +89 -0
  34. package/src/cli/groups/uv.js +73 -0
  35. package/src/cli/groups/version-manager.js +186 -0
  36. package/src/cli/helpers/args.js +49 -0
  37. package/src/cli/index.js +129 -0
  38. package/src/config.js +133 -0
  39. package/src/config.json5 +158 -0
  40. package/src/formatter.js +13 -0
  41. package/src/harnesses/base.js +205 -0
  42. package/src/harnesses/opencode/clear.js +239 -0
  43. package/src/harnesses/opencode/db.js +39 -0
  44. package/src/harnesses/opencode/index.js +46 -0
  45. package/src/harnesses/opencode/sqlite.js +200 -0
  46. package/src/index.js +46 -0
  47. package/src/managers/apt.js +377 -0
  48. package/src/managers/base.js +167 -0
  49. package/src/managers/brew.js +374 -0
  50. package/src/managers/bun.js +342 -0
  51. package/src/managers/composer.js +280 -0
  52. package/src/managers/dnf.js +370 -0
  53. package/src/managers/http.js +432 -0
  54. package/src/managers/node.js +150 -0
  55. package/src/managers/npm.js +473 -0
  56. package/src/managers/php.js +157 -0
  57. package/src/managers/pip.js +555 -0
  58. package/src/managers/python.js +180 -0
  59. package/src/managers/runtime.js +309 -0
  60. package/src/managers/winget.js +268 -0
  61. package/src/presets.js +24 -0
  62. package/src/proxy-presets.js +12 -0
@@ -0,0 +1,514 @@
1
+ /**
2
+ * 包管理命令组
3
+ *
4
+ * 一组相似命令(npm/pip)的共享路由:同一套 action 分发,仅底层管理器不同。
5
+ * 新增包管理器:实现 src/managers/base.js 接口后在此 MANAGERS 注册。
6
+ */
7
+
8
+ import { parseFlags } from '../helpers/args.js'
9
+ import { NpmManager } from '../../managers/npm.js'
10
+ import { PipManager } from '../../managers/pip.js'
11
+ import { DnfManager } from '../../managers/dnf.js'
12
+ import { AptManager } from '../../managers/apt.js'
13
+ import { BrewManager } from '../../managers/brew.js'
14
+ import { ComposerManager } from '../../managers/composer.js'
15
+ import { BunManager } from '../../managers/bun.js'
16
+ import { WingetManager } from '../../managers/winget.js'
17
+ import { NodeManager } from '../../managers/node.js'
18
+ import { PhpManager } from '../../managers/php.js'
19
+ import { PythonManager } from '../../managers/python.js'
20
+ import { getPresets } from '../../presets.js'
21
+ import { getActiveProxy } from '../../config.js'
22
+ import { output } from '../../formatter.js'
23
+
24
+ const MANAGERS = {
25
+ npm: () => new NpmManager(),
26
+ pip: () => new PipManager(),
27
+ dnf: () => new DnfManager(),
28
+ apt: () => new AptManager(),
29
+ brew: () => new BrewManager(),
30
+ composer: () => new ComposerManager(),
31
+ bun: () => new BunManager(),
32
+ winget: () => new WingetManager(),
33
+ node: () => new NodeManager(),
34
+ php: () => new PhpManager(),
35
+ python: () => new PythonManager(),
36
+ }
37
+
38
+ const MANAGER_DESC = {
39
+ npm: 'npm package manager',
40
+ pip: 'pip package manager',
41
+ dnf: 'dnf package manager',
42
+ apt: 'apt package manager',
43
+ brew: 'brew package manager (Homebrew)',
44
+ composer: 'composer package manager (PHP)',
45
+ bun: 'bun package manager (Bun runtime)',
46
+ winget: 'winget package manager (Windows)',
47
+ node: 'node version manager (via nvm)',
48
+ php: 'php version manager (via phpenv)',
49
+ python: 'python version manager (via uv)',
50
+ }
51
+
52
+ const ACTIONS = {
53
+ version: { desc: 'Show manager version' },
54
+ installed: { desc: 'Check if the manager itself is installed' },
55
+ registry: { desc: 'Query or set registry/mirror address' },
56
+ search: { desc: 'Check if a package is available in the source' },
57
+ view: { desc: 'Check if a package is already installed' },
58
+ info: { desc: 'Show package details' },
59
+ versions: { desc: 'List all available versions of a package' },
60
+ list: { desc: 'List all installed packages' },
61
+ outdated: { desc: 'List outdated packages' },
62
+ install: { desc: 'Install a package' },
63
+ uninstall: { desc: 'Uninstall a package' },
64
+ upgrade: { desc: 'Upgrade a package' },
65
+ cache: { desc: 'Clean cache' },
66
+ help: { desc: 'Show help for this manager' },
67
+ }
68
+
69
+ export class PackageGroup {
70
+ /**
71
+ * @param {string} managerName - 管理器名称(npm/pip)
72
+ */
73
+ constructor(managerName) {
74
+ this.name = managerName
75
+ this.desc = MANAGER_DESC[managerName] || `${managerName} package manager`
76
+ }
77
+
78
+ async run(argv) {
79
+ const [action, ...rest] = argv
80
+
81
+ if (!action || action === '-h' || action === '--help' || action === 'help') {
82
+ this.printHelp()
83
+ return
84
+ }
85
+
86
+ if (!ACTIONS[action]) {
87
+ console.error(`Unknown action: ${action}`)
88
+ this.printHelp()
89
+ process.exit(1)
90
+ }
91
+
92
+ const manager = MANAGERS[this.name]()
93
+ const parsed = parseFlags(rest)
94
+ const global = parsed.flags.global === 'true' || parsed.flags.g === 'true'
95
+ let proxy = parsed.flags.proxy || parsed.flags.p || ''
96
+ if (proxy === 'true') proxy = ''
97
+ if (!proxy) proxy = getActiveProxy()
98
+
99
+ await this._dispatch(manager, action, parsed, { global, proxy })
100
+ }
101
+
102
+ async _dispatch(manager, action, parsed, opts) {
103
+ const { positional } = parsed
104
+
105
+ switch (action) {
106
+ case 'version': {
107
+ const result = await manager.getVersion()
108
+ output((result.version || '').replace(/^v/, ''))
109
+ break
110
+ }
111
+
112
+ case 'installed': {
113
+ const result = await manager.getVersion()
114
+ output({ name: result.name, installed: result.installed, ...(result.version ? { version: result.version } : {}), ...(result.hint ? { hint: result.hint } : {}) })
115
+ break
116
+ }
117
+
118
+ case 'registry': {
119
+ if (positional[0] === 'set') {
120
+ const url = positional[1]
121
+ if (!url) {
122
+ console.error(`Usage: opm ${this.name} registry set <url|preset>`)
123
+ process.exit(1)
124
+ }
125
+ const result = await manager.setRegistry(url)
126
+ output(result)
127
+ } else if (positional[0] === 'presets') {
128
+ const presets = getPresets(this.name)
129
+ const items = Object.entries(presets).map(([name, url]) => ({ name, url }))
130
+ output(items)
131
+ } else {
132
+ const result = await manager.getRegistry()
133
+ output(result)
134
+ }
135
+ break
136
+ }
137
+
138
+ case 'search': {
139
+ const name = positional[0]
140
+ const version = positional[1]
141
+ if (!name) {
142
+ console.error(`Usage: opm ${this.name} search <name> [version]`)
143
+ process.exit(1)
144
+ }
145
+ const result = await manager.searchPackage(name, version, { proxy: opts.proxy })
146
+ output(result)
147
+ break
148
+ }
149
+
150
+ case 'view': {
151
+ const name = positional[0]
152
+ if (!name) {
153
+ console.error(`Usage: opm ${this.name} view <name> [-g]`)
154
+ process.exit(1)
155
+ }
156
+ const result = await manager.isInstalled(name, { global: !!opts.global })
157
+ output(result)
158
+ break
159
+ }
160
+
161
+ case 'info': {
162
+ const name = positional[0]
163
+ if (!name) {
164
+ console.error(`Usage: opm ${this.name} info <name>`)
165
+ process.exit(1)
166
+ }
167
+ const result = await manager.packageInfo(name, { proxy: opts.proxy })
168
+ output(result)
169
+ break
170
+ }
171
+
172
+ case 'versions': {
173
+ const name = positional[0]
174
+ if (!name) {
175
+ console.error(`Usage: opm ${this.name} versions <name>`)
176
+ process.exit(1)
177
+ }
178
+ const result = await manager.packageVersions(name, { proxy: opts.proxy })
179
+ output(result)
180
+ break
181
+ }
182
+
183
+ case 'list': {
184
+ const result = await manager.listInstalled({ global: !!opts.global })
185
+ output(result)
186
+ break
187
+ }
188
+
189
+ case 'outdated': {
190
+ const result = await manager.listOutdated({ global: !!opts.global, proxy: opts.proxy })
191
+ output(result)
192
+ break
193
+ }
194
+
195
+ case 'install': {
196
+ const name = positional[0]
197
+ const version = positional[1]
198
+ if (!name) {
199
+ console.error(`Usage: opm ${this.name} install <name> [version]`)
200
+ process.exit(1)
201
+ }
202
+ const result = await manager.install(name, version, { global: !!opts.global, proxy: opts.proxy })
203
+ output(result)
204
+ break
205
+ }
206
+
207
+ case 'uninstall': {
208
+ const name = positional[0]
209
+ if (!name) {
210
+ console.error(`Usage: opm ${this.name} uninstall <name>`)
211
+ process.exit(1)
212
+ }
213
+ const result = await manager.uninstall(name, { global: !!opts.global })
214
+ output(result)
215
+ break
216
+ }
217
+
218
+ case 'upgrade': {
219
+ const name = positional[0]
220
+ if (!name) {
221
+ console.error(`Usage: opm ${this.name} upgrade <name>`)
222
+ process.exit(1)
223
+ }
224
+ const result = await manager.upgrade(name, { global: !!opts.global, proxy: opts.proxy })
225
+ output(result)
226
+ break
227
+ }
228
+
229
+ case 'cache': {
230
+ if (positional[0] !== 'clean') {
231
+ console.error(`Usage: opm ${this.name} cache clean`)
232
+ process.exit(1)
233
+ }
234
+ const result = await manager.cleanCache()
235
+ output(result)
236
+ break
237
+ }
238
+
239
+ default:
240
+ console.error(`Unknown action: ${action}`)
241
+ process.exit(1)
242
+ }
243
+ }
244
+
245
+ printHelp() {
246
+ console.log(`
247
+ Usage: opm ${this.name} <action> [args] [options]
248
+
249
+ Actions:
250
+ version Show ${this.name} version
251
+ installed Check if ${this.name} itself is installed
252
+ registry Show active registry (from opm config)
253
+ registry set <url|preset> Set active registry (writes opm config)
254
+ registry presets List available mirror presets
255
+ search <name> [version] Check package availability in source
256
+ view <name> [-g] Check if package is installed (local or global)
257
+ info <name> Show package details
258
+ versions <name> List all available versions
259
+ list [-g] List all installed packages
260
+ outdated [-g] List outdated packages
261
+ install <name> [version] Install a package
262
+ uninstall <name> Uninstall a package
263
+ upgrade <name> Upgrade a package to latest
264
+ cache clean Clean cache
265
+ help Show this help
266
+
267
+ Options:
268
+ -g, --global Global operation (npm: global node_modules; pip: system Python)
269
+ -p, --proxy <url> Proxy address (search/info/versions/outdated/install/upgrade);
270
+ overrides config proxy.active when provided
271
+ -h, --help Show this help
272
+
273
+ Examples:
274
+ opm ${this.name} registry
275
+ opm ${this.name} registry set <preset|url>
276
+ opm ${this.name} search <name>
277
+ opm ${this.name} view <name>
278
+ opm ${this.name} info <name>
279
+ opm ${this.name} versions <name>
280
+ opm ${this.name} list
281
+ opm ${this.name} outdated
282
+ opm ${this.name} install <name>
283
+ opm ${this.name} uninstall <name>
284
+ opm ${this.name} upgrade <name>
285
+ opm ${this.name} cache clean
286
+ `)
287
+ }
288
+ }
289
+
290
+ /**
291
+ * 运行时版本管理器元数据(node / php / python)
292
+ * 由 RuntimeGroup 据此生成一致的帮助文本。
293
+ */
294
+ const RUNTIME_META = {
295
+ node: {
296
+ label: 'Node.js',
297
+ tool: 'nvm',
298
+ versionsHint: 'nvm ls-remote',
299
+ listHint: 'nvm ls',
300
+ useHint: 'nvm use',
301
+ currentHint: 'nvm current',
302
+ defaultHint: 'nvm alias default',
303
+ latest: 'LTS',
304
+ installExample: '20',
305
+ useExample: '20',
306
+ searchExample: 'v20.20.2',
307
+ uninstallExample: '18',
308
+ },
309
+ php: {
310
+ label: 'PHP',
311
+ tool: 'phpenv',
312
+ versionsHint: 'phpenv install --list',
313
+ listHint: 'phpenv versions',
314
+ useHint: 'phpenv global',
315
+ currentHint: 'phpenv version',
316
+ defaultHint: 'phpenv global',
317
+ latest: '',
318
+ installExample: '8.3',
319
+ useExample: '8.3',
320
+ searchExample: '8.3.13',
321
+ uninstallExample: '8.2',
322
+ },
323
+ python: {
324
+ label: 'Python',
325
+ tool: 'uv',
326
+ versionsHint: 'uv python list',
327
+ listHint: 'uv python list --only-installed',
328
+ useHint: 'uv python pin',
329
+ currentHint: 'python --version',
330
+ defaultHint: '~/.python-version',
331
+ latest: '',
332
+ installExample: '3.12',
333
+ useExample: '3.12',
334
+ searchExample: '3.12.4',
335
+ uninstallExample: '3.11',
336
+ },
337
+ }
338
+
339
+ /**
340
+ * 运行时命令组 — 在 PackageGroup(registry/search/info/versions/list/install/...)
341
+ * 基础上增加 use/current/default 三个版本管理专属 action。
342
+ * node / php / python 共用同一实现,仅顶层命令名与帮助元数据不同。
343
+ */
344
+ export class RuntimeGroup extends PackageGroup {
345
+ constructor(managerName) {
346
+ super(managerName)
347
+ this.meta = RUNTIME_META[managerName]
348
+ }
349
+
350
+ async run(argv) {
351
+ const [action, ...rest] = argv
352
+
353
+ if (!action || action === '-h' || action === '--help' || action === 'help') {
354
+ this.printHelp()
355
+ return
356
+ }
357
+
358
+ const runtimeActions = new Set(['use', 'current', 'default', 'installed', 'dir', 'install', 'upgrade', 'search', 'versions'])
359
+ if (runtimeActions.has(action)) {
360
+ const manager = MANAGERS[this.name]()
361
+ const parsed = parseFlags(rest)
362
+ let proxy = parsed.flags.proxy || parsed.flags.p || ''
363
+ if (proxy === 'true') proxy = ''
364
+ if (!proxy) proxy = getActiveProxy()
365
+ await this._dispatch(manager, action, parsed, { global: false, proxy })
366
+ return
367
+ }
368
+
369
+ return super.run(argv)
370
+ }
371
+
372
+ async _dispatch(manager, action, parsed, opts) {
373
+ const { positional } = parsed
374
+
375
+ switch (action) {
376
+ case 'installed': {
377
+ const result = await manager.getVersion()
378
+ const toolInstalled = manager.cfg.isInstalled()
379
+ output({
380
+ name: result.name,
381
+ installed: result.installed,
382
+ ...(result.version ? { version: result.version } : {}),
383
+ ...(result.hint ? { hint: result.hint } : {}),
384
+ tool: { name: this.meta.tool, installed: toolInstalled },
385
+ })
386
+ break
387
+ }
388
+
389
+ case 'use': {
390
+ const version = positional[0]
391
+ if (!version) {
392
+ console.error(`Usage: opm ${this.name} use <version>`)
393
+ process.exit(1)
394
+ }
395
+ const result = await manager.useVersion(version, { proxy: opts.proxy })
396
+ output(result)
397
+ break
398
+ }
399
+
400
+ case 'current': {
401
+ const result = await manager.currentVersion()
402
+ output(result)
403
+ break
404
+ }
405
+
406
+ case 'default': {
407
+ const version = positional[0]
408
+ if (!version) {
409
+ console.error(`Usage: opm ${this.name} default <version>`)
410
+ process.exit(1)
411
+ }
412
+ const result = await manager.setDefault(version)
413
+ output(result)
414
+ break
415
+ }
416
+
417
+ case 'dir': {
418
+ const install = manager.getInstallDir ? manager.getInstallDir() : ''
419
+ output({
420
+ name: this.name,
421
+ install,
422
+ })
423
+ break
424
+ }
425
+
426
+ case 'install': {
427
+ const version = positional[0] || (manager.getDefaultVersion ? manager.getDefaultVersion() : '')
428
+ if (!version) {
429
+ console.error(`Usage: opm ${this.name} install <version>`)
430
+ process.exit(1)
431
+ }
432
+ const result = await manager.install(this.name, version, { proxy: opts.proxy })
433
+ output(result)
434
+ break
435
+ }
436
+
437
+ case 'upgrade': {
438
+ const result = await manager.upgrade(this.name, { proxy: opts.proxy })
439
+ output(result)
440
+ break
441
+ }
442
+
443
+ case 'search': {
444
+ const version = positional[0]
445
+ if (!version) {
446
+ console.error(`Usage: opm ${this.name} search <version>`)
447
+ process.exit(1)
448
+ }
449
+ const result = await manager.searchPackage(this.name, version, { proxy: opts.proxy })
450
+ output(result)
451
+ break
452
+ }
453
+
454
+ case 'versions': {
455
+ const result = await manager.packageVersions(this.name, { proxy: opts.proxy })
456
+ output(result)
457
+ break
458
+ }
459
+
460
+ default:
461
+ return super._dispatch(manager, action, parsed, opts)
462
+ }
463
+ }
464
+
465
+ /**
466
+ * 依据元数据生成一致的运行时帮助文本
467
+ */
468
+ printHelp() {
469
+ const { label, tool, versionsHint, listHint, useHint, currentHint, defaultHint, latest, installExample, useExample, searchExample, uninstallExample } = this.meta
470
+ const latestLabel = latest ? ` install latest ${latest}` : ''
471
+ const installDesc = `Install a ${label} version (e.g. ${installExample}${latest ? `, --lts` : ''})`
472
+ console.log(`
473
+ Usage: opm ${this.name} <action> [args] [options]
474
+
475
+ Actions:
476
+ version Show current ${label} version
477
+ installed Check if ${label} is installed (and its version manager ${tool})
478
+ registry Show active ${label} dist/source mirror (from opm config)
479
+ registry set <url|preset> Set active ${label} mirror (writes opm config)
480
+ registry presets List available mirror presets
481
+ search <version> Check if a ${label} version is available for download
482
+ view <version> Check if a ${label} version is installed
483
+ versions List all available ${label} versions (${versionsHint})
484
+ list List installed ${label} versions (${listHint})
485
+ install [version] ${installDesc} (default: config defaultVersion)
486
+ uninstall <version> Uninstall a ${label} version
487
+ upgrade ${latest ? 'Install latest ' + latest : 'Install latest'} ${label}
488
+ use <version> Switch to a ${label} version (${useHint})
489
+ current Show current active ${label} version (${currentHint})
490
+ default <version> Set default ${label} version (${defaultHint})
491
+ dir Show ${label} install directory
492
+ help Show this help
493
+
494
+ Options:
495
+ -p, --proxy <url> Proxy address (search/versions/install/upgrade/use)
496
+ -h, --help Show this help
497
+
498
+ Examples:
499
+ opm ${this.name} current
500
+ opm ${this.name} install ${installExample}
501
+ opm ${this.name} use ${useExample}
502
+ opm ${this.name} default ${useExample}
503
+ opm ${this.name} dir
504
+ opm ${this.name} list
505
+ opm ${this.name} versions
506
+ opm ${this.name} search ${searchExample}
507
+ opm ${this.name} view ${searchExample}
508
+ opm ${this.name} uninstall ${uninstallExample}
509
+ opm ${this.name} upgrade
510
+ `)
511
+ }
512
+ }
513
+
514
+ export { MANAGERS, ACTIONS }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * phpenv 命令组 — 管理 phpenv 自身
3
+ *
4
+ * phpenv 是常规二进制(~/.phpenv/bin/phpenv),通过 git clone 安装;
5
+ * install = git clone phpenv + git clone php-build plugin + 写 shell profile;
6
+ * upgrade = git pull(含 php-build 插件);uninstall = rm -rf ~/.phpenv。
7
+ * registry 是 phpenv 的 git 镜像地址(official / gitee)。
8
+ */
9
+
10
+ import { execSync } from 'node:child_process'
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+ import os from 'node:os'
14
+ import { ToolManagerGroup } from './version-manager.js'
15
+ import { Shell } from '@wwkit/shared'
16
+ import { getActiveRegistry } from '../../config.js'
17
+ import { installPhpenv } from '../../managers/php.js'
18
+
19
+ const shell = new Shell()
20
+
21
+ /**
22
+ * 获取 phpenv 根目录
23
+ * @returns {string}
24
+ */
25
+ function getPhpenvRoot() {
26
+ return process.env.PHPENV_ROOT || path.join(os.homedir(), '.phpenv')
27
+ }
28
+
29
+ /**
30
+ * 获取 phpenv 二进制路径
31
+ * @returns {string}
32
+ */
33
+ function getPhpenvBin() {
34
+ return path.join(getPhpenvRoot(), 'bin', 'phpenv')
35
+ }
36
+
37
+ export class PhpenvGroup extends ToolManagerGroup {
38
+ constructor() {
39
+ super({
40
+ name: 'phpenv',
41
+ desc: 'phpenv version manager (manage phpenv itself)',
42
+ needsBash: true,
43
+ isInstalled: () => fs.existsSync(getPhpenvBin()),
44
+ getPath: () => getPhpenvRoot(),
45
+ versionCmd: () => `${getPhpenvBin()} --version`,
46
+ parseVersion: (out) => {
47
+ const m = out.trim().match(/phpenv\s+([\d.]+)/)
48
+ return m ? m[1] : out.trim()
49
+ },
50
+ install: () => installPhpenv(),
51
+ upgrade: () => {
52
+ execSync(`cd "${getPhpenvRoot()}" && git remote set-url origin "${getActiveRegistry('phpenv')}" && git pull --tags`, {
53
+ stdio: 'inherit',
54
+ shell: shell.resolveBash(),
55
+ })
56
+ const pluginsDir = path.join(getPhpenvRoot(), 'plugins', 'php-build')
57
+ if (fs.existsSync(pluginsDir)) {
58
+ execSync(`cd "${pluginsDir}" && git pull`, { stdio: 'inherit', shell: shell.resolveBash() })
59
+ }
60
+ return { action: 'upgraded' }
61
+ },
62
+ uninstall: () => {
63
+ fs.rmSync(getPhpenvRoot(), { recursive: true, force: true })
64
+ return { action: 'uninstalled', hint: 'Remove PHPENV_ROOT/init line from shell profile manually' }
65
+ },
66
+ usage: 'opm phpenv <action> [args] [options]',
67
+ actions: [
68
+ ['version', 'Show phpenv version'],
69
+ ['installed', 'Check if phpenv is installed'],
70
+ ['registry', 'Show active phpenv git mirror (from opm config)'],
71
+ ['registry set <url|preset>', 'Set active phpenv git mirror (writes opm config)'],
72
+ ['registry presets', 'List available mirror presets'],
73
+ ['install', 'Install phpenv (git clone + php-build plugin + shell profile)'],
74
+ ['upgrade', 'Update phpenv and php-build plugin (git pull)'],
75
+ ['uninstall', 'Remove phpenv (~/.phpenv)'],
76
+ ['dir', 'Show phpenv install directory'],
77
+ ['help', 'Show this help'],
78
+ ],
79
+ examples: [
80
+ 'opm phpenv version',
81
+ 'opm phpenv installed',
82
+ 'opm phpenv registry set gitee',
83
+ 'opm phpenv install',
84
+ 'opm phpenv upgrade',
85
+ 'opm phpenv dir',
86
+ ],
87
+ })
88
+ }
89
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * uv 命令组 — 管理 uv 自身
3
+ *
4
+ * uv 是 Astral 出品的快速 Python 包/版本管理器(Rust 实现)。
5
+ * 安装方式:curl 安装脚本(非 git clone);
6
+ * upgrade = uv self update;uninstall = rm 二进制 + 数据目录。
7
+ * uv 无镜像源,故无 registry action。
8
+ */
9
+
10
+ import { execSync, spawnSync } from 'node:child_process'
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+ import os from 'node:os'
14
+ import { ToolManagerGroup } from './version-manager.js'
15
+ import { installUv, getUvBin } from '../../managers/python.js'
16
+
17
+ const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh'
18
+ const UV_INSTALL_PS_URL = 'https://astral.sh/uv/install.ps1'
19
+
20
+ export class UvGroup extends ToolManagerGroup {
21
+ constructor() {
22
+ super({
23
+ name: 'uv',
24
+ desc: 'uv version manager (manage uv itself)',
25
+ hasRegistry: false,
26
+ isInstalled: () => {
27
+ try {
28
+ execSync(`${getUvBin()} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
29
+ return true
30
+ } catch {
31
+ return false
32
+ }
33
+ },
34
+ getPath: () => getUvBin(),
35
+ versionCmd: () => `${getUvBin()} --version`,
36
+ parseVersion: (out) => out.trim(),
37
+ install: () => installUv(),
38
+ upgrade: () => {
39
+ execSync(`${getUvBin()} self update`, { stdio: 'inherit' })
40
+ return { action: 'upgraded' }
41
+ },
42
+ uninstall: () => {
43
+ const bin = getUvBin()
44
+ if (bin !== 'uv' && fs.existsSync(bin)) {
45
+ fs.rmSync(bin, { force: true })
46
+ }
47
+ const dataDir = path.join(os.homedir(), '.local', 'share', 'uv')
48
+ if (fs.existsSync(dataDir)) {
49
+ fs.rmSync(dataDir, { recursive: true, force: true })
50
+ }
51
+ return { action: 'uninstalled', hint: 'Remove ~/.local/bin from PATH manually if needed' }
52
+ },
53
+ usage: 'opm uv <action> [options]',
54
+ actions: [
55
+ ['version', 'Show uv version'],
56
+ ['installed', 'Check if uv is installed'],
57
+ ['install', 'Install uv (curl installer from astral.sh)'],
58
+ ['upgrade', 'Update uv (uv self update)'],
59
+ ['uninstall', 'Remove uv (~/.local/bin/uv + ~/.local/share/uv)'],
60
+ ['dir', 'Show uv install directory'],
61
+ ['help', 'Show this help'],
62
+ ],
63
+ examples: [
64
+ 'opm uv version',
65
+ 'opm uv installed',
66
+ 'opm uv install',
67
+ 'opm uv upgrade',
68
+ 'opm uv dir',
69
+ 'opm uv uninstall',
70
+ ],
71
+ })
72
+ }
73
+ }