@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.
@@ -0,0 +1,424 @@
1
+ /**
2
+ * proc 管理器 — 进程与系统信息管理
3
+ *
4
+ * 基于 systeminformation 库 + netstat 回退,提供:
5
+ * 系统信息、进程列表、进程搜索、端口列表、进程终止、进程树、进程详情
6
+ *
7
+ * 端口映射:优先 systeminformation.networkConnections(),
8
+ * 回退 netstat -tlnp(Linux)/ netstat -ano(Windows)。
9
+ */
10
+
11
+ import si from 'systeminformation'
12
+ import { spawn } from 'node:child_process'
13
+ import { Shell } from '@wwkit/shared'
14
+
15
+ const shell = new Shell()
16
+
17
+ /**
18
+ * MB 字节数转换
19
+ * @param {number} bytes
20
+ * @returns {number}
21
+ */
22
+ function toMB(bytes) {
23
+ return Math.round(bytes / 1024 / 1024)
24
+ }
25
+
26
+ /**
27
+ * 获取端口→进程映射
28
+ * @returns {Promise<Map<number, {pid: number, name: string}>>}
29
+ */
30
+ export async function getPortMap() {
31
+ const map = new Map()
32
+
33
+ try {
34
+ const conns = await si.networkConnections()
35
+ for (const c of conns) {
36
+ if (c.state === 'listen' && c.localPort && c.pid) {
37
+ if (!map.has(c.localPort)) {
38
+ map.set(c.localPort, { pid: c.pid, name: c.process || '' })
39
+ }
40
+ }
41
+ }
42
+ if (map.size > 0) return map
43
+ } catch {}
44
+
45
+ const fallback = shell.isWindows ? 'netstat' : 'netstat'
46
+ const args = shell.isWindows ? ['-ano'] : ['-tlnp']
47
+
48
+ return new Promise((resolve) => {
49
+ const child = spawn(fallback, args, { stdio: ['pipe', 'pipe', 'pipe'] })
50
+ let stdout = ''
51
+ child.stdout.on('data', (d) => { stdout += d })
52
+ child.on('close', () => {
53
+ const lines = stdout.split('\n')
54
+ for (const line of lines) {
55
+ if (!line.includes('LISTEN')) continue
56
+ let port = null
57
+ let pid = null
58
+ let name = ''
59
+
60
+ if (shell.isWindows) {
61
+ const m = line.match(/(\d+)\s*$/)
62
+ if (m) pid = parseInt(m[1], 10)
63
+ const addrMatch = line.match(/[:\.](\d+)\s/)
64
+ if (addrMatch) port = parseInt(addrMatch[1], 10)
65
+ } else {
66
+ const parts = line.trim().split(/\s+/)
67
+ if (parts.length >= 6) {
68
+ const localAddr = parts[3] || parts[1] || ''
69
+ const portMatch = localAddr.match(/:(\d+)$/)
70
+ if (portMatch) port = parseInt(portMatch[1], 10)
71
+ const pidPart = parts[parts.length - 1] || ''
72
+ const pidMatch = pidPart.match(/^(\d+)\/(.+)/)
73
+ if (pidMatch) {
74
+ pid = parseInt(pidMatch[1], 10)
75
+ name = pidMatch[2]
76
+ }
77
+ }
78
+ }
79
+
80
+ if (port && pid && !map.has(port)) {
81
+ map.set(port, { pid, name })
82
+ }
83
+ }
84
+ resolve(map)
85
+ })
86
+ child.on('error', () => resolve(map))
87
+ })
88
+ }
89
+
90
+ /**
91
+ * 获取系统信息
92
+ * @returns {Promise<object>}
93
+ */
94
+ export async function getSystemInfo() {
95
+ const [osInfo, cpuInfo, memInfo, fsInfo, timeInfo] = await Promise.all([
96
+ si.osInfo(),
97
+ si.cpu(),
98
+ si.mem(),
99
+ si.fsSize(),
100
+ si.time(),
101
+ ])
102
+
103
+ return {
104
+ os: {
105
+ platform: osInfo.platform,
106
+ distro: osInfo.distro,
107
+ release: osInfo.release,
108
+ arch: osInfo.arch,
109
+ hostname: osInfo.hostname,
110
+ kernel: osInfo.kernel,
111
+ },
112
+ cpu: {
113
+ manufacturer: cpuInfo.manufacturer,
114
+ brand: cpuInfo.brand,
115
+ cores: cpuInfo.cores,
116
+ speed: cpuInfo.speed ? `${cpuInfo.speed}GHz` : '',
117
+ },
118
+ memory: {
119
+ total: toMB(memInfo.total),
120
+ used: toMB(memInfo.active),
121
+ available: toMB(memInfo.available),
122
+ unit: 'MB',
123
+ },
124
+ disk: fsInfo.map((f) => ({
125
+ fs: f.fs,
126
+ mount: f.mount,
127
+ total: toMB(f.size),
128
+ used: toMB(f.used),
129
+ available: toMB(f.available),
130
+ use: f.use,
131
+ unit: 'MB',
132
+ })),
133
+ uptime: Math.round(timeInfo.uptime),
134
+ }
135
+ }
136
+
137
+ /**
138
+ * 获取进程列表(排序后取前 N 个)
139
+ * @param {number} count - 数量
140
+ * @param {'mem'|'cpu'} sortBy - 排序字段
141
+ * @returns {Promise<object>}
142
+ */
143
+ export async function listProcesses(count, sortBy) {
144
+ const [procs, portMap] = await Promise.all([
145
+ si.processes(),
146
+ getPortMap(),
147
+ ])
148
+
149
+ const pidToPorts = new Map()
150
+ for (const [port, info] of portMap) {
151
+ const arr = pidToPorts.get(info.pid) || []
152
+ arr.push(port)
153
+ pidToPorts.set(info.pid, arr)
154
+ }
155
+
156
+ const list = procs.list.map((p) => ({
157
+ pid: p.pid,
158
+ parentPid: p.parentPid,
159
+ name: p.name,
160
+ mem: Math.round(p.memRss / 1024),
161
+ memPercent: Math.round(p.mem * 100) / 100,
162
+ cpu: Math.round(p.cpu * 100) / 100,
163
+ ports: pidToPorts.get(p.pid) || [],
164
+ user: p.user || '',
165
+ state: p.state || '',
166
+ started: p.started || '',
167
+ }))
168
+
169
+ list.sort((a, b) => {
170
+ if (sortBy === 'cpu') return b.cpu - a.cpu
171
+ return b.mem - a.mem
172
+ })
173
+
174
+ return {
175
+ count,
176
+ sortBy,
177
+ processes: list.slice(0, count),
178
+ }
179
+ }
180
+
181
+ /**
182
+ * 搜索进程
183
+ * @param {{name?: string, port?: string, exact?: boolean}} opts
184
+ * @returns {Promise<object>}
185
+ */
186
+ export async function searchProcesses({ name, port, exact }) {
187
+ const [procs, portMap] = await Promise.all([
188
+ si.processes(),
189
+ getPortMap(),
190
+ ])
191
+
192
+ const pidToPorts = new Map()
193
+ for (const [p, info] of portMap) {
194
+ const arr = pidToPorts.get(info.pid) || []
195
+ arr.push(p)
196
+ pidToPorts.set(info.pid, arr)
197
+ }
198
+
199
+ let list = procs.list.map((p) => ({
200
+ pid: p.pid,
201
+ parentPid: p.parentPid,
202
+ name: p.name,
203
+ mem: Math.round(p.memRss / 1024),
204
+ memPercent: Math.round(p.mem * 100) / 100,
205
+ cpu: Math.round(p.cpu * 100) / 100,
206
+ ports: pidToPorts.get(p.pid) || [],
207
+ user: p.user || '',
208
+ state: p.state || '',
209
+ started: p.started || '',
210
+ command: p.command,
211
+ params: p.params,
212
+ }))
213
+
214
+ if (name) {
215
+ if (exact) {
216
+ list = list.filter((p) => p.name === name || p.command === name)
217
+ } else {
218
+ const lower = name.toLowerCase()
219
+ list = list.filter((p) =>
220
+ p.name.toLowerCase().includes(lower) ||
221
+ (p.command && p.command.toLowerCase().includes(lower))
222
+ )
223
+ }
224
+ }
225
+
226
+ if (port) {
227
+ const portNum = parseInt(port, 10)
228
+ list = list.filter((p) => p.ports.includes(portNum))
229
+ }
230
+
231
+ return {
232
+ name: name || null,
233
+ port: port || null,
234
+ exact: !!exact,
235
+ count: list.length,
236
+ processes: list,
237
+ }
238
+ }
239
+
240
+ /**
241
+ * 列出监听端口
242
+ * @param {string} [portFilter] - 可选端口过滤
243
+ * @returns {Promise<object>}
244
+ */
245
+ export async function listPorts(portFilter) {
246
+ const portMap = await getPortMap()
247
+ const procs = await si.processes()
248
+ const procMap = new Map(procs.list.map((p) => [p.pid, p]))
249
+
250
+ let entries = []
251
+ for (const [port, info] of portMap) {
252
+ const proc = procMap.get(info.pid)
253
+ entries.push({
254
+ port,
255
+ pid: info.pid,
256
+ name: info.name || (proc ? proc.name : ''),
257
+ user: proc ? proc.user : '',
258
+ state: proc ? proc.state : '',
259
+ started: proc ? proc.started : '',
260
+ })
261
+ }
262
+
263
+ entries.sort((a, b) => a.port - b.port)
264
+
265
+ if (portFilter) {
266
+ const portNum = parseInt(portFilter, 10)
267
+ entries = entries.filter((e) => e.port === portNum)
268
+ }
269
+
270
+ return {
271
+ port: portFilter || null,
272
+ count: entries.length,
273
+ ports: entries,
274
+ }
275
+ }
276
+
277
+ /**
278
+ * 终止进程
279
+ * @param {number} pid
280
+ * @param {'SIGTERM'|'SIGKILL'} signal
281
+ * @returns {Promise<object>}
282
+ */
283
+ export async function killByPid(pid, signal) {
284
+ try {
285
+ process.kill(pid, signal)
286
+ return { pid, signal, killed: true }
287
+ } catch (err) {
288
+ return { pid, signal, killed: false, error: err.message }
289
+ }
290
+ }
291
+
292
+ /**
293
+ * 按端口终止进程
294
+ * @param {number} port
295
+ * @param {'SIGTERM'|'SIGKILL'} signal
296
+ * @returns {Promise<object>}
297
+ */
298
+ export async function killByPort(port, signal) {
299
+ const portMap = await getPortMap()
300
+ const info = portMap.get(port)
301
+ if (!info) {
302
+ return { port, signal, killed: [], error: `No process listening on port ${port}` }
303
+ }
304
+ const result = await killByPid(info.pid, signal)
305
+ return { port, signal, killed: [{ pid: info.pid, name: info.name, ...result }] }
306
+ }
307
+
308
+ /**
309
+ * 按名称终止进程
310
+ * @param {string} name - 进程名
311
+ * @param {'SIGTERM'|'SIGKILL'} signal
312
+ * @returns {Promise<object>}
313
+ */
314
+ export async function killByName(name, signal) {
315
+ const procs = await si.processes()
316
+ const lower = name.toLowerCase()
317
+ const matched = procs.list.filter((p) =>
318
+ p.name.toLowerCase().includes(lower) ||
319
+ (p.command && p.command.toLowerCase().includes(lower))
320
+ )
321
+
322
+ if (matched.length === 0) {
323
+ return { name, signal, killed: [], error: `No process found matching "${name}"` }
324
+ }
325
+
326
+ const results = []
327
+ for (const p of matched) {
328
+ const result = await killByPid(p.pid, signal)
329
+ results.push({ pid: p.pid, name: p.name, ...result })
330
+ }
331
+
332
+ return { name, signal, killed: results }
333
+ }
334
+
335
+ /**
336
+ * 获取进程树
337
+ * @param {number} [rootPid] - 根 PID(可选,不传则显示所有顶层进程)
338
+ * @returns {Promise<object>}
339
+ */
340
+ export async function getProcessTree(rootPid) {
341
+ const procs = await si.processes()
342
+ const procMap = new Map(procs.list.map((p) => [p.pid, p]))
343
+ const childrenMap = new Map()
344
+ for (const p of procs.list) {
345
+ const arr = childrenMap.get(p.parentPid) || []
346
+ arr.push(p)
347
+ childrenMap.set(p.parentPid, arr)
348
+ }
349
+
350
+ function buildNode(pid) {
351
+ const proc = procMap.get(pid)
352
+ if (!proc) return null
353
+ const children = (childrenMap.get(pid) || []).map((c) => buildNode(c.pid)).filter(Boolean)
354
+ return {
355
+ pid: proc.pid,
356
+ parentPid: proc.parentPid,
357
+ name: proc.name,
358
+ mem: Math.round(proc.memRss / 1024),
359
+ cpu: Math.round(proc.cpu * 100) / 100,
360
+ state: proc.state || '',
361
+ children,
362
+ }
363
+ }
364
+
365
+ if (rootPid) {
366
+ const tree = buildNode(rootPid)
367
+ return { rootPid, tree }
368
+ }
369
+
370
+ const topPids = procs.list
371
+ .filter((p) => !procMap.has(p.parentPid))
372
+ .map((p) => p.pid)
373
+ .sort((a, b) => a - b)
374
+
375
+ const trees = topPids.map((pid) => buildNode(pid)).filter(Boolean)
376
+ return { rootPid: null, trees }
377
+ }
378
+
379
+ /**
380
+ * 获取进程详情
381
+ * @param {number} pid
382
+ * @returns {Promise<object>}
383
+ */
384
+ export async function getProcessDetail(pid) {
385
+ const [procs, portMap] = await Promise.all([
386
+ si.processes(),
387
+ getPortMap(),
388
+ ])
389
+
390
+ const proc = procs.list.find((p) => p.pid === pid)
391
+ if (!proc) {
392
+ return { pid, error: `Process ${pid} not found` }
393
+ }
394
+
395
+ const pidToPorts = new Map()
396
+ for (const [port, info] of portMap) {
397
+ if (info.pid === pid) {
398
+ const arr = pidToPorts.get(pid) || []
399
+ arr.push(port)
400
+ pidToPorts.set(pid, arr)
401
+ }
402
+ }
403
+
404
+ const childCount = procs.list.filter((p) => p.parentPid === pid).length
405
+
406
+ return {
407
+ pid: proc.pid,
408
+ parentPid: proc.parentPid,
409
+ name: proc.name,
410
+ command: proc.command,
411
+ params: proc.params,
412
+ path: proc.path,
413
+ user: proc.user,
414
+ state: proc.state,
415
+ priority: proc.priority,
416
+ mem: Math.round(proc.memRss / 1024),
417
+ memVsz: Math.round(proc.memVsz / 1024),
418
+ memPercent: Math.round(proc.mem * 100) / 100,
419
+ cpu: Math.round(proc.cpu * 100) / 100,
420
+ started: proc.started,
421
+ ports: pidToPorts.get(pid) || [],
422
+ childCount,
423
+ }
424
+ }
@@ -17,8 +17,8 @@ import fs from 'node:fs'
17
17
  import path from 'node:path'
