@wwkit/opm 1.0.1 → 1.0.3
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 +4 -2
- package/src/cli/groups/docker.js +121 -0
- 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/groups/wsl.js +220 -0
- package/src/cli/index.js +12 -0
- package/src/config.json5 +80 -6
- package/src/harnesses/base.js +34 -0
- package/src/harnesses/opencode/index.js +56 -0
- package/src/index.js +19 -0
- package/src/managers/brew.js +6 -6
- package/src/managers/docker.js +298 -0
- package/src/managers/node.js +8 -2
- package/src/managers/proc.js +424 -0
- package/src/managers/python.js +7 -4
- package/src/managers/wsl.js +258 -0
- package/src/presets.js +3 -0
- package/src/tools/ftp/client.js +214 -0
- package/src/tools/ftp/index.js +406 -0
- package/src/tools/ftp/util.js +174 -0
- package/src/tools/git/config.js +374 -0
- package/src/tools/git/index.js +378 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proc 命令组 — 进程与系统信息管理
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* opm proc info 系统信息(OS、CPU、内存、磁盘、运行时间)
|
|
6
|
+
* opm proc list 列出前 N 个进程(默认按内存排序,默认 10)
|
|
7
|
+
* opm proc search 按进程名或端口号搜索
|
|
8
|
+
* opm proc port 列出监听端口及其进程
|
|
9
|
+
* opm proc kill 终止进程(按 ID / 端口 / 名称)
|
|
10
|
+
* opm proc tree 显示进程树
|
|
11
|
+
* opm proc detail 显示指定 PID 的详细信息
|
|
12
|
+
*
|
|
13
|
+
* 基于 systeminformation(随包安装)+ netstat 回退获取端口映射。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseFlags } from '../helpers/args.js'
|
|
17
|
+
import { output } from '../../formatter.js'
|
|
18
|
+
import {
|
|
19
|
+
getSystemInfo,
|
|
20
|
+
listProcesses,
|
|
21
|
+
searchProcesses,
|
|
22
|
+
listPorts,
|
|
23
|
+
killByPid,
|
|
24
|
+
killByPort,
|
|
25
|
+
killByName,
|
|
26
|
+
getProcessTree,
|
|
27
|
+
getProcessDetail,
|
|
28
|
+
} from '../../managers/proc.js'
|
|
29
|
+
|
|
30
|
+
const ACTIONS = {
|
|
31
|
+
info: { desc: 'Show system information (OS, CPU, memory, disk, uptime)' },
|
|
32
|
+
list: { desc: 'List top N processes by memory or CPU (default: 10, by mem)' },
|
|
33
|
+
search: { desc: 'Search processes by name or port' },
|
|
34
|
+
port: { desc: 'List listening ports and their processes' },
|
|
35
|
+
kill: { desc: 'Kill process by ID, port, or name' },
|
|
36
|
+
tree: { desc: 'Show process tree (by PID or all top-level)' },
|
|
37
|
+
detail: { desc: 'Show detailed info for a specific PID' },
|
|
38
|
+
help: { desc: 'Show this help' },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class ProcGroup {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.name = 'proc'
|
|
44
|
+
this.desc = 'Process & system info management (list/search/kill/port/tree)'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async run(argv) {
|
|
48
|
+
const [action, ...rest] = argv
|
|
49
|
+
|
|
50
|
+
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
51
|
+
this.printHelp()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsed = parseFlags(rest)
|
|
56
|
+
|
|
57
|
+
switch (action) {
|
|
58
|
+
case 'info':
|
|
59
|
+
return this._info()
|
|
60
|
+
case 'list':
|
|
61
|
+
return this._list(parsed)
|
|
62
|
+
case 'search':
|
|
63
|
+
return this._search(parsed)
|
|
64
|
+
case 'port':
|
|
65
|
+
return this._port(parsed)
|
|
66
|
+
case 'kill':
|
|
67
|
+
return this._kill(parsed)
|
|
68
|
+
case 'tree':
|
|
69
|
+
return this._tree(parsed)
|
|
70
|
+
case 'detail':
|
|
71
|
+
return this._detail(parsed)
|
|
72
|
+
default:
|
|
73
|
+
console.error(`Unknown action: ${action}`)
|
|
74
|
+
this.printHelp()
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async _info() {
|
|
80
|
+
const info = await getSystemInfo()
|
|
81
|
+
output(info)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async _list(parsed) {
|
|
85
|
+
const count = parseInt(parsed.flags.n || parsed.flags.count || '10', 10)
|
|
86
|
+
const n = (isNaN(count) || count < 1) ? 10 : count
|
|
87
|
+
const by = parsed.flags.b || parsed.flags.by || 'mem'
|
|
88
|
+
const sortBy = by === 'cpu' ? 'cpu' : 'mem'
|
|
89
|
+
const result = await listProcesses(n, sortBy)
|
|
90
|
+
output(result)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async _search(parsed) {
|
|
94
|
+
const name = parsed.flags.n || parsed.flags.name || ''
|
|
95
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
96
|
+
const exact = parsed.flags.exact === 'true'
|
|
97
|
+
|
|
98
|
+
if (!name && !port) {
|
|
99
|
+
console.error('Usage: opm proc search -n <name> [-p <port>] [--exact]')
|
|
100
|
+
console.error('At least one of -n/--name or -p/--port is required')
|
|
101
|
+
process.exit(1)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = await searchProcesses({ name: name || undefined, port: port || undefined, exact })
|
|
105
|
+
output(result)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async _port(parsed) {
|
|
109
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
110
|
+
const result = await listPorts(port || undefined)
|
|
111
|
+
output(result)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async _kill(parsed) {
|
|
115
|
+
const id = parsed.flags.i || parsed.flags.id || ''
|
|
116
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
117
|
+
const name = parsed.flags.n || parsed.flags.name || ''
|
|
118
|
+
const force = parsed.flags.f === 'true' || parsed.flags.force === 'true'
|
|
119
|
+
const yes = parsed.flags.y === 'true' || parsed.flags.yes === 'true'
|
|
120
|
+
const signal = force ? 'SIGKILL' : 'SIGTERM'
|
|
121
|
+
|
|
122
|
+
if (!id && !port && !name) {
|
|
123
|
+
console.error('Usage: opm proc kill -i <pid> | -p <port> | -n <name> [-f] [-y]')
|
|
124
|
+
console.error('At least one of -i/--id, -p/--port, or -n/--name is required')
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!yes) {
|
|
129
|
+
const targets = []
|
|
130
|
+
if (id) targets.push(`PID ${id}`)
|
|
131
|
+
if (port) targets.push(`port ${port}`)
|
|
132
|
+
if (name) targets.push(`name "${name}"`)
|
|
133
|
+
const msg = `Kill process by ${targets.join(', ')} with ${signal}? [y/N] `
|
|
134
|
+
const answer = await this._confirm(msg)
|
|
135
|
+
if (!answer) {
|
|
136
|
+
output({ action: 'kill', signal, cancelled: true })
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (id) {
|
|
142
|
+
const pid = parseInt(id, 10)
|
|
143
|
+
const result = await killByPid(pid, signal)
|
|
144
|
+
output({ action: 'kill', signal, killed: [result] })
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (port) {
|
|
149
|
+
const portNum = parseInt(port, 10)
|
|
150
|
+
const result = await killByPort(portNum, signal)
|
|
151
|
+
output({ action: 'kill', signal, ...result })
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (name) {
|
|
156
|
+
const result = await killByName(name, signal)
|
|
157
|
+
output({ action: 'kill', signal, ...result })
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async _tree(parsed) {
|
|
163
|
+
const pidArg = parsed.positional[0] || parsed.flags.pid || ''
|
|
164
|
+
const rootPid = pidArg ? parseInt(pidArg, 10) : null
|
|
165
|
+
const result = await getProcessTree(rootPid)
|
|
166
|
+
output(result)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async _detail(parsed) {
|
|
170
|
+
const pidArg = parsed.positional[0] || parsed.flags.pid || ''
|
|
171
|
+
if (!pidArg) {
|
|
172
|
+
console.error('Usage: opm proc detail <pid>')
|
|
173
|
+
process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
const pid = parseInt(pidArg, 10)
|
|
176
|
+
const result = await getProcessDetail(pid)
|
|
177
|
+
output(result)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_confirm(msg) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
process.stderr.write(msg)
|
|
183
|
+
process.stdin.setEncoding('utf8')
|
|
184
|
+
process.stdin.resume()
|
|
185
|
+
process.stdin.once('data', (data) => {
|
|
186
|
+
process.stdin.pause()
|
|
187
|
+
const answer = data.trim().toLowerCase()
|
|
188
|
+
resolve(answer === 'y' || answer === 'yes')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
printHelp() {
|
|
194
|
+
const actionLines = Object.entries(ACTIONS)
|
|
195
|
+
.map(([sig, { desc }]) => ` ${sig.padEnd(12)} ${desc}`)
|
|
196
|
+
.join('\n')
|
|
197
|
+
|
|
198
|
+
console.log(`
|
|
199
|
+
Usage: opm proc <action> [options]
|
|
200
|
+
|
|
201
|
+
Actions:
|
|
202
|
+
${actionLines}
|
|
203
|
+
|
|
204
|
+
Options:
|
|
205
|
+
-n, --count <n> Number of processes (list, default: 10)
|
|
206
|
+
-n, --name <name> Process name pattern (search/kill, fuzzy by default)
|
|
207
|
+
-b, --by <mem|cpu> Sort by memory or CPU (list, default: mem)
|
|
208
|
+
-p, --port <port> Port number (search/kill/port)
|
|
209
|
+
-i, --id <pid> Process ID (kill)
|
|
210
|
+
--exact Exact match (search, default: fuzzy)
|
|
211
|
+
-f, --force Use SIGKILL instead of SIGTERM (kill)
|
|
212
|
+
-y, --yes Skip confirmation prompt (kill)
|
|
213
|
+
-h, --help Show this help
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
opm proc info
|
|
217
|
+
opm proc list
|
|
218
|
+
opm proc list -n 20 -b cpu
|
|
219
|
+
opm proc search -n node
|
|
220
|
+
opm proc search -n node -p 3000
|
|
221
|
+
opm proc search -n node --exact
|
|
222
|
+
opm proc port
|
|
223
|
+
opm proc port -p 8080
|
|
224
|
+
opm proc kill -i 1234
|
|
225
|
+
opm proc kill -p 8080 -f -y
|
|
226
|
+
opm proc kill -n node -y
|
|
227
|
+
opm proc tree
|
|
228
|
+
opm proc tree 1234
|
|
229
|
+
opm proc detail 1234
|
|
230
|
+
`)
|
|
231
|
+
}
|
|
232
|
+
}
|
package/src/cli/groups/uv.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* uv 命令组 — 管理 uv 自身
|
|
3
3
|
*
|
|
4
4
|
* uv 是 Astral 出品的快速 Python 包/版本管理器(Rust 实现)。
|
|
5
|
-
* 安装方式:curl 安装脚本(非 git clone
|
|
5
|
+
* 安装方式:curl 安装脚本(非 git clone);脚本 URL 由 uv.registry.active 提供;
|
|
6
6
|
* upgrade = uv self update;uninstall = rm 二进制 + 数据目录。
|
|
7
|
-
* uv 无镜像源,故无 registry action。
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { execSync, spawnSync } from 'node:child_process'
|
|
@@ -14,15 +13,11 @@ import os from 'node:os'
|
|
|
14
13
|
import { ToolManagerGroup } from './version-manager.js'
|
|
15
14
|
import { installUv, getUvBin } from '../../managers/python.js'
|
|
16
15
|
|
|
17
|
-
const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh'
|
|
18
|
-
const UV_INSTALL_PS_URL = 'https://astral.sh/uv/install.ps1'
|
|
19
|
-
|
|
20
16
|
export class UvGroup extends ToolManagerGroup {
|
|
21
17
|
constructor() {
|
|
22
18
|
super({
|
|
23
19
|
name: 'uv',
|
|
24
20
|
desc: 'uv version manager (manage uv itself)',
|
|
25
|
-
hasRegistry: false,
|
|
26
21
|
isInstalled: () => {
|
|
27
22
|
try {
|
|
28
23
|
execSync(`${getUvBin()} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
@@ -34,7 +29,7 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
34
29
|
getPath: () => getUvBin(),
|
|
35
30
|
versionCmd: () => `${getUvBin()} --version`,
|
|
36
31
|
parseVersion: (out) => out.trim(),
|
|
37
|
-
install: () => installUv(),
|
|
32
|
+
install: (proxy) => installUv(proxy),
|
|
38
33
|
upgrade: () => {
|
|
39
34
|
execSync(`${getUvBin()} self update`, { stdio: 'inherit' })
|
|
40
35
|
return { action: 'upgraded' }
|
|
@@ -54,7 +49,10 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
54
49
|
actions: [
|
|
55
50
|
['version', 'Show uv version'],
|
|
56
51
|
['installed', 'Check if uv is installed'],
|
|
57
|
-
['
|
|
52
|
+
['registry', 'Show active uv install script URL (from opm config)'],
|
|
53
|
+
['registry set <url|preset>', 'Set active uv install script URL (writes opm config)'],
|
|
54
|
+
['registry presets', 'List available mirror presets'],
|
|
55
|
+
['install', 'Install uv (curl installer)'],
|
|
58
56
|
['upgrade', 'Update uv (uv self update)'],
|
|
59
57
|
['uninstall', 'Remove uv (~/.local/bin/uv + ~/.local/share/uv)'],
|
|
60
58
|
['dir', 'Show uv install directory'],
|
|
@@ -63,6 +61,9 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
63
61
|
examples: [
|
|
64
62
|
'opm uv version',
|
|
65
63
|
'opm uv installed',
|
|
64
|
+
'opm uv registry',
|
|
65
|
+
'opm uv registry set official',
|
|
66
|
+
'opm uv registry presets',
|
|
66
67
|
'opm uv install',
|
|
67
68
|
'opm uv upgrade',
|
|
68
69
|
'opm uv dir',
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { execSync } from 'node:child_process'
|
|
10
|
+
import readline from 'node:readline'
|
|
10
11
|
import { parseFlags } from '../helpers/args.js'
|
|
11
12
|
import { Shell } from '@wwkit/shared'
|
|
12
|
-
import { getActiveRegistry, setActiveRegistry, getPresets } from '../../config.js'
|
|
13
|
+
import { getActiveRegistry, setActiveRegistry, getPresets, getActiveProxy } from '../../config.js'
|
|
13
14
|
import { output } from '../../formatter.js'
|
|
14
15
|
|
|
15
16
|
const shell = new Shell()
|
|
@@ -24,7 +25,7 @@ const shell = new Shell()
|
|
|
24
25
|
* @property {() => string} getPath - view 展示的安装路径
|
|
25
26
|
* @property {() => string} versionCmd - 返回查询版本的 shell 命令
|
|
26
27
|
* @property {(out: string) => string} [parseVersion] - 解析版本输出
|
|
27
|
-
* @property {() => Object} install - 安装并返回结果字段(失败时 throw)
|
|
28
|
+
* @property {(proxy?: string) => Object} install - 安装并返回结果字段(失败时 throw)
|
|
28
29
|
* @property {() => Object} upgrade - 升级并返回结果字段(失败时 throw)
|
|
29
30
|
* @property {() => Object} uninstall - 卸载并返回结果字段(失败时 throw)
|
|
30
31
|
* @property {string} usage - 帮助 usage 行
|
|
@@ -44,7 +45,8 @@ export class ToolManagerGroup {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
async run(argv) {
|
|
47
|
-
const
|
|
48
|
+
const parsed = parseFlags(argv)
|
|
49
|
+
const [action, ...rest] = parsed.positional
|
|
48
50
|
|
|
49
51
|
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
50
52
|
this.printHelp()
|
|
@@ -57,6 +59,13 @@ export class ToolManagerGroup {
|
|
|
57
59
|
process.exit(1)
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
let proxy = ''
|
|
63
|
+
if ('p' in parsed.flags || 'proxy' in parsed.flags) {
|
|
64
|
+
let raw = parsed.flags.proxy || parsed.flags.p || ''
|
|
65
|
+
if (raw === 'true') raw = ''
|
|
66
|
+
proxy = raw || getActiveProxy()
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
switch (action) {
|
|
61
70
|
case 'version':
|
|
62
71
|
return this._version()
|
|
@@ -65,11 +74,11 @@ export class ToolManagerGroup {
|
|
|
65
74
|
case 'registry':
|
|
66
75
|
return this._registry(parseFlags(rest))
|
|
67
76
|
case 'install':
|
|
68
|
-
return this._install()
|
|
77
|
+
return this._install(proxy)
|
|
69
78
|
case 'upgrade':
|
|
70
79
|
return this._upgrade()
|
|
71
80
|
case 'uninstall':
|
|
72
|
-
return this._uninstall()
|
|
81
|
+
return this._uninstall(parsed)
|
|
73
82
|
case 'dir':
|
|
74
83
|
return this._dir()
|
|
75
84
|
default:
|
|
@@ -110,13 +119,15 @@ export class ToolManagerGroup {
|
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
_install() {
|
|
122
|
+
_install(proxy) {
|
|
114
123
|
if (this.cfg.isInstalled()) {
|
|
115
124
|
output({ name: this.name, installed: true, hint: `${this.name} already installed, run \`opm ${this.name} upgrade\` to update` })
|
|
116
125
|
return
|
|
117
126
|
}
|
|
118
127
|
try {
|
|
119
|
-
|
|
128
|
+
const installFn = this.cfg.install
|
|
129
|
+
const result = installFn.length >= 1 ? installFn(proxy) : installFn()
|
|
130
|
+
output({ name: this.name, installed: true, ...(result || {}) })
|
|
120
131
|
} catch (err) {
|
|
121
132
|
console.error(err.message || `Failed to install ${this.name}`)
|
|
122
133
|
process.exit(1)
|
|
@@ -136,11 +147,26 @@ export class ToolManagerGroup {
|
|
|
136
147
|
}
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
_uninstall() {
|
|
150
|
+
async _uninstall(parsed) {
|
|
140
151
|
if (!this.cfg.isInstalled()) {
|
|
141
152
|
output({ name: this.name, installed: false, hint: `${this.name} not installed` })
|
|
142
153
|
return
|
|
143
154
|
}
|
|
155
|
+
const force = parsed.flags.y || parsed.flags.yes || parsed.flags.force
|
|
156
|
+
if (force !== 'true' && force !== true) {
|
|
157
|
+
console.error(`WARNING: This will remove ${this.name} and all its data.`)
|
|
158
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
|
|
159
|
+
const answer = await new Promise((resolve) => {
|
|
160
|
+
rl.question(`Are you sure? [y/N] `, (ans) => {
|
|
161
|
+
rl.close()
|
|
162
|
+
resolve(ans.trim().toLowerCase())
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
166
|
+
output({ name: this.name, installed: true, hint: 'Uninstall cancelled' })
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
}
|
|
144
170
|
try {
|
|
145
171
|
output({ name: this.name, ...(this.cfg.uninstall() || {}) })
|
|
146
172
|
} catch (err) {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wsl 命令组 — 管理 WSL2 自身与发行版
|
|
3
|
+
*
|
|
4
|
+
* 继承 ToolManagerGroup 复用 version/installed/upgrade/uninstall/dir,
|
|
5
|
+
* 额外增加发行版管理动作(list/available/remove/start/stop/stop-all/
|
|
6
|
+
* set-default/default/status/open)与带可选参数的 install。
|
|
7
|
+
* 仅 Windows 可用;wsl.exe 输出为 UTF-16LE,需 override _version/_installed。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'node:child_process'
|
|
11
|
+
import { ToolManagerGroup } from './version-manager.js'
|
|
12
|
+
import { parseFlags } from '../helpers/args.js'
|
|
13
|
+
import { output } from '../../formatter.js'
|
|
14
|
+
import {
|
|
15
|
+
isWslInstalled,
|
|
16
|
+
getWslVersion,
|
|
17
|
+
installWsl,
|
|
18
|
+
updateWsl,
|
|
19
|
+
uninstallWsl,
|
|
20
|
+
getWslDir,
|
|
21
|
+
listDistros,
|
|
22
|
+
listAvailable,
|
|
23
|
+
installDistro,
|
|
24
|
+
removeDistro,
|
|
25
|
+
startDistro,
|
|
26
|
+
stopDistro,
|
|
27
|
+
stopAll,
|
|
28
|
+
setDefaultDistro,
|
|
29
|
+
getDefaultDistro,
|
|
30
|
+
getWslStatus,
|
|
31
|
+
openWsl,
|
|
32
|
+
} from '../../managers/wsl.js'
|
|
33
|
+
|
|
34
|
+
export class WslGroup extends ToolManagerGroup {
|
|
35
|
+
constructor() {
|
|
36
|
+
super({
|
|
37
|
+
name: 'wsl',
|
|
38
|
+
desc: 'WSL2 manager (install/upgrade/list/open distros)',
|
|
39
|
+
hasRegistry: false,
|
|
40
|
+
isInstalled: () => isWslInstalled(),
|
|
41
|
+
getPath: () => getWslDir(),
|
|
42
|
+
versionCmd: () => 'wsl.exe --version',
|
|
43
|
+
parseVersion: (out) => out.trim().split('\n')[0].trim(),
|
|
44
|
+
install: () => installWsl(),
|
|
45
|
+
upgrade: () => updateWsl(),
|
|
46
|
+
uninstall: () => uninstallWsl(),
|
|
47
|
+
usage: 'opm wsl <action> [args] [options]',
|
|
48
|
+
actions: [
|
|
49
|
+
['version', 'Show WSL version'],
|
|
50
|
+
['installed', 'Check if WSL is installed'],
|
|
51
|
+
['install [distro]', 'Install WSL2 (or a specific distribution)'],
|
|
52
|
+
['upgrade', 'Update WSL kernel (wsl --update)'],
|
|
53
|
+
['uninstall', 'Show uninstall instructions'],
|
|
54
|
+
['dir', 'Show WSL data directory'],
|
|
55
|
+
['list', 'List installed distributions'],
|
|
56
|
+
['available', 'List available distributions'],
|
|
57
|
+
['remove <distro>', 'Remove a distribution'],
|
|
58
|
+
['start [distro]', 'Start a distribution in current terminal'],
|
|
59
|
+
['stop <distro>', 'Stop a distribution'],
|
|
60
|
+
['stop-all', 'Stop all distributions'],
|
|
61
|
+
['set-default <distro>', 'Set default distribution'],
|
|
62
|
+
['default', 'Show default distribution'],
|
|
63
|
+
['status', 'Show WSL status'],
|
|
64
|
+
['open [distro]', 'Open PowerShell window and enter WSL'],
|
|
65
|
+
['help', 'Show this help'],
|
|
66
|
+
],
|
|
67
|
+
examples: [
|
|
68
|
+
'opm wsl version',
|
|
69
|
+
'opm wsl installed',
|
|
70
|
+
'opm wsl install',
|
|
71
|
+
'opm wsl install Ubuntu',
|
|
72
|
+
'opm wsl list',
|
|
73
|
+
'opm wsl available',
|
|
74
|
+
'opm wsl open',
|
|
75
|
+
'opm wsl open Ubuntu',
|
|
76
|
+
'opm wsl start Ubuntu',
|
|
77
|
+
'opm wsl stop Ubuntu',
|
|
78
|
+
'opm wsl stop-all',
|
|
79
|
+
'opm wsl set-default Ubuntu',
|
|
80
|
+
'opm wsl default',
|
|
81
|
+
'opm wsl status',
|
|
82
|
+
'opm wsl dir',
|
|
83
|
+
],
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async run(argv) {
|
|
88
|
+
const [action, ...rest] = argv
|
|
89
|
+
|
|
90
|
+
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
91
|
+
this.printHelp()
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
switch (action) {
|
|
96
|
+
case 'list':
|
|
97
|
+
return this._list()
|
|
98
|
+
case 'available':
|
|
99
|
+
return this._available()
|
|
100
|
+
case 'remove':
|
|
101
|
+
return this._remove(parseFlags(rest))
|
|
102
|
+
case 'start':
|
|
103
|
+
return this._start(parseFlags(rest))
|
|
104
|
+
case 'stop':
|
|
105
|
+
return this._stop(parseFlags(rest))
|
|
106
|
+
case 'stop-all':
|
|
107
|
+
return this._stopAll()
|
|
108
|
+
case 'set-default':
|
|
109
|
+
return this._setDefault(parseFlags(rest))
|
|
110
|
+
case 'default':
|
|
111
|
+
return this._default()
|
|
112
|
+
case 'status':
|
|
113
|
+
return this._status()
|
|
114
|
+
case 'open':
|
|
115
|
+
return this._open(parseFlags(rest))
|
|
116
|
+
case 'install':
|
|
117
|
+
return this._installDistro(parseFlags(rest))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return super.run(argv)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_version() {
|
|
124
|
+
if (!isWslInstalled()) {
|
|
125
|
+
output('')
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
output(getWslVersion())
|
|
130
|
+
} catch {
|
|
131
|
+
output('')
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_installed() {
|
|
136
|
+
const installed = isWslInstalled()
|
|
137
|
+
const result = { name: 'wsl', installed }
|
|
138
|
+
if (installed) {
|
|
139
|
+
result.path = getWslDir()
|
|
140
|
+
try {
|
|
141
|
+
result.version = getWslVersion()
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
output(result)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_installDistro(parsed) {
|
|
148
|
+
const distro = parsed.positional[0]
|
|
149
|
+
if (distro) {
|
|
150
|
+
output(installDistro(distro))
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
if (isWslInstalled()) {
|
|
154
|
+
output({ name: 'wsl', installed: true, hint: 'WSL already installed, use "opm wsl install <distro>" to install a distribution' })
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
output(installWsl())
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_list() {
|
|
161
|
+
output(listDistros())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_available() {
|
|
165
|
+
output(listAvailable().map((name) => ({ name })))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_remove(parsed) {
|
|
169
|
+
const distro = parsed.positional[0]
|
|
170
|
+
if (!distro) {
|
|
171
|
+
console.error('Usage: opm wsl remove <distro>')
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
output(removeDistro(distro))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_start(parsed) {
|
|
178
|
+
const distro = parsed.positional[0] || getDefaultDistro()
|
|
179
|
+
if (!distro) {
|
|
180
|
+
console.error('Usage: opm wsl start [distro]')
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
output(startDistro(distro))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_stop(parsed) {
|
|
187
|
+
const distro = parsed.positional[0]
|
|
188
|
+
if (!distro) {
|
|
189
|
+
console.error('Usage: opm wsl stop <distro>')
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
output(stopDistro(distro))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_stopAll() {
|
|
196
|
+
output(stopAll())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
_setDefault(parsed) {
|
|
200
|
+
const distro = parsed.positional[0]
|
|
201
|
+
if (!distro) {
|
|
202
|
+
console.error('Usage: opm wsl set-default <distro>')
|
|
203
|
+
process.exit(1)
|
|
204
|
+
}
|
|
205
|
+
output(setDefaultDistro(distro))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
_default() {
|
|
209
|
+
output({ default: getDefaultDistro() })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_status() {
|
|
213
|
+
output(getWslStatus())
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_open(parsed) {
|
|
217
|
+
const distro = parsed.positional[0] || getDefaultDistro()
|
|
218
|
+
output(openWsl(distro || ''))
|
|
219
|
+
}
|
|
220
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -15,7 +15,13 @@ import { NvmGroup } from './groups/nvm.js'
|
|
|
15
15
|
import { PhpenvGroup } from './groups/phpenv.js'
|
|
16
16
|
import { UvGroup } from './groups/uv.js'
|
|
17
17
|
import { HomebrewGroup } from './groups/homebrew.js'
|
|
18
|
+
import { WslGroup } from './groups/wsl.js'
|
|
19
|
+
import { DockerGroup } from './groups/docker.js'
|
|
18
20
|
import { EnsureGroup } from './groups/ensure.js'
|
|
21
|
+
import { PingGroup } from './groups/ping.js'
|
|
22
|
+
import { ProcGroup } from './groups/proc.js'
|
|
23
|
+
import { FtpGroup } from '../tools/ftp/index.js'
|
|
24
|
+
import { GitGroup } from '../tools/git/index.js'
|
|
19
25
|
import { HarnessGroup } from '../harnesses/base.js'
|
|
20
26
|
import { opencodeHarness } from '../harnesses/opencode/index.js'
|
|
21
27
|
import { CftGroup } from '../binaries/cft/index.js'
|
|
@@ -33,6 +39,10 @@ const GROUPS = {
|
|
|
33
39
|
apt: new PackageGroup('apt'),
|
|
34
40
|
brew: new PackageGroup('brew'),
|
|
35
41
|
homebrew: new HomebrewGroup(),
|
|
42
|
+
wsl: new WslGroup(),
|
|
43
|
+
docker: new DockerGroup(),
|
|
44
|
+
ftp: new FtpGroup(),
|
|
45
|
+
git: new GitGroup(),
|
|
36
46
|
composer: new PackageGroup('composer'),
|
|
37
47
|
bun: new PackageGroup('bun'),
|
|
38
48
|
winget: new PackageGroup('winget'),
|
|
@@ -46,6 +56,8 @@ const GROUPS = {
|
|
|
46
56
|
opencode: new HarnessGroup(opencodeHarness),
|
|
47
57
|
cft: new CftGroup(),
|
|
48
58
|
ensure: new EnsureGroup(),
|
|
59
|
+
ping: new PingGroup(),
|
|
60
|
+
proc: new ProcGroup(),
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
class CLI {
|