dsh-plugin-container 0.5.0 → 0.5.1

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 CHANGED
@@ -90,7 +90,7 @@ npm publish --access public --provenance
90
90
  ```bash
91
91
  npm install
92
92
  npm run build # src/client -> lib/client.js
93
- npm test # 53 tests
93
+ npm test # 55 tests
94
94
  npm pack # pre-release check
95
95
  ```
96
96
 
package/README.zh-CN.md CHANGED
@@ -90,7 +90,7 @@ npm publish --access public --provenance
90
90
  ```bash
91
91
  npm install
92
92
  npm run build # src/client -> lib/client.js
93
- npm test # 53 tests
93
+ npm test # 55 tests
94
94
  npm pack # 发布前检查
95
95
  ```
96
96
 
package/lib/client.js CHANGED
@@ -1284,7 +1284,7 @@ function BuildImageDialog({ open, t, api, provider, defaultDir = "", pickDirecto
1284
1284
  if (id !== requestId.current) return;
1285
1285
  setInfo(value);
1286
1286
  setFile((current) => value.dockerfiles.includes(current) ? current : value.dockerfiles[0] ?? "");
1287
- if (value.suggestedTag !== "") setTag((current) => tagEdited || current !== "" ? current : value.suggestedTag);
1287
+ if (value.suggestedTag !== "" && !tagEdited) setTag(value.suggestedTag);
1288
1288
  } catch {
1289
1289
  if (id === requestId.current) setInfo(null);
1290
1290
  }
@@ -1293,9 +1293,9 @@ function BuildImageDialog({ open, t, api, provider, defaultDir = "", pickDirecto
1293
1293
  }, [api, dir, open, tagEdited]);
1294
1294
  const args = (0, import_react4.useMemo)(() => buildArgs.split(/\n/u).map((line) => line.trim()).filter(Boolean), [buildArgs]);
1295
1295
  const tagValid = IMAGE_REF.test(tag.trim());
1296
- const dirOk = info !== null && info.isDirectory && info.dockerfiles.length > 0;
1297
1296
  const trimmedDir = dir.trim();
1298
1297
  const dirChecked = info !== null && info.path === trimmedDir;
1298
+ const dirOk = dirChecked && info.isDirectory && info.dockerfiles.length > 0;
1299
1299
  let dirLine = null;
1300
1300
  if (trimmedDir !== "" && !dirChecked) dirLine = { tone: "hint", text: t("imgBuildDirChecking") };
1301
1301
  else if (dirChecked && !info.exists) dirLine = { tone: "err", text: t("imgBuildDirMissing") };
@@ -1356,7 +1356,7 @@ ${parts.join(" ")}`;
1356
1356
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Button, { size: "sm", variant: "primary", onClick: submit, disabled: busy || !dirOk || !tagValid, children: busy ? t("imgBuilding") : t("imgBuildStart") })
1357
1357
  ] }),
1358
1358
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { className: "dsh-docker-run-form", onSubmit: submit, children: [
1359
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dsh-docker-field", children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-docker-field", children: [
1360
1360
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t("imgBuildDir") }),
1361
1361
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-docker-image-picker", children: [
1362
1362
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-container",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "DSH sidebar container panel for macOS Docker runtimes and Apple container",
6
6
  "type": "module",
@@ -61,7 +61,8 @@ export default function BuildImageDialog({ open, t, api, provider, defaultDir =
61
61
  if (id !== requestId.current) return
62
62
  setInfo(value)
63
63
  setFile((current) => (value.dockerfiles.includes(current) ? current : (value.dockerfiles[0] ?? '')))
64
- if (value.suggestedTag !== '') setTag((current) => (tagEdited || current !== '' ? current : value.suggestedTag))
64
+ // Follow the folder until the user types their own tag.
65
+ if (value.suggestedTag !== '' && !tagEdited) setTag(value.suggestedTag)
65
66
  } catch {
66
67
  if (id === requestId.current) setInfo(null)
67
68
  }
@@ -71,9 +72,9 @@ export default function BuildImageDialog({ open, t, api, provider, defaultDir =
71
72
 
72
73
  const args = useMemo(() => buildArgs.split(/\n/u).map((line) => line.trim()).filter(Boolean), [buildArgs])
73
74
  const tagValid = IMAGE_REF.test(tag.trim())
74
- const dirOk = info !== null && info.isDirectory && info.dockerfiles.length > 0
75
75
  const trimmedDir = dir.trim()
76
76
  const dirChecked = info !== null && info.path === trimmedDir
77
+ const dirOk = dirChecked && info.isDirectory && info.dockerfiles.length > 0
77
78
 
78
79
  let dirLine = null
79
80
  if (trimmedDir !== '' && !dirChecked) dirLine = { tone: 'hint', text: t('imgBuildDirChecking') }
@@ -141,7 +142,8 @@ export default function BuildImageDialog({ open, t, api, provider, defaultDir =
141
142
  }
142
143
  >
143
144
  <form className="dsh-docker-run-form" onSubmit={submit}>
144
- <label className="dsh-docker-field">
145
+ {/* A div, not a label: clicking the caption should not open the folder picker. */}
146
+ <div className="dsh-docker-field">
145
147
  <span>{t('imgBuildDir')}</span>
146
148
  <div className="dsh-docker-image-picker">
147
149
  <input
@@ -160,7 +162,7 @@ export default function BuildImageDialog({ open, t, api, provider, defaultDir =
160
162
  {dirLine === null || dirLine.tone === 'hint'
161
163
  ? <span className="dsh-docker-hint">{dirLine?.text ?? t('imgBuildDirHint')}</span>
162
164
  : <span className={dirLine.tone === 'ok' ? 'dsh-docker-line-ok' : 'dsh-docker-line-err'}>{dirLine.text}</span>}
163
- </label>
165
+ </div>
164
166
  {dirOk && info.dockerfiles.length > 1 && (
165
167
  <label className="dsh-docker-field">
166
168
  <span>{t('imgBuildFile')}</span>
package/src/host/apple.js CHANGED
@@ -397,7 +397,7 @@ export class AppleProvider {
397
397
  if (kind === 'image') {
398
398
  if (action === 'pull') {
399
399
  const platform = platformOf(payload.platform)
400
- return this.run({ argv: ['image', 'pull', ...(platform === undefined ? [] : ['--platform', platform]), safeImageRef(payload.id ?? payload.reference)], cwd: payload.cwd, title: payload.title ?? 'container image pull' }, signal)
400
+ return this.run({ argv: ['image', 'pull', ...(platform === undefined ? [] : ['--platform', platform]), safeImageRef(payload.id ?? payload.reference)], cwd: payload.cwd, timeoutMs: LONG_JOB_MS, title: payload.title ?? 'container image pull' }, signal)
401
401
  }
402
402
  if (action === 'build') {
403
403
  const spec = buildSpec(payload)
@@ -77,8 +77,10 @@ function statOrNull(value) {
77
77
  export function buildSpec(payload) {
78
78
  const tag = imageRef(payload.tag, 'tag')
79
79
  assertLowercaseRepository(tag)
80
- const contextDir = requireString(payload.contextDir, 'contextDir', { max: 4096 })
81
- if (!isAbsolute(contextDir) || contextDir.includes('\0')) throw new CommandError('构建目录必须是绝对路径')
80
+ const rawDir = requireString(payload.contextDir, 'contextDir', { max: 4096 })
81
+ if (!isAbsolute(rawDir) || rawDir.includes('\0')) throw new CommandError('构建目录必须是绝对路径')
82
+ // Normalise so `/a/b/` and `/a/b/./` compare equal to the resolved Dockerfile path below.
83
+ const contextDir = resolve(rawDir)
82
84
  const dirStat = statOrNull(contextDir)
83
85
  if (dirStat === null) throw new CommandError(`构建目录不存在: ${contextDir}`)
84
86
  if (!dirStat.isDirectory()) throw new CommandError(`不是目录: ${contextDir}`)
@@ -87,7 +89,8 @@ export function buildSpec(payload) {
87
89
  if (payload.file !== undefined && payload.file !== null && String(payload.file).trim() !== '') {
88
90
  const name = requireString(String(payload.file).trim(), 'file', { max: 1024 })
89
91
  const resolved = resolve(contextDir, name)
90
- if (resolved !== contextDir && !resolved.startsWith(`${contextDir}${sep}`)) throw new CommandError('Dockerfile 必须位于构建目录内')
92
+ const prefix = contextDir.endsWith(sep) ? contextDir : `${contextDir}${sep}`
93
+ if (resolved !== contextDir && !resolved.startsWith(prefix)) throw new CommandError('Dockerfile 必须位于构建目录内')
91
94
  const fileStat = statOrNull(resolved)
92
95
  if (fileStat === null || !fileStat.isFile()) throw new CommandError(`找不到 Dockerfile: ${name}`)
93
96
  file = resolved
@@ -7,7 +7,7 @@
7
7
  * records live in this plugin's memory and disappear on unload.
8
8
  */
9
9
  import { homedir } from 'node:os'
10
- import { statSync } from 'node:fs'
10
+ import { readFileSync, statSync } from 'node:fs'
11
11
 
12
12
  import {
13
13
  CommandError,
@@ -42,6 +42,15 @@ import {
42
42
  import { createRuntimeResolver } from './runtime.js'
43
43
  import { codeOf, isRecord, messageOf, optionalInteger, requireString } from './util.js'
44
44
 
45
+ /** Reported by `ping`; read once so it always matches the published package. */
46
+ const PLUGIN_VERSION = (() => {
47
+ try {
48
+ return String(JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version)
49
+ } catch {
50
+ return 'unknown'
51
+ }
52
+ })()
53
+
45
54
  const STATUS_CACHE_MS = 1500
46
55
  const CONTEXT_CACHE_MS = 10_000
47
56
  const VALUE_CACHE_MS = 1500
@@ -190,7 +199,7 @@ export class DockerService {
190
199
  return ok({
191
200
  ok: true,
192
201
  plugin: 'dsh-plugin-container',
193
- version: '0.2.0',
202
+ version: PLUGIN_VERSION,
194
203
  dockerPath: this.cli.dockerPath ?? null,
195
204
  containerPath: this.apple.cli.containerPath ?? null,
196
205
  })
@@ -776,6 +785,13 @@ export class DockerService {
776
785
  if (action === 'pull') {
777
786
  const platform = platformOf(payload.platform)
778
787
  argv = ['pull', ...(platform === undefined ? [] : ['--platform', platform]), id]
788
+ return this.run({
789
+ argv,
790
+ context: payload.context,
791
+ cwd: payload.cwd,
792
+ timeoutMs: LONG_JOB_MS,
793
+ title: payload.title ?? `拉取镜像 ${id}`,
794
+ }, signal)
779
795
  }
780
796
  else if (action === 'delete') argv = ['rmi', id]
781
797
  } else if (kind === 'volume' && action === 'delete') {