@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,186 @@
1
+ /**
2
+ * 版本管理器自身命令组基类(nvm / phpenv / uv 共用)
3
+ *
4
+ * 三个版本管理器均提供 version / registry / install / upgrade / uninstall / view 动作,
5
+ * 差异仅在底层命令与安装/升级/卸载方式,故抽象为数据驱动基类。
6
+ * 新增版本管理器:子类提供 cfg 配置(isInstalled / install / upgrade / uninstall / help 等)即可。
7
+ */
8
+
9
+ import { execSync } from 'node:child_process'
10
+ import { parseFlags } from '../helpers/args.js'
11
+ import { Shell } from '@wwkit/shared'
12
+ import { getActiveRegistry, setActiveRegistry, getPresets } from '../../config.js'
13
+ import { output } from '../../formatter.js'
14
+
15
+ const shell = new Shell()
16
+
17
+ /**
18
+ * @typedef {Object} ToolGroupConfig
19
+ * @property {string} name - 命令名
20
+ * @property {string} desc - 顶层帮助描述
21
+ * @property {boolean} [hasRegistry=true] - 是否支持 registry 动作(uv 为 false)
22
+ * @property {() => boolean} isInstalled
23
+ * @property {boolean} [needsBash=false] - versionCmd 是否需要 bash(nvm source / phpenv 脚本);uv 为 false
24
+ * @property {() => string} getPath - view 展示的安装路径
25
+ * @property {() => string} versionCmd - 返回查询版本的 shell 命令
26
+ * @property {(out: string) => string} [parseVersion] - 解析版本输出
27
+ * @property {() => Object} install - 安装并返回结果字段(失败时 throw)
28
+ * @property {() => Object} upgrade - 升级并返回结果字段(失败时 throw)
29
+ * @property {() => Object} uninstall - 卸载并返回结果字段(失败时 throw)
30
+ * @property {string} usage - 帮助 usage 行
31
+ * @property {[string,string][]} actions - 帮助动作表 [签名, 描述]
32
+ * @property {string[]} examples - 帮助示例
33
+ */
34
+
35
+ export class ToolManagerGroup {
36
+ /**
37
+ * @param {ToolGroupConfig} cfg
38
+ */
39
+ constructor(cfg) {
40
+ this.name = cfg.name
41
+ this.desc = cfg.desc
42
+ this.hasRegistry = cfg.hasRegistry !== false
43
+ this.cfg = cfg
44
+ }
45
+
46
+ async run(argv) {
47
+ const [action, ...rest] = argv
48
+
49
+ if (!action || action === '-h' || action === '--help' || action === 'help') {
50
+ this.printHelp()
51
+ return
52
+ }
53
+
54
+ if (action === 'registry' && !this.hasRegistry) {
55
+ console.error(`Action "registry" is not supported for ${this.name}`)
56
+ this.printHelp()
57
+ process.exit(1)
58
+ }
59
+
60
+ switch (action) {
61
+ case 'version':
62
+ return this._version()
63
+ case 'installed':
64
+ return this._installed()
65
+ case 'registry':
66
+ return this._registry(parseFlags(rest))
67
+ case 'install':
68
+ return this._install()
69
+ case 'upgrade':
70
+ return this._upgrade()
71
+ case 'uninstall':
72
+ return this._uninstall()
73
+ case 'dir':
74
+ return this._dir()
75
+ default:
76
+ console.error(`Unknown action: ${action}`)
77
+ this.printHelp()
78
+ process.exit(1)
79
+ }
80
+ }
81
+
82
+ _version() {
83
+ if (!this.cfg.isInstalled()) {
84
+ output('')
85
+ return
86
+ }
87
+ try {
88
+ const out = execSync(this.cfg.versionCmd(), { encoding: 'utf8', shell: shell.shellForExec(this.cfg.needsBash) })
89
+ const parsed = this.cfg.parseVersion ? this.cfg.parseVersion(out) : out.trim()
90
+ output(parsed.replace(/^v/, ''))
91
+ } catch {
92
+ output('')
93
+ }
94
+ }
95
+
96
+ _registry(parsed) {
97
+ const [sub, ...args] = parsed.positional
98
+ if (sub === 'set') {
99
+ const url = args[0]
100
+ if (!url) {
101
+ console.error(`Usage: opm ${this.name} registry set <url|preset>`)
102
+ process.exit(1)
103
+ }
104
+ output({ registry: setActiveRegistry(this.name, url) })
105
+ } else if (sub === 'presets') {
106
+ const items = Object.entries(getPresets(this.name)).map(([n, u]) => ({ name: n, url: u }))
107
+ output(items)
108
+ } else {
109
+ output({ registry: getActiveRegistry(this.name) })
110
+ }
111
+ }
112
+
113
+ _install() {
114
+ if (this.cfg.isInstalled()) {
115
+ output({ name: this.name, installed: true, hint: `${this.name} already installed, run \`opm ${this.name} upgrade\` to update` })
116
+ return
117
+ }
118
+ try {
119
+ output({ name: this.name, installed: true, ...(this.cfg.install() || {}) })
120
+ } catch (err) {
121
+ console.error(err.message || `Failed to install ${this.name}`)
122
+ process.exit(1)
123
+ }
124
+ }
125
+
126
+ _upgrade() {
127
+ if (!this.cfg.isInstalled()) {
128
+ console.error(`${this.name} not installed, run \`opm ${this.name} install\` first`)
129
+ process.exit(1)
130
+ }
131
+ try {
132
+ output({ name: this.name, ...(this.cfg.upgrade() || {}) })
133
+ } catch (err) {
134
+ console.error(err.message || `Failed to upgrade ${this.name}`)
135
+ process.exit(1)
136
+ }
137
+ }
138
+
139
+ _uninstall() {
140
+ if (!this.cfg.isInstalled()) {
141
+ output({ name: this.name, installed: false, hint: `${this.name} not installed` })
142
+ return
143
+ }
144
+ try {
145
+ output({ name: this.name, ...(this.cfg.uninstall() || {}) })
146
+ } catch (err) {
147
+ console.error(err.message || `Failed to remove ${this.name}`)
148
+ process.exit(1)
149
+ }
150
+ }
151
+
152
+ _dir() {
153
+ const installed = this.cfg.isInstalled()
154
+ output({ name: this.name, installed, install: installed ? this.cfg.getPath() : '' })
155
+ }
156
+
157
+ _installed() {
158
+ const installed = this.cfg.isInstalled()
159
+ const result = { name: this.name, installed }
160
+ if (installed) {
161
+ result.path = this.cfg.getPath()
162
+ try {
163
+ const out = execSync(this.cfg.versionCmd(), { encoding: 'utf8', shell: shell.shellForExec(this.cfg.needsBash) })
164
+ result.version = this.cfg.parseVersion ? this.cfg.parseVersion(out) : out.trim()
165
+ } catch {}
166
+ }
167
+ output(result)
168
+ }
169
+
170
+ printHelp() {
171
+ const actionLines = this.cfg.actions.map(([sig, desc]) => ` ${sig.padEnd(34)} ${desc}`).join('\n')
172
+ const exampleLines = this.cfg.examples.map((e) => ` ${e}`).join('\n')
173
+ console.log(`
174
+ Usage: ${this.cfg.usage}
175
+
176
+ Actions:
177
+ ${actionLines}
178
+
179
+ Options:
180
+ -h, --help Show this help
181
+
182
+ Examples:
183
+ ${exampleLines}
184
+ `)
185
+ }
186
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 共享命令行参数解析器
3
+ *
4
+ * 支持以下格式的参数:
5
+ * --flag value 长标志(空格分隔)
6
+ * --flag=value 长标志(等号形式)
7
+ * -f value 短标志(空格分隔)
8
+ * -f=value 短标志(等号形式)
9
+ * positional 位置参数(不以 - 开头)
10
+ *
11
+ * @param {string[]} argv - 原始参数数组
12
+ * @returns {{ flags: Record<string, string>, positional: string[] }}
13
+ */
14
+ export function parseFlags(argv) {
15
+ const flags = {}
16
+ const positional = []
17
+
18
+ for (let i = 0; i < argv.length; i++) {
19
+ const arg = argv[i]
20
+
21
+ if (arg.startsWith('--') && arg.includes('=')) {
22
+ const idx = arg.indexOf('=')
23
+ flags[arg.slice(2, idx)] = arg.slice(idx + 1)
24
+ } else if (arg.startsWith('--')) {
25
+ const next = argv[i + 1]
26
+ if (next !== undefined && !next.startsWith('-')) {
27
+ flags[arg.slice(2)] = next
28
+ i++
29
+ } else {
30
+ flags[arg.slice(2)] = 'true'
31
+ }
32
+ } else if (arg.startsWith('-') && arg.includes('=')) {
33
+ const idx = arg.indexOf('=')
34
+ flags[arg.slice(1, idx)] = arg.slice(idx + 1)
35
+ } else if (arg.startsWith('-') && arg.length > 1) {
36
+ const next = argv[i + 1]
37
+ if (next !== undefined && !next.startsWith('-')) {
38
+ flags[arg.slice(1)] = next
39
+ i++
40
+ } else {
41
+ flags[arg.slice(1)] = 'true'
42
+ }
43
+ } else {
44
+ positional.push(arg)
45
+ }
46
+ }
47
+
48
+ return { flags, positional }
49
+ }
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * opm CLI 入口
5
+ *
6
+ * 统一命令行工具,聚合包管理与配置查看。顶层命令按「命令组」组织,
7
+ * 注册表见下方 GROUPS(帮助、命令列表均由 GROUPS 动态生成,无需手动维护)。
8
+ */
9
+
10
+ import { pathToFileURL } from 'node:url'
11
+ import { realpathSync, readFileSync } from 'node:fs'
12
+ import { PackageGroup, RuntimeGroup } from './groups/package.js'
13
+ import { ConfigGroup } from './groups/config.js'
14
+ import { NvmGroup } from './groups/nvm.js'
15
+ import { PhpenvGroup } from './groups/phpenv.js'
16
+ import { UvGroup } from './groups/uv.js'
17
+ import { HomebrewGroup } from './groups/homebrew.js'
18
+ import { EnsureGroup } from './groups/ensure.js'
19
+ import { HarnessGroup } from '../harnesses/base.js'
20
+ import { opencodeHarness } from '../harnesses/opencode/index.js'
21
+ import { CftGroup } from '../binaries/cft/index.js'
22
+ import { generateDocHtml } from './doc.js'
23
+
24
+ /**
25
+ * 顶层命令组注册表
26
+ * 新增命令组:实现 run(argv) + printHelp() + desc 后在此注册(帮助输出自动跟随)。
27
+ * @type {Record<string, object>}
28
+ */
29
+ const GROUPS = {
30
+ npm: new PackageGroup('npm'),
31
+ pip: new PackageGroup('pip'),
32
+ dnf: new PackageGroup('dnf'),
33
+ apt: new PackageGroup('apt'),
34
+ brew: new PackageGroup('brew'),
35
+ homebrew: new HomebrewGroup(),
36
+ composer: new PackageGroup('composer'),
37
+ bun: new PackageGroup('bun'),
38
+ winget: new PackageGroup('winget'),
39
+ node: new RuntimeGroup('node'),
40
+ nvm: new NvmGroup(),
41
+ php: new RuntimeGroup('php'),
42
+ phpenv: new PhpenvGroup(),
43
+ python: new RuntimeGroup('python'),
44
+ uv: new UvGroup(),
45
+ config: new ConfigGroup(),
46
+ opencode: new HarnessGroup(opencodeHarness),
47
+ cft: new CftGroup(),
48
+ ensure: new EnsureGroup(),
49
+ }
50
+
51
+ class CLI {
52
+ async run(argv) {
53
+ const [groupName, ...rest] = argv
54
+
55
+ if (!groupName || groupName === '-h' || groupName === '--help' || groupName === 'help') {
56
+ this.printHelp()
57
+ return
58
+ }
59
+
60
+ if (groupName === 'version' || groupName === '-v' || groupName === '--version') {
61
+ const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
62
+ console.log(pkg.version)
63
+ return
64
+ }
65
+
66
+ if (groupName === 'doc') {
67
+ generateDocHtml()
68
+ return
69
+ }
70
+
71
+ const group = GROUPS[groupName]
72
+ if (!group) {
73
+ console.error(`Unknown command: ${groupName}`)
74
+ console.error(`Available commands: ${Object.keys(GROUPS).join(', ')}`)
75
+ process.exit(1)
76
+ }
77
+
78
+ try {
79
+ await group.run(rest)
80
+ } catch (err) {
81
+ console.error(`[opm] Error: ${err.message}`)
82
+ process.exit(1)
83
+ }
84
+ }
85
+
86
+ printHelp() {
87
+ const groupLines = Object.entries(GROUPS)
88
+ .map(([name, group]) => ` ${name.padEnd(12)} ${group.desc || group.name}`)
89
+ .join('\n')
90
+
91
+ console.log(`
92
+ Usage: opm <command> [args] [options]
93
+
94
+ Commands:
95
+ ${groupLines}
96
+ version Show opm version
97
+ doc Build docs HTML and open in browser
98
+
99
+ Options:
100
+ -g, --global Global operation (npm: global node_modules; pip: system Python)
101
+ -p, --proxy <url> Proxy address (search/info/versions/outdated/install/upgrade)
102
+ -h, --help Show help
103
+
104
+ Run "opm <command> help" for command details.
105
+
106
+ Examples:
107
+ opm npm registry set taobao Set npm registry to taobao mirror
108
+ opm pip install requests Install requests
109
+ opm config Show common config (proxy + registries)
110
+ opm opencode clear Clean up opencode sessions before today
111
+ opm opencode clear --days 7 Keep sessions from the last 7 days
112
+ `)
113
+ }
114
+ }
115
+
116
+ if (process.argv[1]) {
117
+ try {
118
+ const resolved = pathToFileURL(realpathSync(process.argv[1])).href
119
+ if (import.meta.url === resolved || import.meta.url === resolved + '/index.js' || import.meta.url === resolved + 'index.js') {
120
+ const cli = new CLI()
121
+ cli.run(process.argv.slice(2)).catch((err) => {
122
+ console.error(`[opm] Error: ${err.message}`)
123
+ process.exit(1)
124
+ })
125
+ }
126
+ } catch {}
127
+ }
128
+
129
+ export { CLI, GROUPS }
package/src/config.js ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * opm 配置
3
+ *
4
+ * 维护一份用户可编辑的 config.json5(安装后复制到标准配置目录),
5
+ * 采用 @wwkit/shared 的多层合并读取:
6
+ * 内置默认(src/config.json5) → 用户文件(~/.config/opm/config.json5) → OPM_CONFIG_CONTENT
7
+ *
8
+ * npm/pip/dnf/apt/brew 的 registry 预设与 active(当前激活镜像)都来自合并后的配置,
9
+ * active 可以是预设 key(如 "taobao"),也可以是完整 URL。
10
+ */
11
+
12
+ import fs from 'node:fs'
13
+ import path from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+ import { createConfigLoader, getXdgConfigDir } from '@wwkit/shared'
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
18
+
19
+ const _loader = createConfigLoader({
20
+ builtinConfigPath: path.join(__dirname, 'config.json5'),
21
+ userConfigDir: getXdgConfigDir('opm'),
22
+ envContentVar: 'OPM_CONFIG_CONTENT',
23
+ envDirVar: 'OPM_CONFIG_DIR',
24
+ })
25
+
26
+ export function getUserConfigDir() {
27
+ return process.env.OPM_CONFIG_DIR || getXdgConfigDir('opm')
28
+ }
29
+
30
+ export function getUserConfigFile() {
31
+ return path.join(getUserConfigDir(), 'config.json5')
32
+ }
33
+
34
+ /**
35
+ * 读取合并后的完整配置
36
+ * @returns {object} { proxy, npm:{registry}, pip:{registry}, dnf:{registry}, apt:{registry}, brew:{registry} }
37
+ */
38
+ export function getConfig() {
39
+ return _loader.getConfig()
40
+ }
41
+
42
+ /**
43
+ * 复制内置配置到用户配置目录(不覆盖已存在文件)
44
+ * @returns {boolean} 是否写入了新文件
45
+ */
46
+ export function copyBuiltinConfig() {
47
+ return _loader.copyBuiltinConfig()
48
+ }
49
+
50
+ /**
51
+ * 按点分路径取配置子节点
52
+ * @param {object} config - getConfig 的返回结果
53
+ * @param {string} [path] - 如 'proxy' / 'npm.registry'
54
+ * @returns {*} 子节点;路径为空时返回整个 config
55
+ */
56
+ export function getSection(config, path) {
57
+ if (!path) return config
58
+ return path.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), config)
59
+ }
60
+
61
+ const presetCache = new Map()
62
+
63
+ /**
64
+ * 获取指定管理器的镜像预设(不含 active)
65
+ * @param {string} manager - 管理器名称 (npm/pip/dnf/apt/brew)
66
+ * @returns {Record<string, string>}
67
+ */
68
+ export function getPresets(manager) {
69
+ const cached = presetCache.get(manager)
70
+ if (cached) return cached
71
+ const reg = getConfig()[manager]?.registry ?? {}
72
+ const presets = { ...reg }
73
+ delete presets.active
74
+ Object.freeze(presets)
75
+ presetCache.set(manager, presets)
76
+ return presets
77
+ }
78
+
79
+ /**
80
+ * 解析镜像地址:如果输入是预设 key 则返回对应 URL,否则原样返回
81
+ * @param {string} input - URL 或预设 key
82
+ * @param {string} manager - 管理器名称
83
+ * @returns {string}
84
+ */
85
+ export function resolveRegistryInput(input, manager) {
86
+ const presets = getPresets(manager)
87
+ return presets[input] || input
88
+ }
89
+
90
+ /**
91
+ * 解析当前激活镜像地址(key → URL,URL 原样返回)
92
+ * 未配置 active 时回退到官方源。
93
+ * @param {string} manager - 管理器名称
94
+ * @returns {string}
95
+ */
96
+ export function getActiveRegistry(manager) {
97
+ const reg = getConfig()[manager]?.registry
98
+ if (!reg) return ''
99
+ const active = reg.active
100
+ if (!active) return reg.official || ''
101
+ return reg[active] || active
102
+ }
103
+
104
+ /**
105
+ * 解析配置中激活的代理地址(key → URL,URL 原样返回)
106
+ * active 为空时返回 ''(不启用代理)。
107
+ * @returns {string}
108
+ */
109
+ export function getActiveProxy() {
110
+ const proxy = getConfig().proxy ?? {}
111
+ const active = proxy.active
112
+ if (!active) return ''
113
+ return proxy[active] || active
114
+ }
115
+
116
+ /**
117
+ * 将指定镜像设为某个管理器的 active,写入用户配置文件
118
+ * @param {string} manager - 管理器名称
119
+ * @param {string} input - 预设 key 或完整 URL
120
+ * @returns {string} 解析后的 URL
121
+ */
122
+ export function setActiveRegistry(manager, input) {
123
+ const resolved = resolveRegistryInput(input, manager)
124
+ const cfg = getConfig()
125
+ if (!cfg[manager]) cfg[manager] = {}
126
+ if (!cfg[manager].registry) cfg[manager].registry = {}
127
+ cfg[manager].registry.active = input
128
+ presetCache.clear()
129
+ const userConfigFile = getUserConfigFile()
130
+ fs.mkdirSync(path.dirname(userConfigFile), { recursive: true })
131
+ fs.writeFileSync(userConfigFile, `${JSON.stringify(cfg, null, 2)}\n`, 'utf8')
132
+ return resolved
133
+ }
@@ -0,0 +1,158 @@
1
+ // opm 用户配置(可编辑)
2
+ //
3
+ // 安装后复制到 ~/.config/opm/config.json5(或 $XDG_CONFIG_HOME/opm/config.json5),
4
+ // 通过 OPM_CONFIG_DIR / OPM_CONFIG_CONTENT 可覆盖(测试与特殊场景)。
5
+ // 合并顺序(低 → 高):内置默认 → 用户文件 → OPM_CONFIG_CONTENT。
6
+ // 字符串中的 {env:XXX} 占位符会在读取时替换为环境变量。
7
+
8
+ {
9
+ // 代理预设与当前激活项
10
+ // active 默认为空(不使用代理);可改为预设 key(如 "local")或完整 URL。
11
+ // 命令行 -p/--proxy 传入时优先使用传入值。
12
+ proxy: {
13
+ local: "socks5://127.0.0.1:1080",
14
+ active: "",
15
+ },
16
+
17
+ // npm 镜像预设与当前激活项
18
+ // active 可以是预设 key(如 "taobao"),也可以是完整 URL
19
+ npm: {
20
+ registry: {
21
+ official: "https://registry.npmjs.org/",
22
+ taobao: "https://registry.npmmirror.com/",
23
+ huawei: "https://cmc.centralrepo.rnd.huawei.com/artifactory/api/npm/npm-central-repo/",
24
+ active: "taobao",
25
+ },
26
+ },
27
+
28
+ pip: {
29
+ registry: {
30
+ official: "https://pypi.org/simple",
31
+ tsinghua: "https://pypi.tuna.tsinghua.edu.cn/simple",
32
+ huawei: "https://cmc.centralrepo.rnd.huawei.com/artifactory/api/pypi/pypi-repo/simple",
33
+ active: "tsinghua",
34
+ },
35
+ },
36
+
37
+ dnf: {
38
+ registry: {
39
+ hce: "http://his-mirrors.huawei.com/install/hce/2.0/os/x86_64/",
40
+ "hce-updates": "http://his-mirrors.huawei.com/install/hce/2.0/updates/x86_64/",
41
+ fedora: "https://download.fedoraproject.org/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/",
42
+ "fedora-updates": "https://download.fedoraproject.org/pub/fedora/linux/updates/$releasever/Everything/$basearch/",
43
+ epel: "https://dl.fedoraproject.org/pub/epel/$releasever/Everything/$basearch/",
44
+ "epel-testing": "https://dl.fedoraproject.org/pub/epel/testing/$releasever/Everything/$basearch/",
45
+ active: "hce",
46
+ },
47
+ },
48
+
49
+ apt: {
50
+ registry: {
51
+ "ubuntu-tsinghua": "deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse",
52
+ "ubuntu-huawei": "deb https://mirrors.huaweicloud.com/ubuntu/ focal main restricted universe multiverse",
53
+ "ubuntu-aliyun": "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse",
54
+ "debian-official": "deb http://deb.debian.org/debian/ bookworm main",
55
+ "debian-tsinghua": "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main",
56
+ active: "ubuntu-tsinghua",
57
+ },
58
+ },
59
+
60
+ // brew(Homebrew)镜像预设(macOS)
61
+ // active 指向预设 key(如 "tsinghua")或 bottle 域名 URL;
62
+ // 非 official 时 install/upgrade 自动传 HOMEBREW_API_DOMAIN / HOMEBREW_BOTTLE_DOMAIN
63
+ brew: {
64
+ registry: {
65
+ official: "https://ghcr.io/v2/homebrew",
66
+ tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles",
67
+ ustc: "https://mirrors.ustc.edu.cn/homebrew-bottles",
68
+ active: "tsinghua",
69
+ },
70
+ },
71
+
72
+ // composer(PHP)镜像预设
73
+ // active 指向预设 key 或 P2 元数据 base URL;
74
+ // 镜像仅作用于源查询(search/info/versions 的 P2 元数据拉取),
75
+ // install/upgrade 使用 composer CLI 自身的仓库配置
76
+ composer: {
77
+ registry: {
78
+ official: "https://repo.packagist.org",
79
+ aliyun: "https://mirrors.aliyun.com/composer",
80
+ tencent: "https://mirrors.cloud.tencent.com/composer",
81
+ active: "aliyun",
82
+ },
83
+ },
84
+
85
+ // bun(Bun runtime + 包管理器)镜像预设
86
+ // Bun 使用 npm registry,复用相同镜像源
87
+ // 镜像通过 BUN_CONFIG_REGISTRY 环境变量传递
88
+ bun: {
89
+ registry: {
90
+ official: "https://registry.npmjs.org/",
91
+ taobao: "https://registry.npmmirror.com/",
92
+ huawei: "https://cmc.centralrepo.rnd.huawei.com/artifactory/api/npm/npm-central-repo/",
93
+ active: "taobao",
94
+ },
95
+ },
96
+
97
+ // nvm 自身镜像预设(git clone 地址)
98
+ nvm: {
99
+ registry: {
100
+ official: "https://github.com/nvm-sh/nvm.git",
101
+ gitee: "https://gitee.com/mirrors/nvm.git",
102
+ active: "official",
103
+ },
104
+ },
105
+
106
+ // node(Node.js)镜像预设(dist 下载地址,传给 NVM_NODEJS_ORG_MIRROR)
107
+ node: {
108
+ defaultVersion: "24",
109
+ registry: {
110
+ official: "https://nodejs.org/dist",
111
+ taobao: "https://npmmirror.com/mirrors/node",
112
+ tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/",
113
+ active: "taobao",
114
+ },
115
+ },
116
+
117
+ // phpenv 自身镜像预设(git clone 地址)
118
+ phpenv: {
119
+ registry: {
120
+ official: "https://github.com/phpenv/phpenv.git",
121
+ gitee: "https://gitee.com/mirrors/phpenv.git",
122
+ active: "official",
123
+ },
124
+ },
125
+
126
+ // php(PHP runtime)镜像预设(dist 下载地址,传给 PHP_BUILD_MIRROR_URL)
127
+ php: {
128
+ defaultVersion: "8.3",
129
+ registry: {
130
+ official: "https://www.php.net/distributions",
131
+ tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/php",
132
+ ustc: "https://mirrors.ustc.edu.cn/php",
133
+ active: "official",
134
+ },
135
+ },
136
+
137
+ // python(Python runtime)镜像预设(传给 UV_PYTHON_INSTALL_MIRROR)
138
+ // uv 从 python-build-standalone GitHub releases 下载 Python 发行版
139
+ python: {
140
+ defaultVersion: "3.12",
141
+ registry: {
142
+ official: "https://github.com/astral-sh/python-build-standalone/releases/download",
143
+ ghproxy: "https://ghproxy.com/https://github.com/astral-sh/python-build-standalone/releases/download",
144
+ active: "official",
145
+ },
146
+ },
147
+
148
+ // cft(Chrome for Testing)二进制工具
149
+ // install 缺省版本 + registry 预设(active 默认用镜像,而非 official)
150
+ cft: {
151
+ defaultVersion: "141.0.7390.54",
152
+ registry: {
153
+ official: "https://googlechromelabs.github.io/chrome-for-testing",
154
+ npmmirror: "https://npmmirror.com/mirrors/chrome-for-testing",
155
+ active: "npmmirror",
156
+ },
157
+ },
158
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 输出格式化工具
3
+ *
4
+ * 统一以 JSON 格式输出(默认即 JSON,无 human 模式)。
5
+ */
6
+
7
+ /**
8
+ * 格式化输出结果
9
+ * @param {*} data - 要输出的数据
10
+ */
11
+ export function output(data) {
12
+ console.log(JSON.stringify(data, null, 2))
13
+ }