@wwkit/opm 1.0.1 → 1.0.2

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,295 @@
1
+ /**
2
+ * Docker 管理器
3
+ *
4
+ * 跨平台安装策略:
5
+ * - Linux: 官方便捷脚本 get.docker.com
6
+ * - macOS: brew install --cask docker (Docker Desktop)
7
+ * - Windows: WSL2 内跑便捷脚本(WSL 当作 Linux)
8
+ *
9
+ * daemon 管理:
10
+ * - Linux: systemctl start/stop/restart docker
11
+ * - macOS: open/quit Docker Desktop app
12
+ * - Windows: wsl -e systemctl start/stop/restart docker
13
+ */
14
+
15
+ import { execSync, spawnSync } from 'node:child_process'
16
+ import fs from 'node:fs'
17
+ import { getBrewBin } from './brew.js'
18
+ import { openWsl } from './wsl.js'
19
+
20
+ const DOCKER_INSTALL_URL = 'https://get.docker.com'
21
+
22
+ /**
23
+ * 检查 docker 命令是否可用
24
+ * @returns {boolean}
25
+ */
26
+ export function isDockerInstalled() {
27
+ try {
28
+ execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
29
+ return true
30
+ } catch {
31
+ return false
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 获取 docker 版本
37
+ * @returns {string}
38
+ */
39
+ export function getDockerVersion() {
40
+ const out = execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
41
+ const m = out.match(/Docker version\s+(\S+)/)
42
+ return m ? m[1] : out.trim()
43
+ }
44
+
45
+ /**
46
+ * 在 Linux 环境中执行 Docker 便捷脚本
47
+ * @private
48
+ */
49
+ function runConvenienceScript() {
50
+ execSync(`curl -fsSL ${DOCKER_INSTALL_URL} | sh`, { stdio: 'inherit', shell: true })
51
+ }
52
+
53
+ /**
54
+ * 安装 Docker
55
+ * @returns {{ action: string, method: string, hint?: string }}
56
+ */
57
+ export function installDocker() {
58
+ const platform = process.platform
59
+
60
+ if (platform === 'linux') {
61
+ runConvenienceScript()
62
+ return { action: 'installed', method: 'convenience-script' }
63
+ }
64
+
65
+ if (platform === 'darwin') {
66
+ execSync(`${getBrewBin()} install --cask docker`, { stdio: 'inherit', shell: true })
67
+ return { action: 'installed', method: 'brew-cask', hint: 'Start Docker Desktop from Applications' }
68
+ }
69
+
70
+ if (platform === 'win32') {
71
+ execSync('wsl.exe -e bash -c "curl -fsSL https://get.docker.com | sh"', { stdio: 'inherit', shell: true })
72
+ return { action: 'installed', method: 'wsl-convenience-script', hint: 'Docker installed inside WSL2' }
73
+ }
74
+
75
+ throw new Error(`Unsupported platform: ${platform}`)
76
+ }
77
+
78
+ /**
79
+ * 升级 Docker
80
+ * @returns {{ action: string, method: string }}
81
+ */
82
+ export function upgradeDocker() {
83
+ const platform = process.platform
84
+
85
+ if (platform === 'linux') {
86
+ try {
87
+ execSync('apt-get update && apt-get upgrade -y docker-ce', { stdio: 'inherit', shell: true })
88
+ return { action: 'upgraded', method: 'apt' }
89
+ } catch {
90
+ try {
91
+ execSync('dnf upgrade -y docker-ce', { stdio: 'inherit', shell: true })
92
+ return { action: 'upgraded', method: 'dnf' }
93
+ } catch {
94
+ runConvenienceScript()
95
+ return { action: 'upgraded', method: 'convenience-script' }
96
+ }
97
+ }
98
+ }
99
+
100
+ if (platform === 'darwin') {
101
+ execSync(`${getBrewBin()} upgrade --cask docker`, { stdio: 'inherit', shell: true })
102
+ return { action: 'upgraded', method: 'brew-cask' }
103
+ }
104
+
105
+ if (platform === 'win32') {
106
+ execSync('wsl.exe -e bash -c "sudo apt-get update && sudo apt-get upgrade -y docker-ce"', { stdio: 'inherit', shell: true })
107
+ return { action: 'upgraded', method: 'wsl-apt' }
108
+ }
109
+
110
+ throw new Error(`Unsupported platform: ${platform}`)
111
+ }
112
+
113
+ /**
114
+ * 卸载 Docker
115
+ * @returns {{ action: string, hint: string }}
116
+ */
117
+ export function uninstallDocker() {
118
+ const platform = process.platform
119
+
120
+ if (platform === 'linux') {
121
+ try {
122
+ execSync('apt-get purge -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
123
+ } catch {
124
+ execSync('dnf remove -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
125
+ }
126
+ return { action: 'uninstalled', hint: 'Images/containers left in /var/lib/docker, remove manually if needed' }
127
+ }
128
+
129
+ if (platform === 'darwin') {
130
+ execSync(`${getBrewBin()} uninstall --cask docker`, { stdio: 'inherit', shell: true })
131
+ return { action: 'uninstalled', hint: 'Docker Desktop app removed' }
132
+ }
133
+
134
+ if (platform === 'win32') {
135
+ return {
136
+ action: 'uninstall',
137
+ hint: 'Run in WSL: sudo apt-get purge -y docker-ce docker-ce-cli containerd.io',
138
+ }
139
+ }
140
+
141
+ throw new Error(`Unsupported platform: ${platform}`)
142
+ }
143
+
144
+ /**
145
+ * 获取 Docker 数据目录
146
+ * @returns {string}
147
+ */
148
+ export function getDockerDir() {
149
+ const platform = process.platform
150
+
151
+ if (platform === 'linux') {
152
+ return '/var/lib/docker'
153
+ }
154
+
155
+ if (platform === 'darwin') {
156
+ const home = process.env.HOME || ''
157
+ return `${home}/.docker`
158
+ }
159
+
160
+ if (platform === 'win32') {
161
+ const localAppData = process.env.LOCALAPPDATA || ''
162
+ return localAppData ? `${localAppData}\\Docker` : ''
163
+ }
164
+
165
+ return ''
166
+ }
167
+
168
+ /**
169
+ * 获取 Docker 状态(docker info 摘要)
170
+ * @returns {{ installed: boolean, running?: boolean, info?: string }}
171
+ */
172
+ export function getDockerStatus() {
173
+ if (!isDockerInstalled()) return { installed: false }
174
+ try {
175
+ const out = execSync('docker info --format "{{.ServerVersion}} {{.OperatingSystem}} {{.Containers}} {{.Images}}"', {
176
+ encoding: 'utf8',
177
+ stdio: ['pipe', 'pipe', 'pipe'],
178
+ })
179
+ const [serverVersion, operatingSystem, containers, images] = out.trim().split(' ')
180
+ return {
181
+ installed: true,
182
+ running: true,
183
+ serverVersion,
184
+ operatingSystem,
185
+ containers: parseInt(containers, 10) || 0,
186
+ images: parseInt(images, 10) || 0,
187
+ }
188
+ } catch {
189
+ return { installed: true, running: false, hint: 'Docker daemon not running' }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * 启动 Docker daemon
195
+ * @returns {{ action: string, method: string }}
196
+ */
197
+ export function startDocker() {
198
+ const platform = process.platform
199
+
200
+ if (platform === 'linux') {
201
+ execSync('sudo systemctl start docker', { stdio: 'inherit', shell: true })
202
+ return { action: 'started', method: 'systemctl' }
203
+ }
204
+
205
+ if (platform === 'darwin') {
206
+ execSync('open -a Docker', { stdio: 'inherit', shell: true })
207
+ return { action: 'started', method: 'docker-desktop' }
208
+ }
209
+
210
+ if (platform === 'win32') {
211
+ execSync('wsl.exe -e sudo systemctl start docker', { stdio: 'inherit', shell: true })
212
+ return { action: 'started', method: 'wsl-systemctl' }
213
+ }
214
+
215
+ throw new Error(`Unsupported platform: ${platform}`)
216
+ }
217
+
218
+ /**
219
+ * 停止 Docker daemon
220
+ * @returns {{ action: string, method: string }}
221
+ */
222
+ export function stopDocker() {
223
+ const platform = process.platform
224
+
225
+ if (platform === 'linux') {
226
+ execSync('sudo systemctl stop docker', { stdio: 'inherit', shell: true })
227
+ return { action: 'stopped', method: 'systemctl' }
228
+ }
229
+
230
+ if (platform === 'darwin') {
231
+ execSync('osascript -e \'quit app "Docker"\'', { stdio: 'inherit', shell: true })
232
+ return { action: 'stopped', method: 'docker-desktop' }
233
+ }
234
+
235
+ if (platform === 'win32') {
236
+ execSync('wsl.exe -e sudo systemctl stop docker', { stdio: 'inherit', shell: true })
237
+ return { action: 'stopped', method: 'wsl-systemctl' }
238
+ }
239
+
240
+ throw new Error(`Unsupported platform: ${platform}`)
241
+ }
242
+
243
+ /**
244
+ * 重启 Docker daemon
245
+ * @returns {{ action: string, method: string }}
246
+ */
247
+ export function restartDocker() {
248
+ const platform = process.platform
249
+
250
+ if (platform === 'linux') {
251
+ execSync('sudo systemctl restart docker', { stdio: 'inherit', shell: true })
252
+ return { action: 'restarted', method: 'systemctl' }
253
+ }
254
+
255
+ if (platform === 'darwin') {
256
+ execSync('osascript -e \'quit app "Docker"\' && sleep 1 && open -a Docker', { stdio: 'inherit', shell: true })
257
+ return { action: 'restarted', method: 'docker-desktop' }
258
+ }
259
+
260
+ if (platform === 'win32') {
261
+ execSync('wsl.exe -e sudo systemctl restart docker', { stdio: 'inherit', shell: true })
262
+ return { action: 'restarted', method: 'wsl-systemctl' }
263
+ }
264
+
265
+ throw new Error(`Unsupported platform: ${platform}`)
266
+ }
267
+
268
+ /**
269
+ * 打开终端并确保 docker 可用
270
+ * - Linux: 新开终端
271
+ * - macOS: 打开 Terminal.app
272
+ * - Windows: 打开 PowerShell → WSL2(docker 装在 WSL 内)
273
+ * @returns {{ action: string, method: string }}
274
+ */
275
+ export function openDocker() {
276
+ const platform = process.platform
277
+
278
+ if (platform === 'linux') {
279
+ const shell = process.env.SHELL || 'bash'
280
+ spawnSync(shell, [], { stdio: 'inherit' })
281
+ return { action: 'opened', method: 'shell' }
282
+ }
283
+
284
+ if (platform === 'darwin') {
285
+ execSync('open -a Terminal', { stdio: 'inherit', shell: true })
286
+ return { action: 'opened', method: 'terminal-app' }
287
+ }
288
+
289
+ if (platform === 'win32') {
290
+ openWsl('')
291
+ return { action: 'opened', method: 'wsl-powershell' }
292
+ }
293
+
294
+ throw new Error(`Unsupported platform: ${platform}`)
295
+ }
@@ -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
+ }