@wwkit/opm 1.0.22 → 1.0.23
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/docs/managers/mise.md +38 -0
- package/docs/managers/node.md +16 -16
- package/docs/opm.html +21 -20
- package/package.json +6 -4
- package/src/cli/groups/mise.js +128 -0
- package/src/cli/groups/package.js +54 -10
- package/src/cli/groups/version-manager.js +2 -2
- package/src/cli/index.js +2 -2
- package/src/config.json5 +15 -11
- package/src/index.js +4 -4
- package/src/managers/node.js +263 -78
- package/src/managers/runtime.js +4 -4
- package/src/presets.js +0 -1
- package/src/tools/webwork/index.js +310 -27
- package/docs/managers/nvm.md +0 -36
- package/src/cli/groups/nvm.js +0 -81
package/src/managers/node.js
CHANGED
|
@@ -1,117 +1,305 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* node(Node.js)版本管理器实现(基于
|
|
2
|
+
* node(Node.js)版本管理器实现(基于 mise)
|
|
3
3
|
*
|
|
4
|
-
* 通过
|
|
5
|
-
*
|
|
4
|
+
* 通过 mise CLI 管理多个 Node.js 版本。mise 是原生跨平台二进制(Rust),
|
|
5
|
+
* 非 shell function,直接调用即可(无需 bash)。
|
|
6
6
|
*
|
|
7
|
-
* 源查询使用
|
|
8
|
-
* 镜像通过
|
|
7
|
+
* 源查询使用 mise ls-remote node,本地查询使用 mise ls node --json(机器可读)。
|
|
8
|
+
* 镜像通过 MISE_NODE_MIRROR_URL 环境变量传递给 mise(node 后端 mirror_url)。
|
|
9
|
+
* proxy 通过 HTTP(S)_PROXY 环境变量传递(RuntimeManager._buildEnv 已处理)。
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import fs from 'node:fs'
|
|
12
13
|
import path from 'node:path'
|
|
13
14
|
import os from 'node:os'
|
|
14
|
-
import { execSync } from 'node:child_process'
|
|
15
|
+
import { execSync, spawnSync } from 'node:child_process'
|
|
15
16
|
import { RuntimeManager } from './runtime.js'
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
17
|
+
import { downloadToFile, fetchText } from '@wwkit/shared'
|
|
18
|
+
import { getConfig } from '../config.js'
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
/** mise 官方 release 下载 base URL(config mise.registry.active 覆盖) */
|
|
21
|
+
const MISE_DEFAULT_BASE = 'https://github.com/jdx/mise/releases/download'
|
|
22
|
+
/** 单次下载超时(mise 二进制约 20MB,镜像下载较慢,给足余量) */
|
|
23
|
+
const MISE_DOWNLOAD_TIMEOUT = 180000
|
|
24
|
+
|
|
25
|
+
/** 平台名映射(mise release 资产命名:windows / linux / macos) */
|
|
26
|
+
const MISE_PLATFORM = { win32: 'windows', linux: 'linux', darwin: 'macos' }
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
|
-
* 获取
|
|
29
|
+
* 获取 mise 安装目录(~/.local/bin,跨平台与 uv 一致)
|
|
23
30
|
* @returns {string}
|
|
24
31
|
*/
|
|
25
|
-
function
|
|
26
|
-
return
|
|
32
|
+
export function getMiseInstallDir() {
|
|
33
|
+
return path.join(os.homedir(), '.local', 'bin')
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
|
-
* 获取
|
|
37
|
+
* 获取 mise 二进制路径(优先 ~/.local/bin,否则依赖 PATH)
|
|
31
38
|
* @returns {string}
|
|
32
39
|
*/
|
|
33
|
-
export function
|
|
34
|
-
|
|
40
|
+
export function getMiseBin() {
|
|
41
|
+
const name = process.platform === 'win32' ? 'mise.exe' : 'mise'
|
|
42
|
+
const local = path.join(getMiseInstallDir(), name)
|
|
43
|
+
if (fs.existsSync(local)) return local
|
|
44
|
+
return 'mise'
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/**
|
|
38
|
-
* 检查
|
|
48
|
+
* 检查 mise 是否已安装
|
|
39
49
|
* @returns {boolean}
|
|
40
50
|
*/
|
|
41
|
-
export function
|
|
42
|
-
|
|
51
|
+
export function isMiseInstalled() {
|
|
52
|
+
try {
|
|
53
|
+
execSync(`${getMiseBin()} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
54
|
+
return true
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取 mise 最新版本号
|
|
62
|
+
* 优先 npm registry 镜像(快且稳,走配置的 npm 镜像),回退 GitHub API。
|
|
63
|
+
* @param {string} [proxy]
|
|
64
|
+
* @returns {Promise<string>} 版本号(如 2026.9.13);获取失败返回空串
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
async function _latestMiseVersion(proxy) {
|
|
68
|
+
try {
|
|
69
|
+
const out = execSync('npm view mise version', {
|
|
70
|
+
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000,
|
|
71
|
+
})
|
|
72
|
+
const v = out.trim()
|
|
73
|
+
if (v) return v
|
|
74
|
+
} catch {}
|
|
75
|
+
try {
|
|
76
|
+
const text = await fetchText('https://api.github.com/repos/jdx/mise/releases/latest', { proxy })
|
|
77
|
+
const data = JSON.parse(text)
|
|
78
|
+
return (data.tag_name || '').replace(/^v/, '')
|
|
79
|
+
} catch {}
|
|
80
|
+
return ''
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 构造 mise release 资产 URL:<base>/v<version>/mise-v<version>-<platform>-<arch>[.exe]
|
|
85
|
+
* @param {string} version
|
|
86
|
+
* @param {string} base
|
|
87
|
+
* @returns {string}
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
function _miseAssetUrl(version, base) {
|
|
91
|
+
const platform = MISE_PLATFORM[process.platform] || process.platform
|
|
92
|
+
const arch = process.arch
|
|
93
|
+
const ext = process.platform === 'win32' ? '.exe' : ''
|
|
94
|
+
return `${base}/v${version}/mise-v${version}-${platform}-${arch}${ext}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 生成 mise release 下载 base 尝试顺序:active → 其余预设(去重) → official
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
function _miseBases() {
|
|
103
|
+
const reg = getConfig().mise?.registry || {}
|
|
104
|
+
const ordered = []
|
|
105
|
+
const add = (u) => { if (u && !ordered.includes(u)) ordered.push(u) }
|
|
106
|
+
const active = reg.active ? (reg[reg.active] || reg.active) : ''
|
|
107
|
+
add(active)
|
|
108
|
+
for (const [key, url] of Object.entries(reg)) {
|
|
109
|
+
if (key === 'active') continue
|
|
110
|
+
add(url)
|
|
111
|
+
}
|
|
112
|
+
add(MISE_DEFAULT_BASE)
|
|
113
|
+
return ordered
|
|
43
114
|
}
|
|
44
115
|
|
|
45
116
|
/**
|
|
46
|
-
* 安装
|
|
117
|
+
* 安装 mise(下载官方 GitHub release 二进制;npm 分发在 Windows 上不可用,故直接用 release 资产)
|
|
118
|
+
*
|
|
119
|
+
* 镜像:config mise.registry 预设,依次尝试 active → 其余预设 → official,
|
|
120
|
+
* 首个下载成功即停(自动回退到可用镜像,如 ghproxy.net);
|
|
121
|
+
* proxy:downloadToFile 走 opts.proxy。
|
|
122
|
+
* 下载到 ~/.local/bin/mise[.exe],Unix 下 chmod +x。
|
|
47
123
|
* @param {string} [proxy] - 代理地址
|
|
48
|
-
* @returns {{ action: string,
|
|
124
|
+
* @returns {Promise<{ action: string, version: string, path: string, base: string, hint: string }>}
|
|
49
125
|
*/
|
|
50
|
-
export function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (proxy) {
|
|
55
|
-
env.http_proxy = proxy
|
|
56
|
-
env.https_proxy = proxy
|
|
126
|
+
export async function installMise(proxy) {
|
|
127
|
+
const version = await _latestMiseVersion(proxy)
|
|
128
|
+
if (!version) {
|
|
129
|
+
throw new Error('mise: 无法获取最新版本(npm registry 与 GitHub API 均不可用)')
|
|
57
130
|
}
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
131
|
+
const binDir = getMiseInstallDir()
|
|
132
|
+
const binPath = path.join(binDir, process.platform === 'win32' ? 'mise.exe' : 'mise')
|
|
133
|
+
fs.mkdirSync(binDir, { recursive: true })
|
|
134
|
+
|
|
135
|
+
const bases = _miseBases()
|
|
136
|
+
let lastError = null
|
|
137
|
+
for (const base of bases) {
|
|
138
|
+
const url = _miseAssetUrl(version, base)
|
|
139
|
+
try {
|
|
140
|
+
fs.rmSync(binPath, { force: true })
|
|
141
|
+
await downloadToFile(url, binPath, { proxy, timeout: MISE_DOWNLOAD_TIMEOUT })
|
|
142
|
+
if (process.platform !== 'win32') {
|
|
143
|
+
fs.chmodSync(binPath, 0o755)
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
action: 'installed',
|
|
147
|
+
version,
|
|
148
|
+
path: binPath,
|
|
149
|
+
base,
|
|
150
|
+
hint: `mise ${version} installed at ${binPath} (source: ${base}); ensure ${binDir} is on PATH`,
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
lastError = err
|
|
154
|
+
fs.rmSync(binPath, { force: true })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`mise: 所有下载源均失败,最后错误: ${lastError ? lastError.message : 'unknown'}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 读取系统 PATH(HKLM,Windows)
|
|
162
|
+
*
|
|
163
|
+
* 用 spawnSync 数组参数直调 reg(不经 cmd),避免 cmd 对值中 %VAR% 的展开与引号问题。
|
|
164
|
+
* @param {Function} [spawnSyncImpl] - 可注入的 spawnSync(测试用)
|
|
165
|
+
* @returns {string} 系统 PATH 字符串;读取失败返回空串
|
|
166
|
+
*/
|
|
167
|
+
export function getSystemPath(spawnSyncImpl = spawnSync) {
|
|
168
|
+
try {
|
|
169
|
+
const r = spawnSyncImpl(
|
|
170
|
+
'reg', ['query', 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/v', 'Path'],
|
|
171
|
+
{ encoding: 'utf8' }
|
|
172
|
+
)
|
|
173
|
+
if (r.status !== 0) return ''
|
|
174
|
+
const m = String(r.stdout || '').match(/^\s*Path\s+REG_\w+\s+(.*)$/m)
|
|
175
|
+
return m ? m[1].trim() : ''
|
|
176
|
+
} catch {
|
|
177
|
+
return ''
|
|
68
178
|
}
|
|
69
179
|
}
|
|
70
180
|
|
|
71
181
|
/**
|
|
72
|
-
*
|
|
182
|
+
* 检测系统 PATH(HKLM)中是否含 node.exe 的目录(Windows)
|
|
183
|
+
*
|
|
184
|
+
* Windows 新进程 PATH = 系统 PATH + 用户 PATH,系统 PATH 优先。若系统 PATH 有
|
|
185
|
+
* 旧版 node(如 D:\library\nodejs\node.exe),会抢先于用户 PATH 的 mise shims,
|
|
186
|
+
* 导致新终端中 node 仍是旧版。返回该目录路径;无冲突返回空串。
|
|
187
|
+
* @param {Function} [spawnSyncImpl] - 可注入的 spawnSync(测试用)
|
|
188
|
+
* @returns {string}
|
|
189
|
+
*/
|
|
190
|
+
export function detectSystemNodeConflict(spawnSyncImpl = spawnSync) {
|
|
191
|
+
if (process.platform !== 'win32') return ''
|
|
192
|
+
const sysPath = getSystemPath(spawnSyncImpl)
|
|
193
|
+
for (const entry of sysPath.split(';')) {
|
|
194
|
+
const dir = entry.trim()
|
|
195
|
+
if (!dir) continue
|
|
196
|
+
try {
|
|
197
|
+
if (fs.existsSync(path.join(dir, 'node.exe'))) return dir
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
return ''
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 把指定目录前置到系统 PATH(HKLM,Windows,幂等)
|
|
205
|
+
*
|
|
206
|
+
* 已在系统 PATH 则移到最前(不重复);写回保留 REG_EXPAND_SZ 类型。
|
|
207
|
+
* 用 spawnSync 数组参数直调 reg(不经 cmd),避免系统 PATH 中的 %VAR% 被
|
|
208
|
+
* cmd 先行展开(写回会丢失变量引用)及引号破坏命令。
|
|
209
|
+
* 写 HKLM 需管理员权限,失败(Access denied 等)返回 false,由调用方决定提示策略。
|
|
210
|
+
* @param {string[]} dirs - 需前置的目录(按序前置,最后一个在最前)
|
|
211
|
+
* @param {Function} [spawnSyncImpl] - 可注入的 spawnSync(测试用)
|
|
212
|
+
* @returns {boolean} 是否写入成功
|
|
213
|
+
*/
|
|
214
|
+
export function prependSystemPathDirs(dirs, spawnSyncImpl = spawnSync) {
|
|
215
|
+
const sysPath = getSystemPath(spawnSyncImpl)
|
|
216
|
+
if (!sysPath) return false
|
|
217
|
+
const key = (e) => String(e || '').replace(/[\\/]+$/, '').toLowerCase()
|
|
218
|
+
let entries = sysPath.split(';').map((e) => e.trim()).filter(Boolean)
|
|
219
|
+
for (const d of dirs) {
|
|
220
|
+
const needle = key(d)
|
|
221
|
+
entries = entries.filter((e) => key(e) !== needle)
|
|
222
|
+
entries.unshift(d)
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const r = spawnSyncImpl(
|
|
226
|
+
'reg', ['add', 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
|
|
227
|
+
'/v', 'Path', '/t', 'REG_EXPAND_SZ', '/d', entries.join(';'), '/f'],
|
|
228
|
+
{ encoding: 'utf8' }
|
|
229
|
+
)
|
|
230
|
+
return r.status === 0
|
|
231
|
+
} catch {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 解析 mise ls node --json 输出为已安装版本列表
|
|
238
|
+
* 输出为 JSON 数组,每项含 version / requested_version / install_path / installed / active。
|
|
73
239
|
* @param {string} out
|
|
74
240
|
* @returns {{ name: string, version: string, current: boolean }[]}
|
|
75
241
|
*/
|
|
76
|
-
export function
|
|
242
|
+
export function parseMiseInstalled(out) {
|
|
243
|
+
let records
|
|
244
|
+
try {
|
|
245
|
+
records = JSON.parse(out)
|
|
246
|
+
} catch {
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
249
|
+
if (!Array.isArray(records)) return []
|
|
77
250
|
const items = []
|
|
78
|
-
for (const
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
}
|
|
251
|
+
for (const rec of records) {
|
|
252
|
+
if (!rec || typeof rec.version !== 'string') continue
|
|
253
|
+
if (rec.installed === false) continue
|
|
254
|
+
const version = rec.version.replace(/^v/, '')
|
|
255
|
+
items.push({ name: `v${version}`, version, current: !!rec.active })
|
|
83
256
|
}
|
|
84
257
|
return items
|
|
85
258
|
}
|
|
86
259
|
|
|
260
|
+
/**
|
|
261
|
+
* 解析 mise ls-remote node 输出为可用版本列表
|
|
262
|
+
* 输出为文本,每行一个版本(如 v24.21.0)。
|
|
263
|
+
* @param {string} out
|
|
264
|
+
* @returns {string[]}
|
|
265
|
+
*/
|
|
266
|
+
export function parseMiseRemote(out) {
|
|
267
|
+
const versions = []
|
|
268
|
+
for (const line of out.split('\n')) {
|
|
269
|
+
const m = line.trim().match(/^v?(\d+\.\d+\.\d+)$/)
|
|
270
|
+
if (m) versions.push(`v${m[1]}`)
|
|
271
|
+
}
|
|
272
|
+
return versions
|
|
273
|
+
}
|
|
274
|
+
|
|
87
275
|
export class NodeManager extends RuntimeManager {
|
|
88
276
|
constructor() {
|
|
89
277
|
super({
|
|
90
278
|
name: 'node',
|
|
91
|
-
mirrorEnv: '
|
|
92
|
-
prefix:
|
|
93
|
-
needsBash:
|
|
94
|
-
isInstalled: () =>
|
|
95
|
-
installTool: () =>
|
|
96
|
-
toolName: '
|
|
97
|
-
toolMissingMsg: '
|
|
98
|
-
listCmd: ['ls', '--
|
|
99
|
-
listRemoteCmd: ['ls-remote', '
|
|
100
|
-
currentCmd: ['current'],
|
|
279
|
+
mirrorEnv: 'MISE_NODE_MIRROR_URL',
|
|
280
|
+
prefix: getMiseBin(),
|
|
281
|
+
needsBash: false,
|
|
282
|
+
isInstalled: () => isMiseInstalled(),
|
|
283
|
+
installTool: () => installMise(),
|
|
284
|
+
toolName: 'mise',
|
|
285
|
+
toolMissingMsg: 'mise not installed, run `opm mise install` first',
|
|
286
|
+
listCmd: ['ls', 'node', '--json'],
|
|
287
|
+
listRemoteCmd: ['ls-remote', 'node'],
|
|
288
|
+
currentCmd: ['current', 'node'],
|
|
101
289
|
parseCurrent: (out) => {
|
|
102
|
-
const c = out.trim()
|
|
290
|
+
const c = out.trim().replace(/^v/, '')
|
|
103
291
|
return c && c !== 'system' && c !== 'none' ? c : ''
|
|
104
292
|
},
|
|
105
|
-
installCmd: (v) => ['install', v],
|
|
106
|
-
uninstallCmd: (n) => ['uninstall', n.startsWith('v') ? n :
|
|
293
|
+
installCmd: (v) => ['install', `node@${v}`],
|
|
294
|
+
uninstallCmd: (n) => ['uninstall', `node@${n.startsWith('v') ? n.slice(1) : n}`],
|
|
107
295
|
resultName: (v) => `v${v}`,
|
|
108
|
-
upgradeLts: ['install', '
|
|
109
|
-
useCmd: (v) => ['use', v],
|
|
110
|
-
useAction: 'switched',
|
|
111
|
-
defaultCmd: (v) => ['
|
|
112
|
-
noCacheMsg: 'no cache for
|
|
113
|
-
installUsage: 'Usage: opm node install <version> (e.g.
|
|
114
|
-
notInstalledHint: '
|
|
296
|
+
upgradeLts: ['install', 'node@lts'],
|
|
297
|
+
useCmd: (v) => ['use', '-g', `node@${v}`],
|
|
298
|
+
useAction: 'switched (global)',
|
|
299
|
+
defaultCmd: (v) => ['use', '-g', `node@${v}`],
|
|
300
|
+
noCacheMsg: 'no cache for mise-managed node',
|
|
301
|
+
installUsage: 'Usage: opm node install <version> (e.g. 20, 24, v24.19.0, --lts)',
|
|
302
|
+
notInstalledHint: 'mise not installed or no node version active, run `opm mise install` and `opm node install <version>`',
|
|
115
303
|
upgradeNoVersionMsg: 'No Node.js version available to upgrade to',
|
|
116
304
|
info: {
|
|
117
305
|
description: 'Node.js JavaScript runtime',
|
|
@@ -129,37 +317,34 @@ export class NodeManager extends RuntimeManager {
|
|
|
129
317
|
*/
|
|
130
318
|
getInstallDir() {
|
|
131
319
|
try {
|
|
132
|
-
const out = this._exec(['
|
|
133
|
-
|
|
134
|
-
|
|
320
|
+
const out = this._exec(['ls', 'node', '--json'], { allowNonZero: true })
|
|
321
|
+
const items = parseMiseInstalled(out)
|
|
322
|
+
const current = items.find((i) => i.current)
|
|
323
|
+
if (current) {
|
|
324
|
+
const rec = JSON.parse(out).find((r) => r.version.replace(/^v/, '') === current.version)
|
|
325
|
+
if (rec && rec.install_path) return rec.install_path
|
|
326
|
+
}
|
|
135
327
|
} catch {}
|
|
136
328
|
return ''
|
|
137
329
|
}
|
|
138
330
|
|
|
139
331
|
/**
|
|
140
|
-
* 解析
|
|
332
|
+
* 解析 mise ls node --json 输出为已安装版本列表
|
|
141
333
|
* @param {string} out
|
|
142
334
|
* @returns {{ name: string, version: string, current: boolean }[]}
|
|
143
335
|
* @private
|
|
144
336
|
*/
|
|
145
337
|
_parseInstalled(out) {
|
|
146
|
-
return
|
|
338
|
+
return parseMiseInstalled(out)
|
|
147
339
|
}
|
|
148
340
|
|
|
149
341
|
/**
|
|
150
|
-
* 解析
|
|
342
|
+
* 解析 mise ls-remote node 输出为可用版本列表
|
|
151
343
|
* @param {string} out
|
|
152
344
|
* @returns {string[]}
|
|
153
345
|
* @private
|
|
154
346
|
*/
|
|
155
347
|
_parseRemote(out) {
|
|
156
|
-
|
|
157
|
-
for (const line of out.split('\n')) {
|
|
158
|
-
const m = line.match(/^\s*(->)?\s*v(\d+\.\d+\.\d+)\b/)
|
|
159
|
-
if (m) {
|
|
160
|
-
versions.push(`v${m[2]}`)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return versions
|
|
348
|
+
return parseMiseRemote(out)
|
|
164
349
|
}
|
|
165
350
|
}
|
package/src/managers/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 运行时版本管理器基类(node / php / python 共用)
|
|
3
3
|
*
|
|
4
|
-
* 三个运行时均通过底层版本管理工具(
|
|
4
|
+
* 三个运行时均通过底层版本管理工具(mise / phpenv / uv)管理多版本,
|
|
5
5
|
* 接口一致(registry/search/view/info/versions/list/install/uninstall/upgrade/use/current/default)。
|
|
6
6
|
* 差异(命令前缀、镜像环境变量、动作命令参数、解析函数)通过 cfg 配置注入;
|
|
7
7
|
* 解析函数由子类实现。python 的 getVersion/currentVersion/setDefault 等差异较大,
|
|
@@ -19,8 +19,8 @@ const shell = new Shell()
|
|
|
19
19
|
* @typedef {Object} RuntimeConfig
|
|
20
20
|
* @property {string} name
|
|
21
21
|
* @property {string} mirrorEnv - dist 镜像环境变量名
|
|
22
|
-
* @property {string} prefix - 工具调用前缀(如 `source ".../
|
|
23
|
-
* @property {boolean} [needsBash=false] - 命令是否需要 bash(
|
|
22
|
+
* @property {string} prefix - 工具调用前缀(如 `mise`、`source ".../phpenv"`、bin 路径)
|
|
23
|
+
* @property {boolean} [needsBash=false] - 命令是否需要 bash(phpenv 脚本);mise/uv 原生二进制为 false
|
|
24
24
|
* @property {() => boolean} isInstalled
|
|
25
25
|
* @property {string} toolMissingMsg
|
|
26
26
|
* @property {string[]} listCmd - 列出已安装版本的参数
|
|
@@ -243,7 +243,7 @@ export class RuntimeManager extends PackageManager {
|
|
|
243
243
|
}
|
|
244
244
|
if (!this.cfg.isInstalled() && this.cfg.installTool) {
|
|
245
245
|
console.log(`[opm] ${this.cfg.toolName} not installed, auto-installing...`)
|
|
246
|
-
this.cfg.installTool()
|
|
246
|
+
await this.cfg.installTool()
|
|
247
247
|
}
|
|
248
248
|
this._execInherit(this.cfg.installCmd(version), { proxy: opts.proxy })
|
|
249
249
|
return { name: this.cfg.resultName(version), version, action: 'installed' }
|
package/src/presets.js
CHANGED
|
@@ -14,7 +14,6 @@ export const APT_PRESETS = _getPresets('apt')
|
|
|
14
14
|
export const BREW_PRESETS = _getPresets('brew')
|
|
15
15
|
export const COMPOSER_PRESETS = _getPresets('composer')
|
|
16
16
|
export const BUN_PRESETS = _getPresets('bun')
|
|
17
|
-
export const NVM_PRESETS = _getPresets('nvm')
|
|
18
17
|
export const NODE_PRESETS = _getPresets('node')
|
|
19
18
|
export const PHPENV_PRESETS = _getPresets('phpenv')
|
|
20
19
|
export const PHP_PRESETS = _getPresets('php')
|