@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.
@@ -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
- const child = spawn('ping', args, { stdio: ['pipe', 'pipe', 'pipe'] })
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: received > 0,
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', '/dev/null', '-w', '%{http_code} %{time_total}', '--connect-timeout', '2', '--max-time', '5']
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
- const child = spawn('curl', args, { stdio: ['pipe', 'pipe', 'pipe'] })
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 allowlist = [404]
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
- const child = spawn('wget', args, { stdio: ['pipe', 'pipe', 'pipe'] })
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
- const reachable = code === 0
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 ('p' in parsed.flags || 'proxy' in parsed.flags) {
282
- let raw = parsed.flags.proxy || parsed.flags.p || ''
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 usable = await this._checkProxyUsable(proxy, tool, n)
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
- * 检查代理是否可用:通过代理访问 npm active registry 测试
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 testUrl = getActiveRegistry('npm')
318
- const result = await checkReachability(testUrl, tool, count, proxyUrl)
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
- const usable = await this._checkProxyUsable(proxy, tool, 1)
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> Record proxy used for the check (informational)
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: ping > curl > wget (auto-detected)
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
- * 优先级:系统环境变量 > opm config opencode.env
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] = process.env[key] || cfgEnv[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
- * 自动注入有值的环境变量(系统 env > opm config)
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 './managers/http.js'
66
+ export { downloadToFile, fetchText } from '@wwkit/shared'
66
67
  export {
67
68
  getConfig, getSection, copyBuiltinConfig, getActiveRegistry, setActiveRegistry,
68
69
  getUserConfigDir, getUserConfigFile, getActiveProxy,
@@ -166,6 +166,7 @@ export class BrewManager extends PackageManager {
166
166
  _execInherit(args, opts = {}) {
167
167
  const result = spawnSync(this._brewBin(), args, {
168
168
  stdio: 'inherit',
169
+ shell: true,
169
170
  env: this._buildEnv(opts.proxy),
170
171
  })
171
172
  if (result.error) {
@@ -2,7 +2,7 @@
2
2
  * bun 包管理器实现
3
3
  *
4
4
  * Bun 是全栈 JS 工具链(runtime + 包管理器 + 打包器)。
5
- * 包管理命令通过 bun CLI 实现,源查询通过 HTTP 直接查 npm registry。
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 { default: http } = await import('node:http')
121
- const { default: https } = await import('node:https')
122
- const client = url.startsWith('https') ? https : http
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()
@@ -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 './http.js'
17
+ import { fetchText } from '@wwkit/shared'
18
18
  import { getActiveRegistry, setActiveRegistry } from '../config.js'
19
19
 
20
20
  export class ComposerManager extends PackageManager {
@@ -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 './http.js'
12
+ import { fetchText } from '@wwkit/shared'
13
13
  import { getActiveRegistry, setActiveRegistry } from '../config.js'
14
14
 
15
15
  export class PipManager extends PackageManager {
@@ -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
- const child = spawn(fallback, args, { stdio: ['pipe', 'pipe', 'pipe'] })
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/cft + 按需 nvm/node/uv) → ensure node/python → 写 NPM/PIP/CFT_REGISTRY → 装 blues-lib → ww init
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/cft) → 写 NPM/PIP/CFT_REGISTRY → pip 升级 blues-lib → ww init -u
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 / CFT_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
- execSync('ww init', { encoding: 'utf8', stdio: 'inherit' })
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 / CFT_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
- execSync('ww init -u', { encoding: 'utf8', stdio: 'inherit' })
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
- * 将 NPM_REGISTRY / PIP_REGISTRY / CFT_REGISTRY 写入 shell profile(与 SELENIUM_CFT_DIR 同机制)
225
- * blues-lib 的相关命令依赖镜像,写入后保持与 opm 配置一致
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
- ['CFT_REGISTRY', getActiveRegistry('cft')],
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 / cft(blues-lib 依赖,环境必然可用)
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 (ping/curl/wget), skipping mirror check')
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
- throw new Error('Some mirrors unreachable, aborting. Check network or set proxy with -p')
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