@wwkit/opm 1.0.2 → 1.0.4
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 +194 -11
- package/bin/index.js +23 -0
- package/docs/managers/git.md +103 -0
- package/docs/managers/proc.md +77 -0
- package/docs/managers/uv.md +9 -3
- package/docs/opm.html +154 -11
- package/docs/packages/opencode.md +46 -15
- package/docs/test/linux-environment-test-report.md +147 -0
- package/package.json +3 -2
- package/src/cli/groups/docker.js +10 -5
- package/src/cli/groups/homebrew.js +9 -5
- package/src/cli/groups/nvm.js +6 -2
- package/src/cli/groups/ping.js +461 -0
- package/src/cli/groups/proc.js +232 -0
- package/src/cli/groups/uv.js +9 -8
- package/src/cli/groups/version-manager.js +34 -8
- package/src/cli/index.js +10 -0
- package/src/config.json5 +84 -7
- package/src/harnesses/base.js +34 -0
- package/src/harnesses/opencode/index.js +56 -0
- package/src/index.js +6 -0
- package/src/managers/brew.js +6 -6
- package/src/managers/docker.js +10 -7
- package/src/managers/node.js +8 -2
- package/src/managers/proc.js +424 -0
- package/src/managers/python.js +7 -4
- package/src/presets.js +3 -0
- package/src/tools/ftp/index.js +151 -16
- package/src/tools/git/config.js +374 -0
- package/src/tools/git/index.js +378 -0
- package/src/tools/share/.htaccess +20 -0
- package/src/tools/share/client.js +172 -0
- package/src/tools/share/index.js +735 -0
- package/src/tools/share/server.js +430 -0
- package/src/tools/share/server.php +452 -0
- package/src/tools/webwork/index.js +558 -0
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* homebrew 命令组 — 管理 Homebrew(brew)自身
|
|
3
3
|
*
|
|
4
4
|
* Homebrew 无独立版本管理器,通过官方 shell 脚本安装/卸载。
|
|
5
|
-
* 安装方式:curl 管道执行 install.sh
|
|
5
|
+
* 安装方式:curl 管道执行 install.sh(URL 由 homebrew.registry.active 提供);
|
|
6
6
|
* upgrade = brew update;uninstall = curl 管道执行 uninstall.sh。
|
|
7
|
-
* brew 自身无镜像源(包镜像走 opm brew registry),故无 registry action。
|
|
8
7
|
* 仅支持 macOS / Linux,Windows 不支持。
|
|
9
8
|
*/
|
|
10
9
|
|
|
@@ -30,7 +29,6 @@ export class HomebrewGroup extends ToolManagerGroup {
|
|
|
30
29
|
super({
|
|
31
30
|
name: 'homebrew',
|
|
32
31
|
desc: 'Homebrew manager (install/upgrade/uninstall brew itself)',
|
|
33
|
-
hasRegistry: false,
|
|
34
32
|
isInstalled: () => isHomebrewInstalled(),
|
|
35
33
|
getPath: () => getBrewRoot(),
|
|
36
34
|
versionCmd: () => `${getBrewBin()} --version`,
|
|
@@ -38,7 +36,7 @@ export class HomebrewGroup extends ToolManagerGroup {
|
|
|
38
36
|
const m = out.trim().match(/^Homebrew\s+(\S+)/)
|
|
39
37
|
return m ? m[1] : out.trim()
|
|
40
38
|
},
|
|
41
|
-
install: () => installHomebrew(),
|
|
39
|
+
install: (proxy) => installHomebrew(proxy),
|
|
42
40
|
upgrade: () => {
|
|
43
41
|
execSync(`${getBrewBin()} update`, { stdio: 'inherit' })
|
|
44
42
|
return { action: 'upgraded' }
|
|
@@ -54,7 +52,10 @@ export class HomebrewGroup extends ToolManagerGroup {
|
|
|
54
52
|
actions: [
|
|
55
53
|
['version', 'Show Homebrew version'],
|
|
56
54
|
['installed', 'Check if Homebrew is installed'],
|
|
57
|
-
['
|
|
55
|
+
['registry', 'Show active install script URL (from opm config)'],
|
|
56
|
+
['registry set <url|preset>', 'Set active install script URL (writes opm config)'],
|
|
57
|
+
['registry presets', 'List available mirror presets'],
|
|
58
|
+
['install', 'Install Homebrew (curl installer from active registry)'],
|
|
58
59
|
['upgrade', 'Update Homebrew (brew update)'],
|
|
59
60
|
['uninstall', 'Remove Homebrew (curl uninstaller from GitHub)'],
|
|
60
61
|
['dir', 'Show Homebrew install directory'],
|
|
@@ -63,6 +64,9 @@ export class HomebrewGroup extends ToolManagerGroup {
|
|
|
63
64
|
examples: [
|
|
64
65
|
'opm homebrew version',
|
|
65
66
|
'opm homebrew installed',
|
|
67
|
+
'opm homebrew registry',
|
|
68
|
+
'opm homebrew registry set official',
|
|
69
|
+
'opm homebrew registry presets',
|
|
66
70
|
'opm homebrew install',
|
|
67
71
|
'opm homebrew upgrade',
|
|
68
72
|
'opm homebrew dir',
|
package/src/cli/groups/nvm.js
CHANGED
|
@@ -35,9 +35,13 @@ export class NvmGroup extends ToolManagerGroup {
|
|
|
35
35
|
getPath: () => getNvmDir(),
|
|
36
36
|
versionCmd: () => `source "${path.join(getNvmDir(), 'nvm.sh')}" && nvm --version`,
|
|
37
37
|
parseVersion: (out) => out.trim(),
|
|
38
|
-
install: () => installNvm(),
|
|
38
|
+
install: (proxy) => installNvm(proxy),
|
|
39
39
|
upgrade: () => {
|
|
40
|
-
|
|
40
|
+
const nvmDir = getNvmDir()
|
|
41
|
+
if (!fs.existsSync(path.join(nvmDir, '.git'))) {
|
|
42
|
+
throw new Error(`${nvmDir} is not a git repository (nvm was not installed via git clone). Run \`opm nvm uninstall -y\` then \`opm nvm install\` to reinstall.`)
|
|
43
|
+
}
|
|
44
|
+
execSync(`cd "${nvmDir}" && git remote set-url origin "${getActiveRegistry('nvm')}" && git pull --tags`, {
|
|
41
45
|
stdio: 'inherit',
|
|
42
46
|
shell: shell.resolveBash(),
|
|
43
47
|
})
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ping 命令组 — 验证主机、代理或 registry 的可访问性
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* opm ping <host> Ping 指定主机/IP/URL
|
|
6
|
+
* opm ping proxy Ping 当前 active 代理地址的主机
|
|
7
|
+
* opm ping registry Ping 所有工具 registry active 地址的主机
|
|
8
|
+
*
|
|
9
|
+
* 自动检测可用工具(ping > curl > wget),无工具时提示并退出。
|
|
10
|
+
* 默认输出精简数组 [{tool, address, proxy, reachable}];-d/--detail 输出完整 JSON。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawn } from 'node:child_process'
|
|
14
|
+
import { Shell } from '@wwkit/shared'
|
|
15
|
+
import { parseFlags } from '../helpers/args.js'
|
|
16
|
+
import { output } from '../../formatter.js'
|
|
17
|
+
import { getActiveProxy, getActiveRegistry, getConfig } from '../../config.js'
|
|
18
|
+
|
|
19
|
+
const shell = new Shell()
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 从 URL 或 deb 行中提取主机名和端口(用于 ping 目标)
|
|
23
|
+
* @param {string} url
|
|
24
|
+
* @returns {{ host: string, port: string }}
|
|
25
|
+
*/
|
|
26
|
+
function extractHostPort(url) {
|
|
27
|
+
let cleaned = url
|
|
28
|
+
const debMatch = url.match(/^deb\s+(https?:\/\/\S+)/)
|
|
29
|
+
if (debMatch) cleaned = debMatch[1]
|
|
30
|
+
try {
|
|
31
|
+
const u = new URL(cleaned)
|
|
32
|
+
return { host: u.hostname, port: u.port || (u.protocol === 'https:' ? '443' : '80') }
|
|
33
|
+
} catch {
|
|
34
|
+
const m = cleaned.match(/^https?:\/\/([^:/]+)(?::(\d+))?/)
|
|
35
|
+
if (m) return { host: m[1], port: m[2] || '80' }
|
|
36
|
+
return { host: '', port: '' }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 判断地址是否为 URL(含协议前缀)
|
|
42
|
+
* @param {string} addr
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function isUrl(addr) {
|
|
46
|
+
return /^https?:\/\//i.test(addr)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 获取所有含 registry 配置的工具名列表
|
|
51
|
+
* @returns {string[]}
|
|
52
|
+
*/
|
|
53
|
+
function getRegistryTools() {
|
|
54
|
+
const config = getConfig()
|
|
55
|
+
return Object.keys(config).filter((k) => config[k] && typeof config[k] === 'object' && config[k].registry)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 从完整结果中提取精简版(仅 tool, address, proxy, reachable)
|
|
60
|
+
* @param {object} item
|
|
61
|
+
* @returns {{tool: string, address: string, proxy: string, reachable: boolean}}
|
|
62
|
+
*/
|
|
63
|
+
function compact(item) {
|
|
64
|
+
return { tool: item.tool, address: item.address, proxy: item.proxy, reachable: item.reachable }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 用 ICMP ping 检查主机可访问性
|
|
69
|
+
* @param {string} host - 主机名或 IP
|
|
70
|
+
* @param {number} count - 发送包数量
|
|
71
|
+
* @returns {Promise<object>}
|
|
72
|
+
*/
|
|
73
|
+
function checkByPing(host, count) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const isWin = shell.isWindows
|
|
76
|
+
const args = isWin
|
|
77
|
+
? ['-n', String(count), '-w', '2000', host]
|
|
78
|
+
: ['-c', String(count), '-W', '2', host]
|
|
79
|
+
|
|
80
|
+
const child = spawn('ping', args, { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
81
|
+
let stdout = ''
|
|
82
|
+
let stderr = ''
|
|
83
|
+
child.stdout.on('data', (d) => { stdout += d })
|
|
84
|
+
child.stderr.on('data', (d) => { stderr += d })
|
|
85
|
+
child.on('close', () => {
|
|
86
|
+
const raw = stdout + stderr
|
|
87
|
+
let received = 0
|
|
88
|
+
let rttMin = ''
|
|
89
|
+
let rttAvg = ''
|
|
90
|
+
let rttMax = ''
|
|
91
|
+
|
|
92
|
+
const recvMatch = raw.match(/(\d+)\s+packets?\s+received/)
|
|
93
|
+
if (recvMatch) received = parseInt(recvMatch[1], 10)
|
|
94
|
+
|
|
95
|
+
if (!isWin) {
|
|
96
|
+
const rttMatch = raw.match(/rtt min\/avg\/max(?:\/mdev)?\s*=\s*([\d.]+)\/([\d.]+)\/([\d.]+)/)
|
|
97
|
+
if (rttMatch) {
|
|
98
|
+
rttMin = rttMatch[1]
|
|
99
|
+
rttAvg = rttMatch[2]
|
|
100
|
+
rttMax = rttMatch[3]
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const msMatch = raw.match(/Minimum\s*=\s*(\d+)ms.*?Maximum\s*=\s*(\d+)ms.*?Average\s*=\s*(\d+)ms/s)
|
|
104
|
+
if (msMatch) {
|
|
105
|
+
rttMin = msMatch[1]
|
|
106
|
+
rttAvg = msMatch[3]
|
|
107
|
+
rttMax = msMatch[2]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
resolve({
|
|
112
|
+
reachable: received > 0,
|
|
113
|
+
sent: count,
|
|
114
|
+
received,
|
|
115
|
+
loss: count - received,
|
|
116
|
+
lossPercent: Math.round(((count - received) / count) * 100),
|
|
117
|
+
rttMin,
|
|
118
|
+
rttAvg,
|
|
119
|
+
rttMax,
|
|
120
|
+
raw: raw.trim(),
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
child.on('error', () => {
|
|
124
|
+
resolve({
|
|
125
|
+
reachable: false, sent: count, received: 0, loss: count,
|
|
126
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'ping command failed to execute',
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 用 curl 检查 URL 可访问性(HTTP,跳过 SSL 校验,跟随跳转)
|
|
134
|
+
* @param {string} url - 完整 URL
|
|
135
|
+
* @param {string} [proxy] - 代理地址,设置时通过 -x 路由
|
|
136
|
+
* @returns {Promise<object>}
|
|
137
|
+
*/
|
|
138
|
+
function checkByCurl(url, proxy) {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const args = ['-k', '-L', '-s', '-o', '/dev/null', '-w', '%{http_code} %{time_total}', '--connect-timeout', '2', '--max-time', '5']
|
|
141
|
+
if (proxy) args.push('-x', proxy)
|
|
142
|
+
args.push(url)
|
|
143
|
+
const child = spawn('curl', args, { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
144
|
+
let stdout = ''
|
|
145
|
+
let stderr = ''
|
|
146
|
+
child.stdout.on('data', (d) => { stdout += d })
|
|
147
|
+
child.stderr.on('data', (d) => { stderr += d })
|
|
148
|
+
child.on('close', (code) => {
|
|
149
|
+
const parts = stdout.trim().split(/\s+/)
|
|
150
|
+
const httpCode = parseInt(parts[0], 10) || 0
|
|
151
|
+
const timeTotal = parseFloat(parts[1]) || 0
|
|
152
|
+
const proxyAuthRequired = httpCode === 407
|
|
153
|
+
const allowlist = [404]
|
|
154
|
+
const reachable = (httpCode >= 200 && httpCode < 400) || allowlist.includes(httpCode)
|
|
155
|
+
const rttMs = reachable ? String(Math.round(timeTotal * 1000)) : ''
|
|
156
|
+
resolve({
|
|
157
|
+
reachable,
|
|
158
|
+
proxyAuthRequired,
|
|
159
|
+
sent: 1,
|
|
160
|
+
received: reachable ? 1 : 0,
|
|
161
|
+
loss: reachable ? 0 : 1,
|
|
162
|
+
lossPercent: reachable ? 0 : 100,
|
|
163
|
+
rttMin: rttMs,
|
|
164
|
+
rttAvg: rttMs,
|
|
165
|
+
rttMax: rttMs,
|
|
166
|
+
raw: `HTTP ${httpCode} (${timeTotal}s)`,
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
child.on('error', () => {
|
|
170
|
+
resolve({
|
|
171
|
+
reachable: false, sent: 1, received: 0, loss: 1,
|
|
172
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'curl failed to execute',
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 用 wget --spider 检查 URL 可访问性
|
|
180
|
+
* @param {string} url - 完整 URL
|
|
181
|
+
* @param {string} [proxy] - 代理地址,设置时通过 -e 路由
|
|
182
|
+
* @returns {Promise<object>}
|
|
183
|
+
*/
|
|
184
|
+
function checkByWget(url, proxy) {
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
const args = ['-q', '--spider', '--timeout=5']
|
|
187
|
+
if (proxy) {
|
|
188
|
+
args.push('-e', `use_proxy=yes`, '-e', `https_proxy=${proxy}`, '-e', `http_proxy=${proxy}`)
|
|
189
|
+
}
|
|
190
|
+
args.push(url)
|
|
191
|
+
const child = spawn('wget', args, { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
192
|
+
let stdout = ''
|
|
193
|
+
let stderr = ''
|
|
194
|
+
child.stdout.on('data', (d) => { stdout += d })
|
|
195
|
+
child.stderr.on('data', (d) => { stderr += d })
|
|
196
|
+
child.on('close', (code) => {
|
|
197
|
+
const reachable = code === 0
|
|
198
|
+
resolve({
|
|
199
|
+
reachable,
|
|
200
|
+
sent: 1,
|
|
201
|
+
received: reachable ? 1 : 0,
|
|
202
|
+
loss: reachable ? 0 : 1,
|
|
203
|
+
lossPercent: reachable ? 0 : 100,
|
|
204
|
+
rttMin: '',
|
|
205
|
+
rttAvg: '',
|
|
206
|
+
rttMax: '',
|
|
207
|
+
raw: stderr.trim() || (reachable ? 'OK' : 'Failed'),
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
child.on('error', () => {
|
|
211
|
+
resolve({
|
|
212
|
+
reachable: false, sent: 1, received: 0, loss: 1,
|
|
213
|
+
lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'wget failed to execute',
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 统一检查入口:根据可用工具选择检查方式
|
|
221
|
+
* @param {string} address - 完整地址(URL 或纯主机名/IP)
|
|
222
|
+
* @param {string} tool - 检测到的工具 ('ping' | 'curl' | 'wget')
|
|
223
|
+
* @param {number} count - ping 包数量(仅 ping 用)
|
|
224
|
+
* @param {string} [proxy] - 代理地址,设置时通过代理路由请求(仅 curl/wget)
|
|
225
|
+
* @returns {Promise<object>}
|
|
226
|
+
*/
|
|
227
|
+
async function checkReachability(address, tool, count, proxy) {
|
|
228
|
+
if (tool === 'ping') {
|
|
229
|
+
let host = address
|
|
230
|
+
if (isUrl(address)) {
|
|
231
|
+
const extracted = extractHostPort(address)
|
|
232
|
+
host = extracted.host
|
|
233
|
+
}
|
|
234
|
+
if (!host) {
|
|
235
|
+
return { reachable: false, sent: count, received: 0, loss: count, lossPercent: 100, rttMin: '', rttAvg: '', rttMax: '', raw: 'Could not parse host from address' }
|
|
236
|
+
}
|
|
237
|
+
return checkByPing(host, count)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let url = address
|
|
241
|
+
const debMatch = address.match(/^deb\s+(https?:\/\/\S+)/)
|
|
242
|
+
if (debMatch) {
|
|
243
|
+
url = debMatch[1]
|
|
244
|
+
} else if (!isUrl(url)) {
|
|
245
|
+
url = `http://${url}`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (tool === 'curl') return checkByCurl(url, proxy)
|
|
249
|
+
if (tool === 'wget') return checkByWget(url, proxy)
|
|
250
|
+
return { reachable: false, sent: 0, received: 0, loss: 0, lossPercent: 0, rttMin: '', rttAvg: '', rttMax: '', raw: 'No tool available' }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export class PingGroup {
|
|
254
|
+
constructor() {
|
|
255
|
+
this.name = 'ping'
|
|
256
|
+
this.desc = 'Ping host, active proxy, or all registries to verify accessibility'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async run(argv) {
|
|
260
|
+
const parsed = parseFlags(argv)
|
|
261
|
+
const [target] = parsed.positional
|
|
262
|
+
|
|
263
|
+
if (!target || target === '-h' || target === '--help' || target === 'help') {
|
|
264
|
+
this.printHelp()
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const tool = shell.getNetProbeTool()
|
|
269
|
+
if (!tool) {
|
|
270
|
+
console.error('[opm] No network check tool available (ping/curl/wget). Please install one.')
|
|
271
|
+
process.exit(1)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const count = parseInt(parsed.flags.c || parsed.flags.count || '4', 10)
|
|
275
|
+
const n = (isNaN(count) || count < 1) ? 4 : count
|
|
276
|
+
const detail = !!(parsed.flags.d || parsed.flags.detail)
|
|
277
|
+
|
|
278
|
+
let proxy = ''
|
|
279
|
+
if ('p' in parsed.flags || 'proxy' in parsed.flags) {
|
|
280
|
+
let raw = parsed.flags.proxy || parsed.flags.p || ''
|
|
281
|
+
if (raw === 'true') raw = ''
|
|
282
|
+
proxy = raw || getActiveProxy()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (target === 'proxy') {
|
|
286
|
+
return this._pingProxy(n, detail, tool)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (target === 'registry') {
|
|
290
|
+
return this._pingRegistry(n, detail, proxy, tool)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (target === 'source') {
|
|
294
|
+
return this._pingSource()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (proxy) {
|
|
298
|
+
const usable = await this._checkProxyUsable(proxy, tool, n)
|
|
299
|
+
if (!usable.ok) {
|
|
300
|
+
console.error(`[opm] Proxy not usable: ${usable.reason}`)
|
|
301
|
+
process.exit(1)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const result = await checkReachability(target, tool, n, proxy)
|
|
306
|
+
const item = { tool: 'host', address: target, proxy, ...result }
|
|
307
|
+
output(detail ? [item] : [compact(item)])
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 检查代理是否可用:通过代理访问 npm active registry 测试
|
|
312
|
+
* @returns {{ ok: boolean, reason: string }}
|
|
313
|
+
*/
|
|
314
|
+
async _checkProxyUsable(proxyUrl, tool, count) {
|
|
315
|
+
const testUrl = getActiveRegistry('npm')
|
|
316
|
+
const result = await checkReachability(testUrl, tool, count, proxyUrl)
|
|
317
|
+
if (result.proxyAuthRequired) {
|
|
318
|
+
return { ok: false, reason: 'authentication required (HTTP 407)' }
|
|
319
|
+
}
|
|
320
|
+
if (!result.reachable) {
|
|
321
|
+
return { ok: false, reason: result.raw }
|
|
322
|
+
}
|
|
323
|
+
return { ok: true, reason: 'OK' }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async _pingProxy(count, detail, tool) {
|
|
327
|
+
const proxyUrl = getActiveProxy()
|
|
328
|
+
if (!proxyUrl) {
|
|
329
|
+
const item = { tool: 'proxy', address: '(no active proxy)', proxy: '', reachable: false, sent: 0, received: 0, loss: 0, lossPercent: 0, rttMin: '', rttAvg: '', rttMax: '', raw: 'No active proxy configured (proxy.active is empty)' }
|
|
330
|
+
output(detail ? [item] : [compact(item)])
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const usable = await this._checkProxyUsable(proxyUrl, tool, count)
|
|
335
|
+
const item = {
|
|
336
|
+
tool: 'proxy',
|
|
337
|
+
address: proxyUrl,
|
|
338
|
+
proxy: '',
|
|
339
|
+
reachable: usable.ok,
|
|
340
|
+
sent: 1,
|
|
341
|
+
received: usable.ok ? 1 : 0,
|
|
342
|
+
loss: usable.ok ? 0 : 1,
|
|
343
|
+
lossPercent: usable.ok ? 0 : 100,
|
|
344
|
+
rttMin: '',
|
|
345
|
+
rttAvg: '',
|
|
346
|
+
rttMax: '',
|
|
347
|
+
raw: usable.reason,
|
|
348
|
+
}
|
|
349
|
+
output(detail ? [item] : [compact(item)])
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async _pingRegistry(count, detail, proxy, tool) {
|
|
353
|
+
const tools = getRegistryTools()
|
|
354
|
+
|
|
355
|
+
if (proxy) {
|
|
356
|
+
const usable = await this._checkProxyUsable(proxy, tool, 1)
|
|
357
|
+
if (!usable.ok) {
|
|
358
|
+
console.error(`[opm] Proxy not usable: ${usable.reason}`)
|
|
359
|
+
process.exit(1)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const items = await Promise.all(tools.map(async (t) => {
|
|
364
|
+
const url = getActiveRegistry(t)
|
|
365
|
+
if (!url) {
|
|
366
|
+
return { tool: t, address: '', proxy, reachable: false, sent: 0, received: 0, loss: 0, lossPercent: 0, rttMin: '', rttAvg: '', rttMax: '', raw: 'No active registry' }
|
|
367
|
+
}
|
|
368
|
+
const result = await checkReachability(url, tool, count, proxy)
|
|
369
|
+
return { tool: t, address: url, proxy, ...result }
|
|
370
|
+
}))
|
|
371
|
+
|
|
372
|
+
output(detail ? items : items.map(compact))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async _pingSource() {
|
|
376
|
+
const url = getActiveRegistry('npm')
|
|
377
|
+
const activeProxy = getActiveProxy()
|
|
378
|
+
const tool = shell.getNetProbeTool()
|
|
379
|
+
|
|
380
|
+
if (!tool) {
|
|
381
|
+
console.error('[opm] No network check tool available (ping/curl/wget).')
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const cleanProxy = activeProxy.replace(/^(https?:\/\/)[^@]*@/, '$1')
|
|
386
|
+
const authProxy = cleanProxy.replace(/^(https?:\/\/)/, '$1user:password@')
|
|
387
|
+
|
|
388
|
+
if (tool === 'curl') {
|
|
389
|
+
console.log(`# tool: ${tool}
|
|
390
|
+
# Windows (PowerShell)
|
|
391
|
+
curl.exe -k --max-time 10 ${url}
|
|
392
|
+
curl.exe -k --max-time 10 --proxy ${cleanProxy} ${url}
|
|
393
|
+
curl.exe -k --max-time 10 --proxy ${authProxy} ${url}
|
|
394
|
+
|
|
395
|
+
# Linux / macOS
|
|
396
|
+
curl -k --max-time 10 ${url}
|
|
397
|
+
curl -k --max-time 10 --proxy ${cleanProxy} ${url}
|
|
398
|
+
curl -k --max-time 10 --proxy ${authProxy} ${url}`)
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (tool === 'wget') {
|
|
403
|
+
console.log(`# tool: ${tool}
|
|
404
|
+
# Windows (PowerShell)
|
|
405
|
+
wget.exe -q --spider --timeout=10 ${url}
|
|
406
|
+
wget.exe -q --spider --timeout=10 -e use_proxy=yes -e https_proxy=${cleanProxy} ${url}
|
|
407
|
+
wget.exe -q --spider --timeout=10 -e use_proxy=yes -e https_proxy=${authProxy} ${url}
|
|
408
|
+
|
|
409
|
+
# Linux / macOS
|
|
410
|
+
wget -q --spider --timeout=10 ${url}
|
|
411
|
+
wget -q --spider --timeout=10 -e use_proxy=yes -e https_proxy=${cleanProxy} ${url}
|
|
412
|
+
wget -q --spider --timeout=10 -e use_proxy=yes -e https_proxy=${authProxy} ${url}`)
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (tool === 'ping') {
|
|
417
|
+
const { host } = extractHostPort(url)
|
|
418
|
+
console.log(`# tool: ${tool}
|
|
419
|
+
# Windows
|
|
420
|
+
ping -n 4 -w 10000 ${host}
|
|
421
|
+
|
|
422
|
+
# Linux / macOS
|
|
423
|
+
ping -c 4 -W 10 ${host}`)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
printHelp() {
|
|
428
|
+
console.log(`
|
|
429
|
+
Usage: opm ping <host|proxy|registry|source> [options]
|
|
430
|
+
|
|
431
|
+
Actions:
|
|
432
|
+
ping <host> Check a host, IP, or URL for accessibility
|
|
433
|
+
ping proxy Check the active proxy address (from config)
|
|
434
|
+
ping registry Check all tools' active registry addresses in parallel
|
|
435
|
+
ping source Print example system commands (npm active registry)
|
|
436
|
+
|
|
437
|
+
Options:
|
|
438
|
+
-c, --count <n> Number of packets to send (ping only; default: 4)
|
|
439
|
+
-p, --proxy <url> Record proxy used for the check (informational)
|
|
440
|
+
-d, --detail Output full JSON details (default: compact [{tool, address, proxy, reachable}])
|
|
441
|
+
-h, --help Show this help
|
|
442
|
+
|
|
443
|
+
Tool detection: ping > curl > wget (auto-detected)
|
|
444
|
+
|
|
445
|
+
Output:
|
|
446
|
+
default (no -d) [{ "tool": "npm", "address": "https://...", "proxy": "", "reachable": true }]
|
|
447
|
+
-d / --detail [{ "tool": "npm", "address": "https://...", "proxy": "", "reachable": true, "sent": 1, "received": 1, "loss": 0, "lossPercent": 0, "rttMin": "...", "rttAvg": "...", "rttMax": "...", "raw": "..." }]
|
|
448
|
+
|
|
449
|
+
Examples:
|
|
450
|
+
opm ping 8.8.8.8
|
|
451
|
+
opm ping https://mirrors.tools.huawei.com/npm/
|
|
452
|
+
opm ping proxy
|
|
453
|
+
opm ping registry
|
|
454
|
+
opm ping source
|
|
455
|
+
opm ping 8.8.8.8 -c 2
|
|
456
|
+
opm ping proxy -d
|
|
457
|
+
opm ping registry -d
|
|
458
|
+
opm ping sample -d
|
|
459
|
+
`)
|
|
460
|
+
}
|
|
461
|
+
}
|