@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
package/src/config.json5
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// 命令行 -p/--proxy 传入时优先使用传入值。
|
|
12
12
|
proxy: {
|
|
13
13
|
local: "socks5://127.0.0.1:1080",
|
|
14
|
+
huawei: "http://{env:PROXY_USER}:{env:PROXY_PASSWORD}@proxyhm-hk.security.huawei.com:8080",
|
|
14
15
|
active: "",
|
|
15
16
|
},
|
|
16
17
|
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"ubuntu-aliyun": "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse",
|
|
54
55
|
"debian-official": "deb http://deb.debian.org/debian/ bookworm main",
|
|
55
56
|
"debian-tsinghua": "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main",
|
|
56
|
-
active: "ubuntu-
|
|
57
|
+
active: "ubuntu-huawei",
|
|
57
58
|
},
|
|
58
59
|
},
|
|
59
60
|
|
|
@@ -99,7 +100,8 @@
|
|
|
99
100
|
registry: {
|
|
100
101
|
official: "https://github.com/nvm-sh/nvm.git",
|
|
101
102
|
gitee: "https://gitee.com/mirrors/nvm.git",
|
|
102
|
-
|
|
103
|
+
huawei: "https://gitee.com/mirrors/nvm.git",
|
|
104
|
+
active: "huawei",
|
|
103
105
|
},
|
|
104
106
|
},
|
|
105
107
|
|
|
@@ -110,7 +112,8 @@
|
|
|
110
112
|
official: "https://nodejs.org/dist",
|
|
111
113
|
taobao: "https://npmmirror.com/mirrors/node",
|
|
112
114
|
tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/",
|
|
113
|
-
|
|
115
|
+
huawei: "https://mirrors.huaweicloud.com/nodejs/",
|
|
116
|
+
active: "huawei",
|
|
114
117
|
},
|
|
115
118
|
},
|
|
116
119
|
|
|
@@ -119,7 +122,8 @@
|
|
|
119
122
|
registry: {
|
|
120
123
|
official: "https://github.com/phpenv/phpenv.git",
|
|
121
124
|
gitee: "https://gitee.com/mirrors/phpenv.git",
|
|
122
|
-
|
|
125
|
+
huawei: "https://mirrors.huaweicloud.com/phpenv.git",
|
|
126
|
+
active: "huawei",
|
|
123
127
|
},
|
|
124
128
|
},
|
|
125
129
|
|
|
@@ -130,7 +134,8 @@
|
|
|
130
134
|
official: "https://www.php.net/distributions",
|
|
131
135
|
tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/php",
|
|
132
136
|
ustc: "https://mirrors.ustc.edu.cn/php",
|
|
133
|
-
|
|
137
|
+
huawei: "https://mirrors.huaweicloud.com/php/",
|
|
138
|
+
active: "huawei",
|
|
134
139
|
},
|
|
135
140
|
},
|
|
136
141
|
|
|
@@ -141,7 +146,8 @@
|
|
|
141
146
|
registry: {
|
|
142
147
|
official: "https://github.com/astral-sh/python-build-standalone/releases/download",
|
|
143
148
|
ghproxy: "https://ghproxy.com/https://github.com/astral-sh/python-build-standalone/releases/download",
|
|
144
|
-
|
|
149
|
+
huawei: "https://mirrors.huaweicloud.com/python-build-standalone/",
|
|
150
|
+
active: "ghproxy",
|
|
145
151
|
},
|
|
146
152
|
},
|
|
147
153
|
|
|
@@ -152,7 +158,75 @@
|
|
|
152
158
|
registry: {
|
|
153
159
|
official: "https://googlechromelabs.github.io/chrome-for-testing",
|
|
154
160
|
npmmirror: "https://npmmirror.com/mirrors/chrome-for-testing",
|
|
161
|
+
huawei: "https://mirrors.huaweicloud.com/google/chrome-for-testing/",
|
|
162
|
+
deepblue: "http://deepbluenet.com/mirrors/chrome-for-testing",
|
|
155
163
|
active: "npmmirror",
|
|
156
164
|
},
|
|
157
165
|
},
|
|
166
|
+
|
|
167
|
+
// uv 自身安装脚本镜像(install.sh URL)
|
|
168
|
+
// active 指向预设 key 或完整 URL;install 时 curl 该地址执行
|
|
169
|
+
uv: {
|
|
170
|
+
registry: {
|
|
171
|
+
official: "https://astral.sh/uv/install.sh",
|
|
172
|
+
ghproxy: "https://ghproxy.com/https://astral.sh/uv/install.sh",
|
|
173
|
+
active: "ghproxy",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// docker 自身安装脚本镜像(便捷脚本 URL)
|
|
178
|
+
// active 指向预设 key 或完整 URL;install 时 curl 该地址执行(Linux 用)
|
|
179
|
+
docker: {
|
|
180
|
+
registry: {
|
|
181
|
+
official: "https://get.docker.com",
|
|
182
|
+
huawei: "https://mirrors.huaweicloud.com/docker-ce/linux/",
|
|
183
|
+
active: "huawei",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// homebrew 自身安装脚本镜像(install.sh URL)
|
|
188
|
+
// active 指向预设 key 或完整 URL;install 时 curl 该地址执行
|
|
189
|
+
homebrew: {
|
|
190
|
+
registry: {
|
|
191
|
+
official: "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh",
|
|
192
|
+
gitee: "https://gitee.com/mirrors/homebrew-install/raw/HEAD/install.sh",
|
|
193
|
+
active: "official",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// FTP 连接配置
|
|
198
|
+
// password 用 {env:XXX} 引用环境变量,不硬编码
|
|
199
|
+
// pasv: false — 阿里云虚拟主机必须用主动模式
|
|
200
|
+
ftp: {
|
|
201
|
+
host: "wh-nxb3pizm7zyfvpv4113.my3v.com",
|
|
202
|
+
port: 21,
|
|
203
|
+
user: "{env:FTP_USER}",
|
|
204
|
+
password: "{env:FTP_PASSWORD}",
|
|
205
|
+
remote_root: "/htdocs/webwork",
|
|
206
|
+
base_url: "http://deepbluenet.com/webwork",
|
|
207
|
+
timeout: 120,
|
|
208
|
+
max_retries: 2,
|
|
209
|
+
pasv: false,
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Git 全局配置预设(opm git init 一键初始化时使用)
|
|
213
|
+
// pullRebase: false — pull 时用 merge(有冲突直接合并),非 rebase
|
|
214
|
+
git: {
|
|
215
|
+
sslVerify: false,
|
|
216
|
+
user: {
|
|
217
|
+
name: "",
|
|
218
|
+
email: "",
|
|
219
|
+
},
|
|
220
|
+
pullRebase: false,
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// opencode AI coding agent 环境变量
|
|
224
|
+
// {pkg:@wwkit/harness} 动态解析为已安装的 @wwkit/harness 包目录
|
|
225
|
+
// 任何设备安装 @wwkit/opm + @wwkit/harness 后自动生效,无需手动配置路径
|
|
226
|
+
opencode: {
|
|
227
|
+
env: {
|
|
228
|
+
OPENCODE_CONFIG_DIR: "{pkg:@wwkit/harness}",
|
|
229
|
+
OPENCODE_CONFIG_CONTENT: "",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
158
232
|
}
|
package/src/harnesses/base.js
CHANGED
|
@@ -26,6 +26,8 @@ const ACTIONS = {
|
|
|
26
26
|
config: { desc: 'Show config file content' },
|
|
27
27
|
clear: { desc: 'Clear sessions / data' },
|
|
28
28
|
dir: { desc: 'Show data / db / log / config paths' },
|
|
29
|
+
env: { desc: 'Show harness environment variables' },
|
|
30
|
+
open: { desc: 'Open TUI in a work directory (with env vars injected)' },
|
|
29
31
|
help: { desc: 'Show this help' },
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -77,6 +79,10 @@ export class HarnessGroup {
|
|
|
77
79
|
return this._clear(rest)
|
|
78
80
|
case 'dir':
|
|
79
81
|
return this._dir()
|
|
82
|
+
case 'env':
|
|
83
|
+
return this._env()
|
|
84
|
+
case 'open':
|
|
85
|
+
return this._open(rest)
|
|
80
86
|
default:
|
|
81
87
|
console.error(`Unknown action: ${action}`)
|
|
82
88
|
this.printHelp()
|
|
@@ -170,6 +176,30 @@ export class HarnessGroup {
|
|
|
170
176
|
})
|
|
171
177
|
}
|
|
172
178
|
|
|
179
|
+
_env() {
|
|
180
|
+
if (!this.cfg.getEnvVars) {
|
|
181
|
+
output({ name: this.cfg.name, env: {}, hint: 'No env vars defined for this harness' })
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
const env = this.cfg.getEnvVars()
|
|
185
|
+
output({ name: this.cfg.name, env })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_open(rest) {
|
|
189
|
+
if (!this.cfg.open) {
|
|
190
|
+
console.error(`open is not supported for ${this.cfg.name}`)
|
|
191
|
+
process.exit(1)
|
|
192
|
+
}
|
|
193
|
+
const parsed = parseFlags(rest)
|
|
194
|
+
const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
|
|
195
|
+
const resolved = path.resolve(workDir)
|
|
196
|
+
if (!fs.existsSync(resolved)) {
|
|
197
|
+
console.error(`Directory not found: ${resolved}`)
|
|
198
|
+
process.exit(1)
|
|
199
|
+
}
|
|
200
|
+
this.cfg.open(resolved)
|
|
201
|
+
}
|
|
202
|
+
|
|
173
203
|
printHelp() {
|
|
174
204
|
const actionLines = Object.entries(ACTIONS)
|
|
175
205
|
.map(([sig, { desc }]) => ` ${sig.padEnd(12)} ${desc}`)
|
|
@@ -191,6 +221,7 @@ ${actionLines}
|
|
|
191
221
|
${clearSection}
|
|
192
222
|
Options:
|
|
193
223
|
-p, --proxy <url> Proxy for install/upgrade (overrides config proxy.active)
|
|
224
|
+
-d, --dir <dir> Working directory for open (default: cwd)
|
|
194
225
|
-h, --help Show this help
|
|
195
226
|
|
|
196
227
|
Examples:
|
|
@@ -200,6 +231,9 @@ Examples:
|
|
|
200
231
|
opm ${this.cfg.name} config
|
|
201
232
|
opm ${this.cfg.name} clear
|
|
202
233
|
opm ${this.cfg.name} dir
|
|
234
|
+
opm ${this.cfg.name} env
|
|
235
|
+
opm ${this.cfg.name} open Open in current directory
|
|
236
|
+
opm ${this.cfg.name} open -d /path/to/project
|
|
203
237
|
`)
|
|
204
238
|
}
|
|
205
239
|
}
|
|
@@ -3,14 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* opencode 是 AI coding agent,通过 npm 全局包 opencode-ai 安装(binary: opencode)。
|
|
5
5
|
* 数据目录跨平台(db.js),会话清理复用 clear.js。
|
|
6
|
+
* env 命令管理 OPENCODE_CONFIG_DIR / OPENCODE_CONFIG_CONTENT 环境变量。
|
|
7
|
+
* open 命令在指定工作目录启动 opencode TUI(自动注入 env 变量)。
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import fs from 'node:fs'
|
|
9
11
|
import path from 'node:path'
|
|
10
12
|
import os from 'node:os'
|
|
13
|
+
import { spawn } from 'node:child_process'
|
|
14
|
+
import { Shell } from '@wwkit/shared'
|
|
15
|
+
import { getConfig } from '../../config.js'
|
|
11
16
|
import { getDataDir, getDbPath, getLogPath } from './db.js'
|
|
12
17
|
import { runClear } from './clear.js'
|
|
13
18
|
|
|
19
|
+
const shell = new Shell()
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* opencode 专属环境变量名
|
|
23
|
+
*/
|
|
24
|
+
const ENV_VARS = ['OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG_CONTENT']
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* 查找 opencode 配置文件路径(优先 .jsonc,其次 .json)
|
|
16
28
|
* @returns {string|null}
|
|
@@ -25,6 +37,47 @@ function findConfigFile() {
|
|
|
25
37
|
return null
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 获取 env 变量当前值
|
|
42
|
+
* 优先级:系统环境变量 > opm config opencode.env
|
|
43
|
+
* @returns {Record<string, string>}
|
|
44
|
+
*/
|
|
45
|
+
function getEnvVars() {
|
|
46
|
+
const cfgEnv = (getConfig().opencode?.env) || {}
|
|
47
|
+
const result = {}
|
|
48
|
+
for (const key of ENV_VARS) {
|
|
49
|
+
result[key] = process.env[key] || cfgEnv[key] || ''
|
|
50
|
+
}
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 在指定工作目录启动 opencode TUI
|
|
56
|
+
* 自动注入有值的环境变量(系统 env > opm config)
|
|
57
|
+
* @param {string} workDir - 工作目录
|
|
58
|
+
*/
|
|
59
|
+
function openTui(workDir) {
|
|
60
|
+
const env = { ...process.env }
|
|
61
|
+
const cfgEnv = getEnvVars()
|
|
62
|
+
for (const key of ENV_VARS) {
|
|
63
|
+
if (cfgEnv[key]) {
|
|
64
|
+
env[key] = cfgEnv[key]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cmd = shell.isWindows ? 'opencode.exe' : 'opencode'
|
|
69
|
+
const child = spawn(cmd, [], {
|
|
70
|
+
cwd: workDir,
|
|
71
|
+
stdio: 'inherit',
|
|
72
|
+
env,
|
|
73
|
+
shell: shell.shellForExec(false),
|
|
74
|
+
})
|
|
75
|
+
child.on('error', (err) => {
|
|
76
|
+
console.error(`Failed to start opencode: ${err.message}`)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
28
81
|
export const opencodeHarness = {
|
|
29
82
|
name: 'opencode',
|
|
30
83
|
desc: 'opencode AI coding agent',
|
|
@@ -43,4 +96,7 @@ export const opencodeHarness = {
|
|
|
43
96
|
['--backup', 'Backup the DB before deleting (default: off)'],
|
|
44
97
|
['--db <path>', 'Override the opencode database path'],
|
|
45
98
|
],
|
|
99
|
+
envVars: ENV_VARS,
|
|
100
|
+
getEnvVars,
|
|
101
|
+
open: openTui,
|
|
46
102
|
}
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,13 @@ export { CftGroup } from './binaries/cft/index.js'
|
|
|
16
16
|
export { NvmGroup } from './cli/groups/nvm.js'
|
|
17
17
|
export { PhpenvGroup } from './cli/groups/phpenv.js'
|
|
18
18
|
export { UvGroup } from './cli/groups/uv.js'
|
|
19
|
+
export { HomebrewGroup } from './cli/groups/homebrew.js'
|
|
20
|
+
export { WslGroup } from './cli/groups/wsl.js'
|
|
21
|
+
export { DockerGroup } from './cli/groups/docker.js'
|
|
22
|
+
export { PingGroup } from './cli/groups/ping.js'
|
|
23
|
+
export { ProcGroup } from './cli/groups/proc.js'
|
|
24
|
+
export { FtpGroup } from './tools/ftp/index.js'
|
|
25
|
+
export { GitGroup } from './tools/git/index.js'
|
|
19
26
|
export { ToolManagerGroup } from './cli/groups/version-manager.js'
|
|
20
27
|
export { parseFlags } from './cli/helpers/args.js'
|
|
21
28
|
|
|
@@ -31,10 +38,22 @@ export { BunManager } from './managers/bun.js'
|
|
|
31
38
|
export { NodeManager, isNvmInstalled, getNvmSh } from './managers/node.js'
|
|
32
39
|
export { PhpManager, isPhpenvInstalled, getPhpenvBin } from './managers/php.js'
|
|
33
40
|
export { PythonManager, isUvInstalled, getUvBin } from './managers/python.js'
|
|
41
|
+
export {
|
|
42
|
+
isWslInstalled, getWslVersion, parseWslVersionOutput, parseDistroList,
|
|
43
|
+
parseAvailableList, listDistros, listAvailable, installDistro, removeDistro,
|
|
44
|
+
startDistro, stopDistro, stopAll, setDefaultDistro, getDefaultDistro,
|
|
45
|
+
getWslStatus, openWsl, installWsl, updateWsl, uninstallWsl, getWslDir,
|
|
46
|
+
} from './managers/wsl.js'
|
|
47
|
+
export {
|
|
48
|
+
isDockerInstalled, getDockerVersion, installDocker, upgradeDocker,
|
|
49
|
+
uninstallDocker, getDockerDir, getDockerStatus, startDocker, stopDocker,
|
|
50
|
+
restartDocker, openDocker,
|
|
51
|
+
} from './managers/docker.js'
|
|
34
52
|
|
|
35
53
|
export {
|
|
36
54
|
NPM_PRESETS, PIP_PRESETS, DNF_PRESETS, APT_PRESETS, BREW_PRESETS, COMPOSER_PRESETS,
|
|
37
55
|
BUN_PRESETS, NVM_PRESETS, NODE_PRESETS, PHPENV_PRESETS, PHP_PRESETS, PYTHON_PRESETS,
|
|
56
|
+
UV_PRESETS, DOCKER_PRESETS, HOMEBREW_PRESETS,
|
|
38
57
|
getPresets, resolveRegistryInput,
|
|
39
58
|
} from './presets.js'
|
|
40
59
|
export { PROXY_PRESETS } from './proxy-presets.js'
|
package/src/managers/brew.js
CHANGED
|
@@ -12,9 +12,6 @@ import fs from 'node:fs'
|
|
|
12
12
|
import { PackageManager } from './base.js'
|
|
13
13
|
import { getActiveRegistry, getPresets, setActiveRegistry } from '../config.js'
|
|
14
14
|
|
|
15
|
-
const BREW_INSTALL_URL = 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh'
|
|
16
|
-
const BREW_UNINSTALL_URL = 'https://raw.githubusercontent.com/Homebrew/uninstall/HEAD/uninstall.sh'
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
16
|
* 定位 brew 可执行文件(兼容 Intel / Apple Silicon 路径),找不到时退回 PATH
|
|
20
17
|
* @returns {string}
|
|
@@ -40,14 +37,17 @@ export function isHomebrewInstalled() {
|
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
/**
|
|
43
|
-
* 安装 Homebrew(curl
|
|
40
|
+
* 安装 Homebrew(curl 官方安装脚本;脚本 URL 来自 homebrew.registry.active)
|
|
41
|
+
* @param {string} [proxy] - 代理地址
|
|
44
42
|
* @returns {{ action: string, hint: string }}
|
|
45
43
|
*/
|
|
46
|
-
export function installHomebrew() {
|
|
44
|
+
export function installHomebrew(proxy) {
|
|
47
45
|
if (process.platform === 'win32') {
|
|
48
46
|
throw new Error('Homebrew is not supported on Windows (use WSL)')
|
|
49
47
|
}
|
|
50
|
-
|
|
48
|
+
const installUrl = getActiveRegistry('homebrew') || 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh'
|
|
49
|
+
const proxyArg = proxy ? ` -x "${proxy}"` : ''
|
|
50
|
+
execSync(`curl -fsSL${proxyArg} "${installUrl}" | bash`, { stdio: 'inherit', shell: true })
|
|
51
51
|
return { action: 'installed', hint: 'Restart shell or ensure /opt/homebrew/bin (or /usr/local/bin) is in PATH' }
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -0,0 +1,298 @@
|
|
|
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
|
+
import { getActiveRegistry } from '../config.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 检查 docker 命令是否可用
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
export function isDockerInstalled() {
|
|
26
|
+
try {
|
|
27
|
+
execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 获取 docker 版本
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export function getDockerVersion() {
|
|
39
|
+
const out = execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
40
|
+
const m = out.match(/Docker version\s+(\S+)/)
|
|
41
|
+
return m ? m[1] : out.trim()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 在 Linux 环境中执行 Docker 便捷脚本(脚本 URL 来自 docker.registry.active)
|
|
46
|
+
* @param {string} [proxy] - 代理地址
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
function runConvenienceScript(proxy) {
|
|
50
|
+
const installUrl = getActiveRegistry('docker') || 'https://get.docker.com'
|
|
51
|
+
const proxyArg = proxy ? ` -x "${proxy}"` : ''
|
|
52
|
+
execSync(`curl -fsSL${proxyArg} ${installUrl} | sh`, { stdio: 'inherit', shell: true })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 安装 Docker
|
|
57
|
+
* @param {string} [proxy] - 代理地址
|
|
58
|
+
* @returns {{ action: string, method: string, hint?: string }}
|
|
59
|
+
*/
|
|
60
|
+
export function installDocker(proxy) {
|
|
61
|
+
const platform = process.platform
|
|
62
|
+
|
|
63
|
+
if (platform === 'linux') {
|
|
64
|
+
runConvenienceScript()
|
|
65
|
+
return { action: 'installed', method: 'convenience-script' }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (platform === 'darwin') {
|
|
69
|
+
execSync(`${getBrewBin()} install --cask docker`, { stdio: 'inherit', shell: true })
|
|
70
|
+
return { action: 'installed', method: 'brew-cask', hint: 'Start Docker Desktop from Applications' }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (platform === 'win32') {
|
|
74
|
+
execSync('wsl.exe -e bash -c "curl -fsSL https://get.docker.com | sh"', { stdio: 'inherit', shell: true })
|
|
75
|
+
return { action: 'installed', method: 'wsl-convenience-script', hint: 'Docker installed inside WSL2' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 升级 Docker
|
|
83
|
+
* @returns {{ action: string, method: string }}
|
|
84
|
+
*/
|
|
85
|
+
export function upgradeDocker() {
|
|
86
|
+
const platform = process.platform
|
|
87
|
+
|
|
88
|
+
if (platform === 'linux') {
|
|
89
|
+
try {
|
|
90
|
+
execSync('apt-get update && apt-get upgrade -y docker-ce', { stdio: 'inherit', shell: true })
|
|
91
|
+
return { action: 'upgraded', method: 'apt' }
|
|
92
|
+
} catch {
|
|
93
|
+
try {
|
|
94
|
+
execSync('dnf upgrade -y docker-ce', { stdio: 'inherit', shell: true })
|
|
95
|
+
return { action: 'upgraded', method: 'dnf' }
|
|
96
|
+
} catch {
|
|
97
|
+
runConvenienceScript(proxy)
|
|
98
|
+
return { action: 'upgraded', method: 'convenience-script' }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (platform === 'darwin') {
|
|
104
|
+
execSync(`${getBrewBin()} upgrade --cask docker`, { stdio: 'inherit', shell: true })
|
|
105
|
+
return { action: 'upgraded', method: 'brew-cask' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (platform === 'win32') {
|
|
109
|
+
execSync('wsl.exe -e bash -c "sudo apt-get update && sudo apt-get upgrade -y docker-ce"', { stdio: 'inherit', shell: true })
|
|
110
|
+
return { action: 'upgraded', method: 'wsl-apt' }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 卸载 Docker
|
|
118
|
+
* @returns {{ action: string, hint: string }}
|
|
119
|
+
*/
|
|
120
|
+
export function uninstallDocker() {
|
|
121
|
+
const platform = process.platform
|
|
122
|
+
|
|
123
|
+
if (platform === 'linux') {
|
|
124
|
+
try {
|
|
125
|
+
execSync('apt-get purge -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
|
|
126
|
+
} catch {
|
|
127
|
+
execSync('dnf remove -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
|
|
128
|
+
}
|
|
129
|
+
return { action: 'uninstalled', hint: 'Images/containers left in /var/lib/docker, remove manually if needed' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (platform === 'darwin') {
|
|
133
|
+
execSync(`${getBrewBin()} uninstall --cask docker`, { stdio: 'inherit', shell: true })
|
|
134
|
+
return { action: 'uninstalled', hint: 'Docker Desktop app removed' }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (platform === 'win32') {
|
|
138
|
+
return {
|
|
139
|
+
action: 'uninstall',
|
|
140
|
+
hint: 'Run in WSL: sudo apt-get purge -y docker-ce docker-ce-cli containerd.io',
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 获取 Docker 数据目录
|
|
149
|
+
* @returns {string}
|
|
150
|
+
*/
|
|
151
|
+
export function getDockerDir() {
|
|
152
|
+
const platform = process.platform
|
|
153
|
+
|
|
154
|
+
if (platform === 'linux') {
|
|
155
|
+
return '/var/lib/docker'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (platform === 'darwin') {
|
|
159
|
+
const home = process.env.HOME || ''
|
|
160
|
+
return `${home}/.docker`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (platform === 'win32') {
|
|
164
|
+
const localAppData = process.env.LOCALAPPDATA || ''
|
|
165
|
+
return localAppData ? `${localAppData}\\Docker` : ''
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return ''
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 获取 Docker 状态(docker info 摘要)
|
|
173
|
+
* @returns {{ installed: boolean, running?: boolean, info?: string }}
|
|
174
|
+
*/
|
|
175
|
+
export function getDockerStatus() {
|
|
176
|
+
if (!isDockerInstalled()) return { installed: false }
|
|
177
|
+
try {
|
|
178
|
+
const out = execSync('docker info --format "{{.ServerVersion}} {{.OperatingSystem}} {{.Containers}} {{.Images}}"', {
|
|
179
|
+
encoding: 'utf8',
|
|
180
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
181
|
+
})
|
|
182
|
+
const [serverVersion, operatingSystem, containers, images] = out.trim().split(' ')
|
|
183
|
+
return {
|
|
184
|
+
installed: true,
|
|
185
|
+
running: true,
|
|
186
|
+
serverVersion,
|
|
187
|
+
operatingSystem,
|
|
188
|
+
containers: parseInt(containers, 10) || 0,
|
|
189
|
+
images: parseInt(images, 10) || 0,
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
return { installed: true, running: false, hint: 'Docker daemon not running' }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 启动 Docker daemon
|
|
198
|
+
* @returns {{ action: string, method: string }}
|
|
199
|
+
*/
|
|
200
|
+
export function startDocker() {
|
|
201
|
+
const platform = process.platform
|
|
202
|
+
|
|
203
|
+
if (platform === 'linux') {
|
|
204
|
+
execSync('sudo systemctl start docker', { stdio: 'inherit', shell: true })
|
|
205
|
+
return { action: 'started', method: 'systemctl' }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (platform === 'darwin') {
|
|
209
|
+
execSync('open -a Docker', { stdio: 'inherit', shell: true })
|
|
210
|
+
return { action: 'started', method: 'docker-desktop' }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (platform === 'win32') {
|
|
214
|
+
execSync('wsl.exe -e sudo systemctl start docker', { stdio: 'inherit', shell: true })
|
|
215
|
+
return { action: 'started', method: 'wsl-systemctl' }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 停止 Docker daemon
|
|
223
|
+
* @returns {{ action: string, method: string }}
|
|
224
|
+
*/
|
|
225
|
+
export function stopDocker() {
|
|
226
|
+
const platform = process.platform
|
|
227
|
+
|
|
228
|
+
if (platform === 'linux') {
|
|
229
|
+
execSync('sudo systemctl stop docker', { stdio: 'inherit', shell: true })
|
|
230
|
+
return { action: 'stopped', method: 'systemctl' }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (platform === 'darwin') {
|
|
234
|
+
execSync('osascript -e \'quit app "Docker"\'', { stdio: 'inherit', shell: true })
|
|
235
|
+
return { action: 'stopped', method: 'docker-desktop' }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (platform === 'win32') {
|
|
239
|
+
execSync('wsl.exe -e sudo systemctl stop docker', { stdio: 'inherit', shell: true })
|
|
240
|
+
return { action: 'stopped', method: 'wsl-systemctl' }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 重启 Docker daemon
|
|
248
|
+
* @returns {{ action: string, method: string }}
|
|
249
|
+
*/
|
|
250
|
+
export function restartDocker() {
|
|
251
|
+
const platform = process.platform
|
|
252
|
+
|
|
253
|
+
if (platform === 'linux') {
|
|
254
|
+
execSync('sudo systemctl restart docker', { stdio: 'inherit', shell: true })
|
|
255
|
+
return { action: 'restarted', method: 'systemctl' }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (platform === 'darwin') {
|
|
259
|
+
execSync('osascript -e \'quit app "Docker"\' && sleep 1 && open -a Docker', { stdio: 'inherit', shell: true })
|
|
260
|
+
return { action: 'restarted', method: 'docker-desktop' }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (platform === 'win32') {
|
|
264
|
+
execSync('wsl.exe -e sudo systemctl restart docker', { stdio: 'inherit', shell: true })
|
|
265
|
+
return { action: 'restarted', method: 'wsl-systemctl' }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 打开终端并确保 docker 可用
|
|
273
|
+
* - Linux: 新开终端
|
|
274
|
+
* - macOS: 打开 Terminal.app
|
|
275
|
+
* - Windows: 打开 PowerShell → WSL2(docker 装在 WSL 内)
|
|
276
|
+
* @returns {{ action: string, method: string }}
|
|
277
|
+
*/
|
|
278
|
+
export function openDocker() {
|
|
279
|
+
const platform = process.platform
|
|
280
|
+
|
|
281
|
+
if (platform === 'linux') {
|
|
282
|
+
const shell = process.env.SHELL || 'bash'
|
|
283
|
+
spawnSync(shell, [], { stdio: 'inherit' })
|
|
284
|
+
return { action: 'opened', method: 'shell' }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (platform === 'darwin') {
|
|
288
|
+
execSync('open -a Terminal', { stdio: 'inherit', shell: true })
|
|
289
|
+
return { action: 'opened', method: 'terminal-app' }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (platform === 'win32') {
|
|
293
|
+
openWsl('')
|
|
294
|
+
return { action: 'opened', method: 'wsl-powershell' }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
298
|
+
}
|
package/src/managers/node.js
CHANGED
|
@@ -44,12 +44,18 @@ export function isNvmInstalled() {
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* 安装 nvm(git clone + 写 shell profile)
|
|
47
|
+
* @param {string} [proxy] - 代理地址
|
|
47
48
|
* @returns {{ action: string, profile: string, hint: string }}
|
|
48
49
|
*/
|
|
49
|
-
export function installNvm() {
|
|
50
|
+
export function installNvm(proxy) {
|
|
50
51
|
const mirror = getActiveRegistry('nvm')
|
|
51
52
|
const nvmDir = getNvmDir()
|
|
52
|
-
|
|
53
|
+
const env = { ...process.env }
|
|
54
|
+
if (proxy) {
|
|
55
|
+
env.http_proxy = proxy
|
|
56
|
+
env.https_proxy = proxy
|
|
57
|
+
}
|
|
58
|
+
execSync(`git clone "${mirror}" "${nvmDir}"`, { stdio: 'inherit', shell: shell.resolveBash(), env })
|
|
53
59
|
const lines = [
|
|
54
60
|
`export NVM_DIR="${nvmDir}"`,
|
|
55
61
|
`[ -s "\\$NVM_DIR/nvm.sh" ] && \\. "\\$NVM_DIR/nvm.sh"`,
|