dsh-plugin-container 0.0.0-stage → 0.3.0
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/LICENSE +21 -0
- package/README.md +80 -2
- package/cordis.patch.yml +3 -0
- package/docs/ai-chat.png +0 -0
- package/docs/ai-new-session.png +0 -0
- package/docs/apple-container.png +0 -0
- package/docs/attach.png +0 -0
- package/docs/docker-console.png +0 -0
- package/docs/docker-overview.png +0 -0
- package/docs/docker-volumes.png +0 -0
- package/docs/image-architecture.png +0 -0
- package/docs/package-install.png +0 -0
- package/docs/provider-switch.png +0 -0
- package/docs/run-container.png +0 -0
- package/index.js +138 -0
- package/lib/client.js +3560 -0
- package/package.json +62 -4
- package/src/client/api.js +98 -0
- package/src/client/attach.jsx +134 -0
- package/src/client/components.jsx +320 -0
- package/src/client/hooks.js +161 -0
- package/src/client/i18n.js +502 -0
- package/src/client/icons.jsx +227 -0
- package/src/client/index.jsx +76 -0
- package/src/client/page.jsx +782 -0
- package/src/client/run-container.jsx +187 -0
- package/src/client/runs.js +182 -0
- package/src/client/store.js +91 -0
- package/src/client/styles.js +371 -0
- package/src/client/tabs/compose.jsx +94 -0
- package/src/client/tabs/console.jsx +211 -0
- package/src/client/tabs/containers.jsx +148 -0
- package/src/client/tabs/images.jsx +127 -0
- package/src/client/tabs/networks.jsx +91 -0
- package/src/client/tabs/overview.jsx +266 -0
- package/src/client/tabs/volumes.jsx +76 -0
- package/src/host/apple-cli.js +307 -0
- package/src/host/apple.js +471 -0
- package/src/host/attach.js +246 -0
- package/src/host/commands.js +235 -0
- package/src/host/docker-cli.js +405 -0
- package/src/host/jobs.js +245 -0
- package/src/host/parsers.js +484 -0
- package/src/host/runtime.js +285 -0
- package/src/host/service.js +836 -0
- package/src/host/tools.js +198 -0
- package/src/host/util.js +97 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Docker runtime discovery and lifecycle actions.
|
|
3
|
+
*
|
|
4
|
+
* The runtime is inferred from the selected Docker context endpoint plus the
|
|
5
|
+
* local application/CLI inventory. Nothing here mutates Docker configuration:
|
|
6
|
+
* commands use `--context`, and colima/Docker Desktop actions are explicit
|
|
7
|
+
* user choices from the panel.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs'
|
|
10
|
+
import { homedir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
|
|
13
|
+
import { classifyEndpoint, parseColimaList, parseColimaStatus } from './parsers.js'
|
|
14
|
+
import { messageOf } from './util.js'
|
|
15
|
+
|
|
16
|
+
const DOCKER_APP_CANDIDATES = ['/Applications/Docker.app', join(homedir(), 'Applications/Docker.app')]
|
|
17
|
+
const ORB_APP_CANDIDATES = ['/Applications/OrbStack.app', join(homedir(), 'Applications/OrbStack.app')]
|
|
18
|
+
|
|
19
|
+
/** @param {string[]} paths @param {(path: string) => boolean} exists */
|
|
20
|
+
function firstExisting(paths, exists) {
|
|
21
|
+
return paths.find((path) => exists(path)) ?? null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {{
|
|
26
|
+
* cli: import('./docker-cli.js').DockerCli,
|
|
27
|
+
* platform?: NodeJS.Platform,
|
|
28
|
+
* env?: NodeJS.ProcessEnv,
|
|
29
|
+
* fs?: { existsSync: (path: string) => boolean },
|
|
30
|
+
* config?: Record<string, any>,
|
|
31
|
+
* }} options
|
|
32
|
+
*/
|
|
33
|
+
export function createRuntimeResolver(options) {
|
|
34
|
+
const cli = options.cli
|
|
35
|
+
const platform = options.platform ?? process.platform
|
|
36
|
+
const env = options.env ?? process.env
|
|
37
|
+
const fs = options.fs ?? { existsSync }
|
|
38
|
+
const config = options.config ?? {}
|
|
39
|
+
const controlEnabled = platform === 'darwin' && config.enableRuntimeControl !== false
|
|
40
|
+
|
|
41
|
+
/** @param {AbortSignal | undefined} signal */
|
|
42
|
+
async function inventory(signal) {
|
|
43
|
+
const dockerDesktop = firstExisting(DOCKER_APP_CANDIDATES, fs.existsSync)
|
|
44
|
+
const orbStack = firstExisting(ORB_APP_CANDIDATES, fs.existsSync)
|
|
45
|
+
const colimaPath = platform === 'darwin' ? await cli.ensureColima({ signal }) : null
|
|
46
|
+
let colimaInstances = []
|
|
47
|
+
let colimaError
|
|
48
|
+
if (colimaPath !== null) {
|
|
49
|
+
try {
|
|
50
|
+
const result = await cli.program(colimaPath, ['list', '--json'], { signal, timeoutMs: 15_000, maxBytes: 128 * 1024 })
|
|
51
|
+
colimaInstances = parseColimaList(result.stdout)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
colimaError = messageOf(error)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { dockerDesktop, orbStack, colimaPath, colimaInstances, colimaError }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {Array<{name:string,description:string,endpoint:string,current:boolean,error:string}>} contexts
|
|
61
|
+
* @param {string | null | undefined} requested
|
|
62
|
+
*/
|
|
63
|
+
function selectContext(contexts, requested) {
|
|
64
|
+
const current = contexts.find((context) => context.current) ?? null
|
|
65
|
+
const selected = (requested ? contexts.find((context) => context.name === requested) : undefined) ?? current ?? contexts[0] ?? null
|
|
66
|
+
return { current, selected, requested: requested ?? null }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {Awaited<ReturnType<typeof inventory>>} found
|
|
71
|
+
* @param {{name:string, endpoint:string} | null} context
|
|
72
|
+
* @param {{running?: boolean, error?: string}} daemon
|
|
73
|
+
*/
|
|
74
|
+
function describe(found, context, daemon = {}) {
|
|
75
|
+
const endpointClass = classifyEndpoint(context?.endpoint)
|
|
76
|
+
const daemonRunning = daemon.running === true
|
|
77
|
+
if (endpointClass.kind === 'colima') {
|
|
78
|
+
const profile = endpointClass.profile ?? 'default'
|
|
79
|
+
const instance =
|
|
80
|
+
found.colimaInstances.find((item) => item.name === profile) ??
|
|
81
|
+
found.colimaInstances.find((item) => (context?.name ?? '').endsWith(profile)) ??
|
|
82
|
+
null
|
|
83
|
+
const vmRunning = instance !== null ? /running/iu.test(instance.status) : daemonRunning
|
|
84
|
+
return {
|
|
85
|
+
kind: 'colima',
|
|
86
|
+
label: 'colima',
|
|
87
|
+
profile,
|
|
88
|
+
installed: found.colimaPath !== null,
|
|
89
|
+
running: daemonRunning,
|
|
90
|
+
vmRunning,
|
|
91
|
+
canStart: controlEnabled && found.colimaPath !== null && !daemonRunning,
|
|
92
|
+
canStop: controlEnabled && found.colimaPath !== null && (daemonRunning || vmRunning),
|
|
93
|
+
canRestart: controlEnabled && found.colimaPath !== null && (daemonRunning || vmRunning) && vmRunning,
|
|
94
|
+
endpoint: context?.endpoint ?? '',
|
|
95
|
+
details: instance,
|
|
96
|
+
error: found.colimaError ?? daemon.error ?? null,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (endpointClass.kind === 'orbstack') {
|
|
100
|
+
return {
|
|
101
|
+
kind: 'orbstack',
|
|
102
|
+
label: 'OrbStack',
|
|
103
|
+
profile: null,
|
|
104
|
+
installed: found.orbStack !== null,
|
|
105
|
+
running: daemonRunning,
|
|
106
|
+
vmRunning: daemonRunning,
|
|
107
|
+
canStart: controlEnabled && found.orbStack !== null && !daemonRunning,
|
|
108
|
+
canStop: controlEnabled && found.orbStack !== null && daemonRunning,
|
|
109
|
+
canRestart: false,
|
|
110
|
+
endpoint: context?.endpoint ?? '',
|
|
111
|
+
details: found.orbStack !== null ? { appPath: found.orbStack } : null,
|
|
112
|
+
error: daemon.error ?? null,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (endpointClass.kind === 'local-socket') {
|
|
116
|
+
if (found.dockerDesktop !== null) {
|
|
117
|
+
return {
|
|
118
|
+
kind: 'docker-desktop',
|
|
119
|
+
label: 'Docker Desktop',
|
|
120
|
+
profile: null,
|
|
121
|
+
installed: true,
|
|
122
|
+
running: daemonRunning,
|
|
123
|
+
vmRunning: daemonRunning,
|
|
124
|
+
canStart: controlEnabled && !daemonRunning,
|
|
125
|
+
canStop: controlEnabled && daemonRunning,
|
|
126
|
+
canRestart: false,
|
|
127
|
+
endpoint: context?.endpoint ?? '',
|
|
128
|
+
details: { appPath: found.dockerDesktop },
|
|
129
|
+
error: daemon.error ?? null,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
kind: 'unknown',
|
|
134
|
+
label: 'Docker',
|
|
135
|
+
profile: null,
|
|
136
|
+
installed: null,
|
|
137
|
+
running: daemonRunning,
|
|
138
|
+
vmRunning: daemonRunning,
|
|
139
|
+
canStart: false,
|
|
140
|
+
canStop: false,
|
|
141
|
+
canRestart: false,
|
|
142
|
+
endpoint: context?.endpoint ?? '',
|
|
143
|
+
details: null,
|
|
144
|
+
error: daemon.error ?? null,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (endpointClass.kind === 'remote') {
|
|
148
|
+
return {
|
|
149
|
+
kind: 'remote',
|
|
150
|
+
label: '远程 Docker',
|
|
151
|
+
profile: null,
|
|
152
|
+
installed: null,
|
|
153
|
+
running: daemonRunning,
|
|
154
|
+
vmRunning: daemonRunning,
|
|
155
|
+
canStart: false,
|
|
156
|
+
canStop: false,
|
|
157
|
+
canRestart: false,
|
|
158
|
+
endpoint: context?.endpoint ?? '',
|
|
159
|
+
details: null,
|
|
160
|
+
error: daemon.error ?? null,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
kind: 'unknown',
|
|
165
|
+
label: 'Docker',
|
|
166
|
+
profile: null,
|
|
167
|
+
installed: null,
|
|
168
|
+
running: daemonRunning,
|
|
169
|
+
vmRunning: daemonRunning,
|
|
170
|
+
canStart: false,
|
|
171
|
+
canStop: false,
|
|
172
|
+
canRestart: false,
|
|
173
|
+
endpoint: context?.endpoint ?? '',
|
|
174
|
+
details: null,
|
|
175
|
+
error: daemon.error ?? null,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {Array<{name:string,description:string,endpoint:string,current:boolean,error:string}>} contexts
|
|
181
|
+
* @param {string | null | undefined} requested
|
|
182
|
+
* @param {{running?: boolean, error?: string}} daemon
|
|
183
|
+
* @param {AbortSignal | undefined} signal
|
|
184
|
+
*/
|
|
185
|
+
async function resolve(contexts, requested, daemon, signal) {
|
|
186
|
+
const found = await inventory(signal)
|
|
187
|
+
const selected = selectContext(contexts, requested)
|
|
188
|
+
const runtime = describe(found, selected.selected, daemon)
|
|
189
|
+
if (runtime.kind === 'colima' && runtime.profile !== null && runtime.installed) {
|
|
190
|
+
try {
|
|
191
|
+
const result = await cli.program(found.colimaPath, ['status', '--json', '--profile', runtime.profile], {
|
|
192
|
+
signal,
|
|
193
|
+
timeoutMs: 15_000,
|
|
194
|
+
maxBytes: 256 * 1024,
|
|
195
|
+
})
|
|
196
|
+
const status = parseColimaStatus(result.stdout)
|
|
197
|
+
if (status !== null) {
|
|
198
|
+
runtime.details = { ...(runtime.details ?? {}), ...status }
|
|
199
|
+
runtime.vmRunning = true
|
|
200
|
+
runtime.canStop = controlEnabled
|
|
201
|
+
runtime.canRestart = controlEnabled
|
|
202
|
+
runtime.cpus = status.cpus
|
|
203
|
+
runtime.memory = status.memory
|
|
204
|
+
runtime.disk = status.disk
|
|
205
|
+
runtime.driver = status.driver
|
|
206
|
+
runtime.arch = status.arch
|
|
207
|
+
runtime.mountType = status.mountType
|
|
208
|
+
runtime.dockerSocket = status.dockerSocket
|
|
209
|
+
runtime.kubernetes = status.kubernetes
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
// Stopped colima instances cannot report status; the list already
|
|
213
|
+
// carries the state, so this is not an error worth surfacing.
|
|
214
|
+
if (runtime.vmRunning) runtime.error = runtime.error ?? messageOf(error)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { ...selected, runtime }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Build one explicit runtime action. The caller decides confirmation and
|
|
222
|
+
* whether to run it as a job.
|
|
223
|
+
* @param {string} action
|
|
224
|
+
* @param {ReturnType<typeof describe>} runtime
|
|
225
|
+
*/
|
|
226
|
+
function actionSpec(action, runtime) {
|
|
227
|
+
if (!controlEnabled) throw Object.assign(new Error('当前平台或配置不允许控制 Docker 运行时'), { code: 'docker/unsupported' })
|
|
228
|
+
if (action !== 'start' && action !== 'stop' && action !== 'restart') {
|
|
229
|
+
throw Object.assign(new Error(`不支持的运行时操作: ${action}`), { code: 'docker/unsupported' })
|
|
230
|
+
}
|
|
231
|
+
if (runtime.kind === 'colima') {
|
|
232
|
+
if (!runtime.installed) throw Object.assign(new Error('未找到 colima CLI'), { code: 'docker/cli-missing' })
|
|
233
|
+
if (action === 'restart' && !runtime.canRestart) {
|
|
234
|
+
throw Object.assign(new Error('当前 colima 未运行,无需重启'), { code: 'docker/unsupported' })
|
|
235
|
+
}
|
|
236
|
+
const colimaPath = cli.colimaPath
|
|
237
|
+
if (colimaPath === undefined || colimaPath === null) throw Object.assign(new Error('未找到 colima CLI'), { code: 'docker/cli-missing' })
|
|
238
|
+
const profile = runtime.profile ?? 'default'
|
|
239
|
+
return {
|
|
240
|
+
argv: [colimaPath, action, '--profile', profile],
|
|
241
|
+
title: `colima ${action}`,
|
|
242
|
+
displayCommand: `colima ${action} --profile ${profile}`,
|
|
243
|
+
timeoutMs: 10 * 60_000,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (runtime.kind === 'docker-desktop') {
|
|
247
|
+
if (action === 'restart') throw Object.assign(new Error('Docker Desktop 暂不支持一键重启,请先停止再启动'), { code: 'docker/unsupported' })
|
|
248
|
+
if (action === 'start') {
|
|
249
|
+
const app = runtime.details?.appPath ?? 'Docker'
|
|
250
|
+
return {
|
|
251
|
+
argv: ['/usr/bin/open', '-a', app],
|
|
252
|
+
title: '启动 Docker Desktop',
|
|
253
|
+
displayCommand: `open -a ${app}`,
|
|
254
|
+
timeoutMs: 30_000,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
argv: ['/usr/bin/osascript', '-e', 'tell application id "com.docker.docker" to quit'],
|
|
259
|
+
title: '停止 Docker Desktop',
|
|
260
|
+
displayCommand: 'osascript -e \'tell application id "com.docker.docker" to quit\'',
|
|
261
|
+
timeoutMs: 120_000,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (runtime.kind === 'orbstack') {
|
|
265
|
+
if (action === 'restart') throw Object.assign(new Error('OrbStack 暂不支持一键重启'), { code: 'docker/unsupported' })
|
|
266
|
+
const app = runtime.details?.appPath ?? 'OrbStack'
|
|
267
|
+
return action === 'start'
|
|
268
|
+
? {
|
|
269
|
+
argv: ['/usr/bin/open', '-a', app],
|
|
270
|
+
title: '启动 OrbStack',
|
|
271
|
+
displayCommand: `open -a ${app}`,
|
|
272
|
+
timeoutMs: 30_000,
|
|
273
|
+
}
|
|
274
|
+
: {
|
|
275
|
+
argv: ['/usr/bin/osascript', '-e', 'tell application "OrbStack" to quit'],
|
|
276
|
+
title: '停止 OrbStack',
|
|
277
|
+
displayCommand: 'osascript -e \'tell application "OrbStack" to quit\'',
|
|
278
|
+
timeoutMs: 120_000,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
throw Object.assign(new Error('当前 Docker 上下文没有可本机控制的运行时'), { code: 'docker/unsupported' })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { inventory, selectContext, resolve, actionSpec, controlEnabled, platform, env }
|
|
285
|
+
}
|