@wwkit/opm 1.0.12 → 1.0.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwkit/opm",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "author": "bluesliu <langcai163@163.com>",
5
5
  "description": "Unified CLI — package management (npm/pip/dnf/apt) + config view + opencode maintenance",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "basic-ftp": "^6.2.1",
36
36
  "extract-zip": "^2.0.1",
37
37
  "systeminformation": "^5.23.0",
38
- "@wwkit/shared": "1.0.16"
38
+ "@wwkit/shared": "1.0.18"
39
39
  },
40
40
  "publishConfig": {
41
41
  "registry": "https://registry.npmjs.org/",
@@ -46,6 +46,14 @@ function isUrl(addr) {
46
46
  return /^https?:\/\//i.test(addr)
47
47
  }
48
48
 
49
+ // HTTP 状态码 → 可达性判断:2xx/3xx 视为可达,allowlist 中的状态码
50
+ // 虽非成功响应但证明服务器已响应(如 404 表示主机/代理存活)
51
+ const REACHABLE_ALLOWLIST = [404]
52
+
53
+ function httpReachable(httpCode) {
54
+ return (httpCode >= 200 && httpCode < 400) || REACHABLE_ALLOWLIST.includes(httpCode)
55
+ }
56
+
49
57
  /**
50
58
  * 获取所有含 registry 配置的工具名列表
51
59
  * @returns {string[]}
@@ -82,14 +90,16 @@ function checkByPing(host, count) {
82
90
  let stderr = ''
83
91
  child.stdout.on('data', (d) => { stdout += d })
84
92
  child.stderr.on('data', (d) => { stderr += d })
85
- child.on('close', () => {
93
+ child.on('close', (code) => {
86
94
  const raw = stdout + stderr
87
95
  let received = 0
88
96
  let rttMin = ''
89
97
  let rttAvg = ''
90
98
  let rttMax = ''
91
99
 
100
+ // English: "1 packets received" / Chinese: "已接收 = 1"
92
101
  const recvMatch = raw.match(/(\d+)\s+packets?\s+received/)
102
+ || raw.match(/(\d+)\s*已接收/)
93
103
  if (recvMatch) received = parseInt(recvMatch[1], 10)
94
104
 
95
105
  if (!isWin) {
@@ -100,7 +110,10 @@ function checkByPing(host, count) {
100
110
  rttMax = rttMatch[3]
101
111
  }
102
112
  } else {
113
+ // English: "Minimum = 28ms, Maximum = 28ms, Average = 28ms"
114
+ // Chinese: "最小 = 28ms,最大 = 28ms,平均 = 28ms"
103
115
  const msMatch = raw.match(/Minimum\s*=\s*(\d+)ms.*?Maximum\s*=\s*(\d+)ms.*?Average\s*=\s*(\d+)ms/s)
116
+ || raw.match(/最小\s*=\s*(\d+)ms.*?最大\s*=\s*(\d+)ms.*?平均\s*=\s*(\d+)ms/s)
104
117
  if (msMatch) {
105
118
  rttMin = msMatch[1]
106
119
  rttAvg = msMatch[3]
@@ -108,8 +121,12 @@ function checkByPing(host, count) {
108
121
  }
109
122
  }
110
123
 
124
+ // Use exit code as primary signal (language-independent);
125
+ // regex-based received count as secondary fallback
126
+ const reachable = code === 0 || received > 0
127
+
111
128
  resolve({
112
- reachable: received > 0,
129
+ reachable,
113
130
  sent: count,
114
131
  received,
115
132
  loss: count - received,
@@ -150,8 +167,7 @@ function checkByCurl(url, proxy) {
150
167
  const httpCode = parseInt(parts[0], 10) || 0
151
168
  const timeTotal = parseFloat(parts[1]) || 0
152
169
  const proxyAuthRequired = httpCode === 407
153
- const allowlist = [404]
154
- const reachable = (httpCode >= 200 && httpCode < 400) || allowlist.includes(httpCode)
170
+ const reachable = httpReachable(httpCode)
155
171
  const rttMs = reachable ? String(Math.round(timeTotal * 1000)) : ''
156
172
  resolve({
157
173
  reachable,
@@ -183,7 +199,7 @@ function checkByCurl(url, proxy) {
183
199
  */
184
200
  function checkByWget(url, proxy) {
185
201
  return new Promise((resolve) => {
186
- const args = ['-q', '--spider', '--timeout=5']
202
+ const args = ['-q', '--spider', '-S', '--timeout=5']
187
203
  if (proxy) {
188
204
  args.push('-e', `use_proxy=yes`, '-e', `https_proxy=${proxy}`, '-e', `http_proxy=${proxy}`)
189
205
  }
@@ -194,7 +210,11 @@ function checkByWget(url, proxy) {
194
210
  child.stdout.on('data', (d) => { stdout += d })
195
211
  child.stderr.on('data', (d) => { stderr += d })
196
212
  child.on('close', (code) => {
197
- const reachable = code === 0
213
+ // -S 输出响应头,从中提取 HTTP 状态码(wget 退出码无法区分 404/网络错误)
214
+ const httpMatch = stderr.match(/HTTP\/\S+\s+(\d{3})/)
215
+ || stderr.match(/awaiting response\.\.\.\s*(\d{3})/)
216
+ const httpCode = httpMatch ? parseInt(httpMatch[1], 10) : 0
217
+ const reachable = httpCode ? httpReachable(httpCode) : code === 0
198
218
  resolve({
199
219
  reachable,
200
220
  sent: 1,
@@ -204,7 +224,7 @@ function checkByWget(url, proxy) {
204
224
  rttMin: '',
205
225
  rttAvg: '',
206
226
  rttMax: '',
207
- raw: stderr.trim() || (reachable ? 'OK' : 'Failed'),
227
+ raw: httpCode ? `HTTP ${httpCode}` : (stderr.trim() || (reachable ? 'OK' : 'Failed')),
208
228
  })
209
229
  })
210
230
  child.on('error', () => {
@@ -225,6 +245,7 @@ function checkByWget(url, proxy) {
225
245
  * @returns {Promise<object>}
226
246
  */
227
247
  async function checkReachability(address, tool, count, proxy) {
248
+ console.log(` [CHECK] ${tool}` + (proxy ? ` via proxy ${proxy}` : '') + ` → ${address}`)
228
249
  if (tool === 'ping') {
229
250
  let host = address
230
251
  if (isUrl(address)) {
@@ -442,7 +463,7 @@ Options:
442
463
  -d, --detail Output full JSON details (default: compact [{tool, address, proxy, reachable}])
443
464
  -h, --help Show this help
444
465
 
445
- Tool detection: ping > curl > wget (auto-detected)
466
+ Tool detection: curl > wget > ping (auto-detected)
446
467
 
447
468
  Output:
448
469
  default (no -d) [{ "tool": "npm", "address": "https://...", "proxy": "", "reachable": true }]
@@ -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
  */
@@ -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