18
18
  import os from 'node:os'
19
19
  import { RuntimeManager } from './runtime.js'
20
+ import { getActiveRegistry } from '../config.js'
20
21
 
21
- const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh'
22
22
  const UV_INSTALL_PS_URL = 'https://astral.sh/uv/install.ps1'
23
23
 
24
24
  /**
@@ -45,10 +45,13 @@ export function isUvInstalled() {
45
45
  }
46
46
 
47
47
  /**
48
- * 安装 uv(curl 安装脚本)
48
+ * 安装 uv(curl 安装脚本;脚本 URL 来自 uv.registry.active)
49
+ * @param {string} [proxy] - 代理地址
49
50
  * @returns {{ action: string, hint: string }}
50
51
  */
51
- export function installUv() {
52
+ export function installUv(proxy) {
53
+ const installUrl = getActiveRegistry('uv') || 'https://astral.sh/uv/install.sh'
54
+ const proxyArg = proxy ? ` -x "${proxy}"` : ''
52
55
  if (process.platform === 'win32') {
53
56
  const r = spawnSync(
54
57
  'powershell',
@@ -59,7 +62,7 @@ export function installUv() {
59
62
  throw new Error('uv install failed (powershell exited non-zero)')
60
63
  }
61
64
  } else {
62
- execSync(`curl -LsSf "${UV_INSTALL_URL}" | sh`, { stdio: 'inherit', shell: true })
65
+ execSync(`curl -LsSf${proxyArg} "${installUrl}" | sh`, { stdio: 'inherit', shell: true })
63
66
  }
64
67
  return { action: 'installed', hint: 'Restart shell or ensure ~/.local/bin is in PATH' }
65
68
  }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * WSL2 管理器(Windows Subsystem for Linux)
3
+ *
4
+ * 通过 wsl.exe CLI 管理自身与发行版。仅 Windows 可用。
5
+ * wsl.exe 输出为 UTF-16LE 编码,需用对应 encoding 读取。
6
+ */
7
+
8
+ import { execSync, spawnSync } from 'node:child_process'
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+
12
+ const WSL_EXE = 'wsl.exe'
13
+
14
+ /**
15
+ * 要求 Windows 平台,否则抛出
16
+ * @private
17
+ */
18
+ function requireWindows() {
19
+ if (process.platform !== 'win32') {
20
+ throw new Error('WSL is only available on Windows')
21
+ }
22
+ }
23
+
24
+ /**
25
+ * 检查 WSL 是否已安装(wsl.exe 是否可用)
26
+ * @returns {boolean}
27
+ */
28
+ export function isWslInstalled() {
29
+ if (process.platform !== 'win32') return false
30
+ try {
31
+ execSync(`${WSL_EXE} --version`, { encoding: 'utf16le', stdio: ['pipe', 'pipe', 'pipe'] })
32
+ return true
33
+ } catch {
34
+ return false
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 解析 wsl --version 输出,提取 WSL 版本号
40
+ * @param {string} out
41
+ * @returns {string}
42
+ */
43
+ export function parseWslVersionOutput(out) {
44
+ const m = out.match(/WSL\s+(?:版本|version)[:\s]\s*([\d.]+)/i)
45
+ return m ? m[1] : out.trim().split('\n')[0].trim()
46
+ }
47
+
48
+ /**
49
+ * 获取 WSL 版本号
50
+ * @returns {string}
51
+ */
52
+ export function getWslVersion() {
53
+ requireWindows()
54
+ const out = execSync(`${WSL_EXE} --version`, { encoding: 'utf16le', stdio: ['pipe', 'pipe', 'pipe'] })
55
+ return parseWslVersionOutput(out)
56
+ }
57
+
58
+ /**
59
+ * 安装 WSL2(含默认发行版)
60
+ * @returns {{ action: string, hint: string }}
61
+ */
62
+ export function installWsl() {
63
+ requireWindows()
64
+ execSync(`${WSL_EXE} --install`, { stdio: 'inherit', shell: true })
65
+ return { action: 'installed', hint: 'Restart may be required' }
66
+ }
67
+
68
+ /**
69
+ * 更新 WSL 内核
70
+ * @returns {{ action: string }}
71
+ */
72
+ export function updateWsl() {
73
+ requireWindows()
74
+ execSync(`${WSL_EXE} --update`, { stdio: 'inherit', shell: true })
75
+ return { action: 'updated' }
76
+ }
77
+
78
+ /**
79
+ * 卸载 WSL 提示(需通过 Windows 设置操作)
80
+ * @returns {{ action: string, hint: string }}
81
+ */
82
+ export function uninstallWsl() {
83
+ return {
84
+ action: 'uninstall',
85
+ hint: 'Go to Settings > Apps > Optional Features > "Windows Subsystem for Linux" to uninstall',
86
+ }
87
+ }
88
+
89
+ /**
90
+ * 获取 WSL 数据目录
91
+ * @returns {string}
92
+ */
93
+ export function getWslDir() {
94
+ if (process.platform !== 'win32') return ''
95
+ const localAppData = process.env.LOCALAPPDATA || ''
96
+ if (!localAppData) return ''
97
+ const packagesDir = path.join(localAppData, 'Packages')
98
+ try {
99
+ const entries = fs.readdirSync(packagesDir)
100
+ const wslEntry = entries.find((e) => /CanonicalGroupLimited|Microsoft\.WSL/i.test(e))
101
+ if (wslEntry) return path.join(packagesDir, wslEntry)
102
+ } catch {}
103
+ return packagesDir
104
+ }
105
+
106
+ /**
107
+ * 解析 wsl -l -v 输出为发行版列表
108
+ * @param {string} out
109
+ * @returns {{ name: string, state: string, version: string, default: boolean }[]}
110
+ */
111
+ export function parseDistroList(out) {
112
+ const lines = out.split('\n').map((l) => l.trim()).filter(Boolean)
113
+ const distros = []
114
+ for (const line of lines) {
115
+ if (/NAME/i.test(line) && /STATE/i.test(line)) continue
116
+ const parts = line.split(/\s+/)
117
+ if (parts.length >= 3) {
118
+ const isDefault = parts[0] === '*'
119
+ const name = isDefault ? parts[1] : parts[0]
120
+ const state = isDefault ? parts[2] : parts[1]
121
+ const version = isDefault ? parts[3] : parts[2]
122
+ distros.push({ name, state, version, default: isDefault })
123
+ }
124
+ }
125
+ return distros
126
+ }
127
+
128
+ /**
129
+ * 列出已安装的发行版
130
+ * @returns {{ name: string, state: string, version: string, default: boolean }[]}
131
+ */
132
+ export function listDistros() {
133
+ requireWindows()
134
+ const out = execSync(`${WSL_EXE} -l -v`, { encoding: 'utf16le', stdio: ['pipe', 'pipe', 'pipe'] })
135
+ return parseDistroList(out)
136
+ }
137
+
138
+ /**
139
+ * 解析 wsl -l -o 输出为可用发行版名称列表
140
+ * @param {string} out
141
+ * @returns {string[]}
142
+ */
143
+ export function parseAvailableList(out) {
144
+ const lines = out.split('\n').map((l) => l.trim()).filter(Boolean)
145
+ return lines.slice(1).map((l) => l.split(/\s+/)[0]).filter(Boolean)
146
+ }
147
+
148
+ /**
149
+ * 列出可安装的发行版
150
+ * @returns {string[]}
151
+ */
152
+ export function listAvailable() {
153
+ requireWindows()
154
+ const out = execSync(`${WSL_EXE} -l -o`, { encoding: 'utf16le', stdio: ['pipe', 'pipe', 'pipe'] })
155
+ return parseAvailableList(out)
156
+ }
157
+
158
+ /**
159
+ * 安装指定发行版
160
+ * @param {string} name
161
+ * @returns {{ name: string, action: string }}
162
+ */
163
+ export function installDistro(name) {
164
+ requireWindows()
165
+ execSync(`${WSL_EXE} --install -d ${name}`, { stdio: 'inherit', shell: true })
166
+ return { name, action: 'installed' }
167
+ }
168
+
169
+ /**
170
+ * 移除指定发行版
171
+ * @param {string} name
172
+ * @returns {{ name: string, action: string }}
173
+ */
174
+ export function removeDistro(name) {
175
+ requireWindows()
176
+ execSync(`${WSL_EXE} --unregister ${name}`, { stdio: 'inherit', shell: true })
177
+ return { name, action: 'removed' }
178
+ }
179
+
180
+ /**
181
+ * 在当前终端启动发行版
182
+ * @param {string} name
183
+ * @returns {{ name: string, action: string }}
184
+ */
185
+ export function startDistro(name) {
186
+ requireWindows()
187
+ execSync(`${WSL_EXE} -d ${name}`, { stdio: 'inherit', shell: true })
188
+ return { name, action: 'started' }
189
+ }
190
+
191
+ /**
192
+ * 停止指定发行版
193
+ * @param {string} name
194
+ * @returns {{ name: string, action: string }}
195
+ */
196
+ export function stopDistro(name) {
197
+ requireWindows()
198
+ execSync(`${WSL_EXE} --terminate ${name}`, { stdio: 'inherit', shell: true })
199
+ return { name, action: 'stopped' }
200
+ }
201
+
202
+ /**
203
+ * 停止所有发行版
204
+ * @returns {{ action: string }}
205
+ */
206
+ export function stopAll() {
207
+ requireWindows()
208
+ execSync(`${WSL_EXE} --shutdown`, { stdio: 'inherit', shell: true })
209
+ return { action: 'all stopped' }
210
+ }
211
+
212
+ /**
213
+ * 设置默认发行版
214
+ * @param {string} name
215
+ * @returns {{ name: string, action: string }}
216
+ */
217
+ export function setDefaultDistro(name) {
218
+ requireWindows()
219
+ execSync(`${WSL_EXE} --set-default ${name}`, { stdio: 'inherit', shell: true })
220
+ return { name, action: 'default set' }
221
+ }
222
+
223
+ /**
224
+ * 获取当前默认发行版名称
225
+ * @returns {string}
226
+ */
227
+ export function getDefaultDistro() {
228
+ if (!isWslInstalled()) return ''
229
+ const distros = listDistros()
230
+ const def = distros.find((d) => d.default)
231
+ return def ? def.name : ''
232
+ }
233
+
234
+ /**
235
+ * 获取 WSL 状态
236
+ * @returns {{ installed: boolean, status?: string }}
237
+ */
238
+ export function getWslStatus() {
239
+ if (!isWslInstalled()) return { installed: false }
240
+ try {
241
+ const out = execSync(`${WSL_EXE} --status`, { encoding: 'utf16le', stdio: ['pipe', 'pipe', 'pipe'] })
242
+ return { installed: true, status: out.trim() }
243
+ } catch {
244
+ return { installed: true, status: '' }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 打开 PowerShell 窗口并进入 WSL
250
+ * @param {string} [distro] - 发行版名称(空则用默认)
251
+ * @returns {{ distro: string, action: string }}
252
+ */
253
+ export function openWsl(distro) {
254
+ requireWindows()
255
+ const wslCmd = distro ? `wsl -d ${distro}` : 'wsl'
256
+ spawnSync('cmd.exe', ['/c', 'start', '"WSL"', 'powershell', '-NoExit', '-Command', wslCmd], { stdio: 'inherit' })
257
+ return { distro: distro || 'default', action: 'opened' }
258
+ }