@wwkit/opm 1.0.13 → 1.0.15
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 +32 -0
- package/package.json +13 -4
- package/src/binaries/base.js +1 -1
- package/src/cli/groups/env.js +451 -0
- package/src/cli/groups/ping.js +87 -26
- package/src/cli/index.js +2 -0
- package/src/harnesses/opencode/index.js +4 -3
- package/src/index.js +2 -1
- package/src/managers/brew.js +1 -0
- package/src/managers/bun.js +7 -34
- package/src/managers/composer.js +1 -1
- package/src/managers/pip.js +1 -1
- package/src/managers/proc.js +7 -1
- package/src/tools/webwork/index.js +31 -16
- package/src/managers/http.js +0 -432
package/src/cli/groups/ping.js
CHANGED
|
@@ -18,6 +18,9 @@ import { getActiveProxy, getActiveRegistry, getConfig } from '../../config.js'
|
|
|
18
18
|
|
|
19
19
|
const shell = new Shell()
|
|
20
20
|
|
|
21
|
+
// 跨平台 null device(curl -o 丢弃输出体)
|
|
22
|
+
const NULL_DEV = process.platform === 'win32' ? 'NUL' : '/dev/null'
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* 从 URL 或 deb 行中提取主机名和端口(用于 ping 目标)
|
|
23
26
|
* @param {string} url
|
|
@@ -46,6 +49,15 @@ function isUrl(addr) {
|
|
|
46
49
|
return /^https?:\/\//i.test(addr)
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
// HTTP 状态码 → 可达性判断:2xx/3xx 视为可达,allowlist 中的状态码
|
|
53
|
+
// 虽非成功响应但证明服务器已响应(如 404 表示主机/代理存活,
|
|
54
|
+
// 429 表示临时限流,服务器实际存活)
|
|
55
|
+
const REACHABLE_ALLOWLIST = [404, 429]
|
|
56
|
+
|
|
57
|
+
function httpReachable(httpCode) {
|
|
58
|
+
return (httpCode >= 200 && httpCode < 400) || REACHABLE_ALLOWLIST.includes(httpCode)
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
/**
|
|
50
62
|
* 获取所有含 registry 配置的工具名列表
|
|
51
63
|
* @returns {string[]}
|
|
@@ -77,19 +89,30 @@ function checkByPing(host, count) {
|
|
|
77
89
|
? ['-n', String(count), '-w', '2000', host]
|
|
78
90
|
: ['-c', String(count), '-W', '2', host]
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
let child
|
|
93
|
+
try {
|
|
94
|
+
child = spawn('ping', args, { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
|
|
95
|
+
} catch {
|
|
96
|
+
resolve({
|
|
97
|
+
reachable: false, sent: count, received: 0, loss: count,
|
|
98
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'ping command failed to execute',
|
|
99
|
+
})
|
|
100
|
+
return
|
|
101
|
+
}
|
|
81
102
|
let stdout = ''
|
|
82
103
|
let stderr = ''
|
|
83
104
|
child.stdout.on('data', (d) => { stdout += d })
|
|
84
105
|
child.stderr.on('data', (d) => { stderr += d })
|
|
85
|
-
child.on('close', () => {
|
|
106
|
+
child.on('close', (code) => {
|
|
86
107
|
const raw = stdout + stderr
|
|
87
108
|
let received = 0
|
|
88
109
|
let rttMin = ''
|
|
89
110
|
let rttAvg = ''
|
|
90
111
|
let rttMax = ''
|
|
91
112
|
|
|
113
|
+
// English: "1 packets received" / Chinese: "已接收 = 1"
|
|
92
114
|
const recvMatch = raw.match(/(\d+)\s+packets?\s+received/)
|
|
115
|
+
|| raw.match(/(\d+)\s*已接收/)
|
|
93
116
|
if (recvMatch) received = parseInt(recvMatch[1], 10)
|
|
94
117
|
|
|
95
118
|
if (!isWin) {
|
|
@@ -100,7 +123,10 @@ function checkByPing(host, count) {
|
|
|
100
123
|
rttMax = rttMatch[3]
|
|
101
124
|
}
|
|
102
125
|
} else {
|
|
126
|
+
// English: "Minimum = 28ms, Maximum = 28ms, Average = 28ms"
|
|
127
|
+
// Chinese: "最小 = 28ms,最大 = 28ms,平均 = 28ms"
|
|
103
128
|
const msMatch = raw.match(/Minimum\s*=\s*(\d+)ms.*?Maximum\s*=\s*(\d+)ms.*?Average\s*=\s*(\d+)ms/s)
|
|
129
|
+
|| raw.match(/最小\s*=\s*(\d+)ms.*?最大\s*=\s*(\d+)ms.*?平均\s*=\s*(\d+)ms/s)
|
|
104
130
|
if (msMatch) {
|
|
105
131
|
rttMin = msMatch[1]
|
|
106
132
|
rttAvg = msMatch[3]
|
|
@@ -108,8 +134,12 @@ function checkByPing(host, count) {
|
|
|
108
134
|
}
|
|
109
135
|
}
|
|
110
136
|
|
|
137
|
+
// Use exit code as primary signal (language-independent);
|
|
138
|
+
// regex-based received count as secondary fallback
|
|
139
|
+
const reachable = code === 0 || received > 0
|
|
140
|
+
|
|
111
141
|
resolve({
|
|
112
|
-
reachable
|
|
142
|
+
reachable,
|
|
113
143
|
sent: count,
|
|
114
144
|
received,
|
|
115
145
|
loss: count - received,
|
|
@@ -137,10 +167,19 @@ function checkByPing(host, count) {
|
|
|
137
167
|
*/
|
|
138
168
|
function checkByCurl(url, proxy) {
|
|
139
169
|
return new Promise((resolve) => {
|
|
140
|
-
const args = ['-k', '-L', '-s', '-o',
|
|
170
|
+
const args = ['-k', '-L', '-s', '-o', NULL_DEV, '-w', '%{http_code} %{time_total}', '--connect-timeout', '2', '--max-time', '5']
|
|
141
171
|
if (proxy) args.push('-x', proxy)
|
|
142
172
|
args.push(url)
|
|
143
|
-
|
|
173
|
+
let child
|
|
174
|
+
try {
|
|
175
|
+
child = spawn('curl', args, { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
|
|
176
|
+
} catch {
|
|
177
|
+
resolve({
|
|
178
|
+
reachable: false, sent: 1, received: 0, loss: 1,
|
|
179
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'curl failed to execute',
|
|
180
|
+
})
|
|
181
|
+
return
|
|
182
|
+
}
|
|
144
183
|
let stdout = ''
|
|
145
184
|
let stderr = ''
|
|
146
185
|
child.stdout.on('data', (d) => { stdout += d })
|
|
@@ -150,8 +189,7 @@ function checkByCurl(url, proxy) {
|
|
|
150
189
|
const httpCode = parseInt(parts[0], 10) || 0
|
|
151
190
|
const timeTotal = parseFloat(parts[1]) || 0
|
|
152
191
|
const proxyAuthRequired = httpCode === 407
|
|
153
|
-
const
|
|
154
|
-
const reachable = (httpCode >= 200 && httpCode < 400) || allowlist.includes(httpCode)
|
|
192
|
+
const reachable = httpReachable(httpCode)
|
|
155
193
|
const rttMs = reachable ? String(Math.round(timeTotal * 1000)) : ''
|
|
156
194
|
resolve({
|
|
157
195
|
reachable,
|
|
@@ -183,18 +221,31 @@ function checkByCurl(url, proxy) {
|
|
|
183
221
|
*/
|
|
184
222
|
function checkByWget(url, proxy) {
|
|
185
223
|
return new Promise((resolve) => {
|
|
186
|
-
const args = ['-q', '--spider', '--timeout=5']
|
|
224
|
+
const args = ['-q', '--spider', '-S', '--timeout=5']
|
|
187
225
|
if (proxy) {
|
|
188
226
|
args.push('-e', `use_proxy=yes`, '-e', `https_proxy=${proxy}`, '-e', `http_proxy=${proxy}`)
|
|
189
227
|
}
|
|
190
228
|
args.push(url)
|
|
191
|
-
|
|
229
|
+
let child
|
|
230
|
+
try {
|
|
231
|
+
child = spawn('wget', args, { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
|
|
232
|
+
} catch {
|
|
233
|
+
resolve({
|
|
234
|
+
reachable: false, sent: 1, received: 0, loss: 1,
|
|
235
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'wget failed to execute',
|
|
236
|
+
})
|
|
237
|
+
return
|
|
238
|
+
}
|
|
192
239
|
let stdout = ''
|
|
193
240
|
let stderr = ''
|
|
194
241
|
child.stdout.on('data', (d) => { stdout += d })
|
|
195
242
|
child.stderr.on('data', (d) => { stderr += d })
|
|
196
243
|
child.on('close', (code) => {
|
|
197
|
-
|
|
244
|
+
// -S 输出响应头,从中提取 HTTP 状态码(wget 退出码无法区分 404/网络错误)
|
|
245
|
+
const httpMatch = stderr.match(/HTTP\/\S+\s+(\d{3})/)
|
|
246
|
+
|| stderr.match(/awaiting response\.\.\.\s*(\d{3})/)
|
|
247
|
+
const httpCode = httpMatch ? parseInt(httpMatch[1], 10) : 0
|
|
248
|
+
const reachable = httpCode ? httpReachable(httpCode) : code === 0
|
|
198
249
|
resolve({
|
|
199
250
|
reachable,
|
|
200
251
|
sent: 1,
|
|
@@ -204,7 +255,7 @@ function checkByWget(url, proxy) {
|
|
|
204
255
|
rttMin: '',
|
|
205
256
|
rttAvg: '',
|
|
206
257
|
rttMax: '',
|
|
207
|
-
raw: stderr.trim() || (reachable ? 'OK' : 'Failed'),
|
|
258
|
+
raw: httpCode ? `HTTP ${httpCode}` : (stderr.trim() || (reachable ? 'OK' : 'Failed')),
|
|
208
259
|
})
|
|
209
260
|
})
|
|
210
261
|
child.on('error', () => {
|
|
@@ -225,6 +276,7 @@ function checkByWget(url, proxy) {
|
|
|
225
276
|
* @returns {Promise<object>}
|
|
226
277
|
*/
|
|
227
278
|
async function checkReachability(address, tool, count, proxy) {
|
|
279
|
+
console.log(` [CHECK] ${tool}` + (proxy ? ` via proxy ${proxy}` : '') + ` → ${address}`)
|
|
228
280
|
if (tool === 'ping') {
|
|
229
281
|
let host = address
|
|
230
282
|
if (isUrl(address)) {
|
|
@@ -250,7 +302,7 @@ async function checkReachability(address, tool, count, proxy) {
|
|
|
250
302
|
return { reachable: false, sent: 0, received: 0, loss: 0, lossPercent: 0, rttMin: '', rttAvg: '', rttMax: '', raw: 'No tool available' }
|
|
251
303
|
}
|
|
252
304
|
|
|
253
|
-
export { checkReachability, extractHostPort }
|
|
305
|
+
export { checkReachability, extractHostPort, httpReachable }
|
|
254
306
|
|
|
255
307
|
export class PingGroup {
|
|
256
308
|
constructor() {
|
|
@@ -277,12 +329,9 @@ export class PingGroup {
|
|
|
277
329
|
const n = (isNaN(count) || count < 1) ? 4 : count
|
|
278
330
|
const detail = !!(parsed.flags.d || parsed.flags.detail)
|
|
279
331
|
|
|
280
|
-
let proxy = ''
|
|
281
|
-
if ('
|
|
282
|
-
|
|
283
|
-
if (raw === 'true') raw = ''
|
|
284
|
-
proxy = raw || getActiveProxy()
|
|
285
|
-
}
|
|
332
|
+
let proxy = parsed.flags.proxy || parsed.flags.p || ''
|
|
333
|
+
if (proxy === 'true') proxy = ''
|
|
334
|
+
if (!proxy) proxy = getActiveProxy()
|
|
286
335
|
|
|
287
336
|
if (target === 'proxy') {
|
|
288
337
|
return this._pingProxy(n, detail, tool)
|
|
@@ -297,7 +346,8 @@ export class PingGroup {
|
|
|
297
346
|
}
|
|
298
347
|
|
|
299
348
|
if (proxy) {
|
|
300
|
-
const
|
|
349
|
+
const testUrl = isUrl(target) ? target : undefined
|
|
350
|
+
const usable = await this._checkProxyUsable(proxy, tool, n, testUrl)
|
|
301
351
|
if (!usable.ok) {
|
|
302
352
|
console.error(`[opm] Proxy not usable: ${usable.reason}`)
|
|
303
353
|
process.exit(1)
|
|
@@ -310,12 +360,16 @@ export class PingGroup {
|
|
|
310
360
|
}
|
|
311
361
|
|
|
312
362
|
/**
|
|
313
|
-
*
|
|
363
|
+
* 检查代理是否可用:通过代理访问指定 URL 测试
|
|
364
|
+
* @param {string} proxyUrl - 代理地址
|
|
365
|
+
* @param {string} tool - 检测工具
|
|
366
|
+
* @param {number} count - ping 包数量
|
|
367
|
+
* @param {string} [testUrl] - 测试 URL(默认 npm active registry)
|
|
314
368
|
* @returns {{ ok: boolean, reason: string }}
|
|
315
369
|
*/
|
|
316
|
-
async _checkProxyUsable(proxyUrl, tool, count) {
|
|
317
|
-
const
|
|
318
|
-
const result = await checkReachability(
|
|
370
|
+
async _checkProxyUsable(proxyUrl, tool, count, testUrl) {
|
|
371
|
+
const url = testUrl || getActiveRegistry('npm')
|
|
372
|
+
const result = await checkReachability(url, tool, count, proxyUrl)
|
|
319
373
|
if (result.proxyAuthRequired) {
|
|
320
374
|
return { ok: false, reason: 'authentication required (HTTP 407)' }
|
|
321
375
|
}
|
|
@@ -355,7 +409,13 @@ export class PingGroup {
|
|
|
355
409
|
const tools = getRegistryTools()
|
|
356
410
|
|
|
357
411
|
if (proxy) {
|
|
358
|
-
|
|
412
|
+
// 用首个可用 registry URL 作为代理可用性测试目标(而非硬编码 npm)
|
|
413
|
+
let testUrl = ''
|
|
414
|
+
for (const t of tools) {
|
|
415
|
+
const u = getActiveRegistry(t)
|
|
416
|
+
if (u) { testUrl = u; break }
|
|
417
|
+
}
|
|
418
|
+
const usable = await this._checkProxyUsable(proxy, tool, 1, testUrl)
|
|
359
419
|
if (!usable.ok) {
|
|
360
420
|
console.error(`[opm] Proxy not usable: ${usable.reason}`)
|
|
361
421
|
process.exit(1)
|
|
@@ -438,11 +498,12 @@ Actions:
|
|
|
438
498
|
|
|
439
499
|
Options:
|
|
440
500
|
-c, --count <n> Number of packets to send (ping only; default: 4)
|
|
441
|
-
-p, --proxy <url>
|
|
501
|
+
-p, --proxy <url> Proxy address for the check (overrides config proxy.active;
|
|
502
|
+
routes curl/wget requests through the proxy and pre-validates usability)
|
|
442
503
|
-d, --detail Output full JSON details (default: compact [{tool, address, proxy, reachable}])
|
|
443
504
|
-h, --help Show this help
|
|
444
505
|
|
|
445
|
-
Tool detection:
|
|
506
|
+
Tool detection: curl > wget > ping (auto-detected)
|
|
446
507
|
|
|
447
508
|
Output:
|
|
448
509
|
default (no -d) [{ "tool": "npm", "address": "https://...", "proxy": "", "reachable": true }]
|
package/src/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { EnsureGroup } from './groups/ensure.js'
|
|
|
21
21
|
import { PingGroup } from './groups/ping.js'
|
|
22
22
|
import { ProcGroup } from './groups/proc.js'
|
|
23
23
|
import { OverlayGroup } from './groups/overlay.js'
|
|
24
|
+
import { EnvGroup } from './groups/env.js'
|
|
24
25
|
import { FtpGroup } from '../tools/ftp/index.js'
|
|
25
26
|
import { GitGroup } from '../tools/git/index.js'
|
|
26
27
|
import { ShareGroup } from '../tools/share/index.js'
|
|
@@ -66,6 +67,7 @@ const GROUPS = {
|
|
|
66
67
|
ping: new PingGroup(),
|
|
67
68
|
proc: new ProcGroup(),
|
|
68
69
|
overlay: new OverlayGroup(),
|
|
70
|
+
env: new EnvGroup(),
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
class CLI {
|
|
@@ -39,21 +39,22 @@ function findConfigFile() {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* 获取 env 变量当前值
|
|
42
|
-
*
|
|
42
|
+
* 优先级:opm config opencode.env > 系统环境变量
|
|
43
|
+
* 用户在配置文件中设置的值优先,无需手动管理环境变量。
|
|
43
44
|
* @returns {Record<string, string>}
|
|
44
45
|
*/
|
|
45
46
|
function getEnvVars() {
|
|
46
47
|
const cfgEnv = (getConfig().opencode?.env) || {}
|
|
47
48
|
const result = {}
|
|
48
49
|
for (const key of ENV_VARS) {
|
|
49
|
-
result[key] =
|
|
50
|
+
result[key] = cfgEnv[key] || process.env[key] || ''
|
|
50
51
|
}
|
|
51
52
|
return result
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* 在指定工作目录启动 opencode TUI
|
|
56
|
-
*
|
|
57
|
+
* 自动注入有值的环境变量(opm config 优先,系统 env 回退)
|
|
57
58
|
* @param {string} workDir - 工作目录
|
|
58
59
|
* @param {string[]} [passThroughArgs=[]] - Extra args passed to opencode
|
|
59
60
|
*/
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export { DockerGroup } from './cli/groups/docker.js'
|
|
|
23
23
|
export { PingGroup } from './cli/groups/ping.js'
|
|
24
24
|
export { ProcGroup } from './cli/groups/proc.js'
|
|
25
25
|
export { OverlayGroup } from './cli/groups/overlay.js'
|
|
26
|
+
export { EnvGroup, parseEnvBlocks, parseRegOutput, isValidEnvKey } from './cli/groups/env.js'
|
|
26
27
|
export { FtpGroup } from './tools/ftp/index.js'
|
|
27
28
|
export { GitGroup } from './tools/git/index.js'
|
|
28
29
|
export { ShareGroup } from './tools/share/index.js'
|
|
@@ -62,7 +63,7 @@ export {
|
|
|
62
63
|
} from './presets.js'
|
|
63
64
|
export { PROXY_PRESETS } from './proxy-presets.js'
|
|
64
65
|
export { output } from './formatter.js'
|
|
65
|
-
export { downloadToFile, fetchText } from '
|
|
66
|
+
export { downloadToFile, fetchText } from '@wwkit/shared'
|
|
66
67
|
export {
|
|
67
68
|
getConfig, getSection, copyBuiltinConfig, getActiveRegistry, setActiveRegistry,
|
|
68
69
|
getUserConfigDir, getUserConfigFile, getActiveProxy,
|
package/src/managers/brew.js
CHANGED
package/src/managers/bun.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* bun 包管理器实现
|
|
3
3
|
*
|
|
4
4
|
* Bun 是全栈 JS 工具链(runtime + 包管理器 + 打包器)。
|
|
5
|
-
* 包管理命令通过 bun CLI
|
|
5
|
+
* 包管理命令通过 bun CLI 实现,源查询通过共享 fetchText 查 npm registry。
|
|
6
6
|
* 镜像通过 BUN_CONFIG_REGISTRY 环境变量传递给 bun 命令。
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -11,6 +11,7 @@ import fs from 'node:fs'
|
|
|
11
11
|
import os from 'node:os'
|
|
12
12
|
import path from 'node:path'
|
|
13
13
|
import { PackageManager } from './base.js'
|
|
14
|
+
import { fetchText } from '@wwkit/shared'
|
|
14
15
|
import { getActiveRegistry, setActiveRegistry } from '../config.js'
|
|
15
16
|
|
|
16
17
|
export class BunManager extends PackageManager {
|
|
@@ -108,52 +109,24 @@ export class BunManager extends PackageManager {
|
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
111
|
* 通过 HTTP 查询 npm registry 获取包元数据
|
|
112
|
+
* 使用共享 fetchText(支持 HTTP/SOCKS5 代理隧道,与 pip/composer 一致)
|
|
111
113
|
* @param {string} pkgName
|
|
112
114
|
* @param {{ proxy?: string }} [opts]
|
|
113
115
|
* @returns {Promise<object|null>}
|
|
114
116
|
* @private
|
|
115
117
|
*/
|
|
116
118
|
async _fetchRegistryMetadata(pkgName, opts = {}) {
|
|
117
|
-
const registry = getActiveRegistry('bun').replace(/\/$/, '')
|
|
119
|
+
const registry = (getActiveRegistry('bun') || 'https://registry.npmjs.org').replace(/\/$/, '')
|
|
118
120
|
const url = `${registry}/${encodeURIComponent(pkgName).replace('%40', '@')}`
|
|
119
121
|
try {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const proxyAgent = opts.proxy ? await this._getProxyAgent(opts.proxy) : null
|
|
124
|
-
return new Promise((resolve) => {
|
|
125
|
-
const req = client.get(url, { agent: proxyAgent }, (res) => {
|
|
126
|
-
let data = ''
|
|
127
|
-
res.on('data', (chunk) => (data += chunk))
|
|
128
|
-
res.on('end', () => {
|
|
129
|
-
try {
|
|
130
|
-
resolve(JSON.parse(data))
|
|
131
|
-
} catch {
|
|
132
|
-
resolve(null)
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
req.on('error', () => resolve(null))
|
|
137
|
-
req.setTimeout(15000, () => {
|
|
138
|
-
req.destroy()
|
|
139
|
-
resolve(null)
|
|
140
|
-
})
|
|
141
|
-
})
|
|
122
|
+
const text = await fetchText(url, { proxy: opts.proxy })
|
|
123
|
+
if (!text) return null
|
|
124
|
+
return JSON.parse(text)
|
|
142
125
|
} catch {
|
|
143
126
|
return null
|
|
144
127
|
}
|
|
145
128
|
}
|
|
146
129
|
|
|
147
|
-
/**
|
|
148
|
-
* 获取 proxy agent(简化实现,仅支持 http proxy)
|
|
149
|
-
* @param {string} proxy
|
|
150
|
-
* @returns {Promise<undefined>}
|
|
151
|
-
* @private
|
|
152
|
-
*/
|
|
153
|
-
async _getProxyAgent(proxy) {
|
|
154
|
-
return undefined
|
|
155
|
-
}
|
|
156
|
-
|
|
157
130
|
async getVersion() {
|
|
158
131
|
try {
|
|
159
132
|
const version = this._exec(['--version'], { allowNonZero: true }).trim()
|
package/src/managers/composer.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from 'node:fs'
|
|
|
14
14
|
import os from 'node:os'
|
|
15
15
|
import path from 'node:path'
|
|
16
16
|
import { PackageManager } from './base.js'
|
|
17
|
-
import { fetchText } from '
|
|
17
|
+
import { fetchText } from '@wwkit/shared'
|
|
18
18
|
import { getActiveRegistry, setActiveRegistry } from '../config.js'
|
|
19
19
|
|
|
20
20
|
export class ComposerManager extends PackageManager {
|
package/src/managers/pip.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync, spawnSync } from 'node:child_process'
|
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
import { URL } from 'node:url'
|
|
11
11
|
import { PackageManager } from './base.js'
|
|
12
|
-
import { fetchText } from '
|
|
12
|
+
import { fetchText } from '@wwkit/shared'
|
|
13
13
|
import { getActiveRegistry, setActiveRegistry } from '../config.js'
|
|
14
14
|
|
|
15
15
|
export class PipManager extends PackageManager {
|
package/src/managers/proc.js
CHANGED
|
@@ -52,7 +52,13 @@ export async function getPortMap() {
|
|
|
52
52
|
const args = shell.isWindows ? ['-ano'] : ['-tlnp']
|
|
53
53
|
|
|
54
54
|
return new Promise((resolve) => {
|
|
55
|
-
|
|
55
|
+
let child
|
|
56
|
+
try {
|
|
57
|
+
child = spawn(fallback, args, { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
|
|
58
|
+
} catch {
|
|
59
|
+
resolve(map)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
56
62
|
let stdout = ''
|
|
57
63
|
child.stdout.on('data', (d) => { stdout += d })
|
|
58
64
|
child.on('close', () => {
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* 由 blues-lib 提供的 ww init / ww init -u 全权处理,不再单独安装/检查。
|
|
6
6
|
*
|
|
7
7
|
* install 流程(5 步):
|
|
8
|
-
* 镜像检查(必查 npm/pip
|
|
8
|
+
* 镜像检查(必查 npm/pip + 按需 nvm/node/uv) → ensure node/python → 写 NPM/PIP_REGISTRY/REGISTRY_PROXY → 装 blues-lib → ww init
|
|
9
9
|
* upgrade 流程(4 步):
|
|
10
|
-
* 镜像检查(必查 npm/pip
|
|
10
|
+
* 镜像检查(必查 npm/pip) → 写 NPM/PIP_REGISTRY/REGISTRY_PROXY → pip 升级 blues-lib → ww init -u
|
|
11
11
|
*
|
|
12
12
|
* 命令: version / versions / installed / install / uninstall / upgrade / help
|
|
13
13
|
*/
|
|
@@ -184,8 +184,8 @@ export class WebworkGroup {
|
|
|
184
184
|
console.log(` → python ${env.python.provided} 已满足`)
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
console.log('\n========== 步骤 3/5: 写入 NPM_REGISTRY / PIP_REGISTRY /
|
|
188
|
-
this._writeRegistryEnv()
|
|
187
|
+
console.log('\n========== 步骤 3/5: 写入 NPM_REGISTRY / PIP_REGISTRY / REGISTRY_PROXY ==========')
|
|
188
|
+
this._writeRegistryEnv(proxy)
|
|
189
189
|
|
|
190
190
|
console.log('\n========== 步骤 4/5: 安装 blues-lib ==========')
|
|
191
191
|
const blue = await this._installPipPackage('blues-lib', proxy)
|
|
@@ -196,7 +196,7 @@ export class WebworkGroup {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
console.log('\n========== 步骤 5/5: ww init ==========')
|
|
199
|
-
|
|
199
|
+
this._runWwInit('init')
|
|
200
200
|
|
|
201
201
|
output({ action: 'install', env, ensured, bluesLib: blue, init: 'ww init' })
|
|
202
202
|
}
|
|
@@ -207,32 +207,44 @@ export class WebworkGroup {
|
|
|
207
207
|
console.log('\n========== 步骤 1/4: 镜像可达检查 ==========')
|
|
208
208
|
await this._checkMirrors(proxy, null)
|
|
209
209
|
|
|
210
|
-
console.log('\n========== 步骤 2/4: 写入 NPM_REGISTRY / PIP_REGISTRY /
|
|
211
|
-
this._writeRegistryEnv()
|
|
210
|
+
console.log('\n========== 步骤 2/4: 写入 NPM_REGISTRY / PIP_REGISTRY / REGISTRY_PROXY ==========')
|
|
211
|
+
this._writeRegistryEnv(proxy)
|
|
212
212
|
|
|
213
213
|
console.log('\n========== 步骤 3/4: 升级 blues-lib ==========')
|
|
214
214
|
const blue = await this._upgradePipPackage('blues-lib', proxy)
|
|
215
215
|
console.log(` ✓ blues-lib 升级完成`)
|
|
216
216
|
|
|
217
217
|
console.log('\n========== 步骤 4/4: ww init -u ==========')
|
|
218
|
-
|
|
218
|
+
this._runWwInit('init -u')
|
|
219
219
|
|
|
220
220
|
output({ action: 'upgrade', bluesLib: blue, init: 'ww init -u' })
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
224
|
+
* 执行 ww init/init -u
|
|
225
|
+
* 镜像与代理配置通过 NPM_REGISTRY/PIP_REGISTRY/REGISTRY_PROXY 环境变量传递(见 _writeRegistryEnv)
|
|
226
|
+
* @param {string} action - 'init' 或 'init -u'
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
_runWwInit(action) {
|
|
230
|
+
execSync(`ww ${action}`, { encoding: 'utf8', stdio: 'inherit' })
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 将 NPM_REGISTRY / PIP_REGISTRY / REGISTRY_PROXY 写入 shell profile,并同步到当前会话 env
|
|
235
|
+
* blues-lib 的相关命令依赖镜像,写入后保持与 opm 配置一致;代理同理,ww init 用同一机制读取
|
|
236
|
+
* @param {string} [proxy] - 代理地址(来自 -p/--proxy 或 opm 配置 proxy.active),空则跳过
|
|
226
237
|
*/
|
|
227
|
-
_writeRegistryEnv() {
|
|
238
|
+
_writeRegistryEnv(proxy = '') {
|
|
228
239
|
const entries = [
|
|
229
240
|
['NPM_REGISTRY', getActiveRegistry('npm')],
|
|
230
241
|
['PIP_REGISTRY', getActiveRegistry('pip')],
|
|
231
|
-
['
|
|
242
|
+
['REGISTRY_PROXY', proxy],
|
|
232
243
|
]
|
|
233
244
|
for (const [name, url] of entries) {
|
|
234
245
|
if (!url) continue
|
|
235
246
|
shell.setEnvVar(name, url)
|
|
247
|
+
process.env[name] = url
|
|
236
248
|
console.log(` [OK] ${name}=${url}`)
|
|
237
249
|
}
|
|
238
250
|
}
|
|
@@ -268,8 +280,9 @@ export class WebworkGroup {
|
|
|
268
280
|
|
|
269
281
|
/**
|
|
270
282
|
* 检查镜像可达性(HEAD 请求,不可达则抛错)
|
|
271
|
-
* 必查:npm / pip
|
|
283
|
+
* 必查:npm / pip(blues-lib 依赖,环境必然可用)
|
|
272
284
|
* 条件查:nvm + node(仅 env.node 不满足时)、uv(仅 env.python 不满足时)
|
|
285
|
+
* cft 不查:由 ww init 全权处理插件安装,不再单独检查。
|
|
273
286
|
* @param {string} proxy
|
|
274
287
|
* @param {{ node: { satisfied: boolean }, python: { satisfied: boolean } } | null} env
|
|
275
288
|
* null 表示不做条件检查(upgrade 场景,不装 runtime)
|
|
@@ -277,7 +290,7 @@ export class WebworkGroup {
|
|
|
277
290
|
async _checkMirrors(proxy, env) {
|
|
278
291
|
const tool = shell.getNetProbeTool()
|
|
279
292
|
if (!tool) {
|
|
280
|
-
console.log(' [WARN] No network probe tool (
|
|
293
|
+
console.log(' [WARN] No network probe tool (curl/wget/ping), skipping mirror check')
|
|
281
294
|
return
|
|
282
295
|
}
|
|
283
296
|
|
|
@@ -287,7 +300,6 @@ export class WebworkGroup {
|
|
|
287
300
|
const checks = [
|
|
288
301
|
{ name: 'npm', url: getActiveRegistry('npm') },
|
|
289
302
|
{ name: 'pip', url: getActiveRegistry('pip') },
|
|
290
|
-
{ name: 'cft', url: getActiveRegistry('cft') },
|
|
291
303
|
...(needNode ? [
|
|
292
304
|
{ name: 'nvm', url: getActiveRegistry('nvm') },
|
|
293
305
|
{ name: 'node', url: getActiveRegistry('node') },
|
|
@@ -318,7 +330,10 @@ export class WebworkGroup {
|
|
|
318
330
|
}
|
|
319
331
|
|
|
320
332
|
if (!allOk) {
|
|
321
|
-
|
|
333
|
+
const hint = proxy
|
|
334
|
+
? `Proxy (${proxy}) was used for ${tool}-based checks. Ensure the proxy is working and mirrors are accessible through it.`
|
|
335
|
+
: `No proxy configured. If behind a proxy, set config proxy.active or use -p <url>. Current tool: ${tool}.`
|
|
336
|
+
throw new Error(`Some mirrors unreachable, aborting. ${hint}`)
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
|