@exagone313/dsh-podman 0.2.0-rc.4 → 0.2.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/README.md +12 -0
- package/README.zh.md +14 -2
- package/cordis.patch.yml +58 -0
- package/dist/approval-reasons.d.ts +3 -4
- package/dist/approval-reasons.js +40 -49
- package/dist/approval-reasons.test.js +43 -15
- package/dist/approval.js +27 -10
- package/dist/approval.test.js +165 -34
- package/dist/card-route.d.ts +3 -2
- package/dist/card-route.js +103 -35
- package/dist/card-route.test.js +60 -43
- package/dist/card-test-support.js +39 -66
- package/dist/client/ContainerCard.d.ts +1 -1
- package/dist/client/ContainerCard.js +76 -70
- package/dist/client/card-protocol.d.ts +3 -3
- package/dist/client/container-card-caches.js +11 -6
- package/dist/client/container-card-controller.d.ts +13 -18
- package/dist/client/container-card-controller.js +86 -40
- package/dist/client/container-card-create-modal.d.ts +2 -2
- package/dist/client/container-card-create-modal.js +13 -5
- package/dist/client/container-card-default-env.d.ts +10 -0
- package/dist/client/container-card-default-env.js +69 -0
- package/dist/client/container-card-directory.d.ts +2 -2
- package/dist/client/container-card-directory.js +12 -4
- package/dist/client/container-card-editors.d.ts +3 -3
- package/dist/client/container-card-editors.js +104 -32
- package/dist/client/container-card-images.d.ts +4 -4
- package/dist/client/container-card-images.js +19 -6
- package/dist/client/container-card-paths.d.ts +3 -3
- package/dist/client/container-card-paths.js +18 -11
- package/dist/client/container-card-row.d.ts +10 -6
- package/dist/client/container-card-row.js +82 -13
- package/dist/client/container-card-secrets.d.ts +3 -3
- package/dist/client/container-card-secrets.js +8 -6
- package/dist/client/container-card-shared.d.ts +4 -17
- package/dist/client/container-card-shared.js +9 -25
- package/dist/client/container-card-styles.d.ts +2 -8
- package/dist/client/container-card-styles.js +14 -52
- package/dist/client/container-card-volumes.d.ts +2 -2
- package/dist/client/container-card-volumes.js +9 -5
- package/dist/client/container-card-workspace.d.ts +8 -6
- package/dist/client/container-card-workspace.js +9 -3
- package/dist/client/directory-picker.js +2 -1
- package/dist/client/index.js +13109 -2117
- package/dist/client/locales.d.ts +6 -1
- package/dist/client/locales.js +88 -38
- package/dist/client/podman-terminal-guide.d.ts +11 -0
- package/dist/client/podman-terminal-guide.js +208 -0
- package/dist/client/podman-terminal-title.d.ts +7 -0
- package/dist/client/podman-terminal-title.js +30 -0
- package/dist/client/podman-terminal.d.ts +26 -0
- package/dist/client/podman-terminal.js +412 -0
- package/dist/client/read-only-approval.d.ts +2 -2
- package/dist/client/read-only-approval.js +13 -3
- package/dist/client/slot-contract.d.ts +0 -11
- package/dist/client/terminal-preference.d.ts +26 -0
- package/dist/client/terminal-preference.js +67 -0
- package/dist/client/terminal-protocol.d.ts +85 -0
- package/dist/client/terminal-protocol.js +14 -0
- package/dist/client/terminal-tab.d.ts +60 -0
- package/dist/client/terminal-tab.js +35 -0
- package/dist/client/terminal-targets.d.ts +33 -0
- package/dist/client/terminal-targets.js +65 -0
- package/dist/client/terminal-titles.d.ts +23 -0
- package/dist/client/terminal-titles.js +52 -0
- package/dist/client/terminal-transport.d.ts +63 -0
- package/dist/client/terminal-transport.js +207 -0
- package/dist/client/tool-views.js +215 -49
- package/dist/container-env.d.ts +11 -0
- package/dist/container-env.js +96 -0
- package/dist/container-env.test.d.ts +1 -0
- package/dist/container-env.test.js +93 -0
- package/dist/containers.test.js +196 -18
- package/dist/daemons.test.js +18 -4
- package/dist/dsh-version.js +2 -1
- package/dist/env-rows.d.ts +14 -0
- package/dist/env-rows.js +52 -0
- package/dist/env-rows.test.d.ts +1 -0
- package/dist/env-rows.test.js +43 -0
- package/dist/fs-provider.d.ts +1 -0
- package/dist/fs-provider.js +26 -12
- package/dist/fs-provider.test.js +51 -10
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +2 -2
- package/dist/grpc/proto/dshctl/v1/control.proto +8 -1
- package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
- package/dist/grpc/runtime-client.d.ts +3 -1
- package/dist/grpc/runtime-client.js +90 -17
- package/dist/guest-rpc.d.ts +15 -19
- package/dist/guest-rpc.js +289 -70
- package/dist/guest-rpc.test.d.ts +1 -0
- package/dist/guest-rpc.test.js +241 -0
- package/dist/guest-terminal.d.ts +30 -0
- package/dist/guest-terminal.js +196 -0
- package/dist/images.test.js +10 -3
- package/dist/index.d.ts +11 -16
- package/dist/index.js +42 -36
- package/dist/locales.test.d.ts +1 -0
- package/dist/locales.test.js +34 -0
- package/dist/misc.test.js +38 -10
- package/dist/mount-enums.js +2 -1
- package/dist/mount-input.d.ts +2 -0
- package/dist/mount-input.js +38 -22
- package/dist/mounts.test.js +81 -11
- package/dist/naming.test.d.ts +1 -0
- package/dist/naming.test.js +28 -0
- package/dist/output-reader.js +36 -4
- package/dist/package-deps.test.d.ts +1 -0
- package/dist/package-deps.test.js +49 -0
- package/dist/paths.test.js +4 -2
- package/dist/plugin-meta.test.d.ts +1 -0
- package/dist/plugin-meta.test.js +29 -0
- package/dist/project-path.js +33 -7
- package/dist/project-path.test.js +14 -0
- package/dist/prompts.d.ts +0 -4
- package/dist/prompts.js +15 -84
- package/dist/read-only-shell.js +3 -1
- package/dist/read-only-shell.test.js +80 -25
- package/dist/runtime-client.test.d.ts +1 -0
- package/dist/runtime-client.test.js +53 -0
- package/dist/secrets.test.js +21 -6
- package/dist/settings-commands.test.js +61 -14
- package/dist/settings-create.test.js +32 -10
- package/dist/settings-default-env.test.d.ts +1 -0
- package/dist/settings-default-env.test.js +175 -0
- package/dist/settings-mounts.test.js +46 -7
- package/dist/settings-schema.d.ts +6 -11
- package/dist/settings-schema.js +4 -8
- package/dist/settings-schema.test.d.ts +1 -0
- package/dist/settings-schema.test.js +35 -0
- package/dist/settings-workspaces.test.js +36 -63
- package/dist/spill-store.d.ts +1 -0
- package/dist/spill-store.js +17 -4
- package/dist/spill-store.test.js +31 -8
- package/dist/subprocess.d.ts +4 -0
- package/dist/subprocess.js +148 -162
- package/dist/subprocess.test.js +118 -8
- package/dist/terminal-preference.test.d.ts +1 -0
- package/dist/terminal-preference.test.js +62 -0
- package/dist/terminal-route.d.ts +10 -0
- package/dist/terminal-route.js +270 -0
- package/dist/terminal-route.test.d.ts +1 -0
- package/dist/terminal-route.test.js +265 -0
- package/dist/terminal-sessions.d.ts +69 -0
- package/dist/terminal-sessions.js +252 -0
- package/dist/terminal-shells.d.ts +22 -0
- package/dist/terminal-shells.js +99 -0
- package/dist/terminal-tab.test.d.ts +1 -0
- package/dist/terminal-tab.test.js +58 -0
- package/dist/terminal-targets.test.d.ts +1 -0
- package/dist/terminal-targets.test.js +43 -0
- package/dist/terminal-titles.test.d.ts +1 -0
- package/dist/terminal-titles.test.js +36 -0
- package/dist/test-support.d.ts +35 -5
- package/dist/test-support.js +91 -29
- package/dist/tool-defs.js +19 -4
- package/dist/tool-handlers.js +80 -28
- package/dist/tool-params.js +19 -5
- package/dist/volumes.test.js +1 -1
- package/dist/workspace-binding.d.ts +18 -9
- package/dist/workspace-binding.js +244 -77
- package/dist/workspace-binding.test.js +189 -25
- package/docs/architecture.md +62 -21
- package/docs/architecture.zh.md +57 -13
- package/docs/configuration.md +61 -20
- package/docs/configuration.zh.md +53 -19
- package/docs/development-prompt.md +72 -0
- package/docs/development-prompt.zh.md +69 -0
- package/docs/development.md +141 -21
- package/docs/development.zh.md +125 -17
- package/docs/install-dsh-and-dsh-podman.md +21 -8
- package/docs/install-dsh-and-dsh-podman.zh.md +18 -8
- package/docs/uninstall.md +83 -0
- package/docs/uninstall.zh.md +79 -0
- package/docs/update.md +120 -0
- package/docs/update.zh.md +114 -0
- package/docs/usage.md +108 -61
- package/docs/usage.zh.md +76 -38
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -25
- package/quadlet/dsh-podman-orchestrator.container +46 -0
- package/quadlet/dsh.container +35 -0
- package/LICENSE.pkg +0 -17103
- package/dist/preferences.d.ts +0 -6
- package/dist/preferences.js +0 -27
|
@@ -3,46 +3,194 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
//
|
|
4
4
|
// SPDX-License-Identifier: MIT
|
|
5
5
|
import { useState } from "react";
|
|
6
|
-
import { DisclosureRow,
|
|
6
|
+
import { DisclosureRow, IconApiOutlineRegular, IconArchiveOutlineRegular, IconBrowseOutlineRegular, IconDataOutlineRegular, IconEditOutlineRegular, IconFolderOpenOutlineRegular, IconLinkOutlineRegular, IconPlayOutlineRegular, IconPlusOutlineRegular, IconRefreshOutlineRegular, IconSearchOutlineRegular, IconSparkleRegular, IconStopFillRegular, IconTrashOutlineRegular, StateDot, TerminalBlock, } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
7
7
|
import { TERMINAL_CLASS } from "./terminal-styles.js";
|
|
8
8
|
const TOOL_PRESENTATION = {
|
|
9
|
-
container_bash: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
9
|
+
container_bash: {
|
|
10
|
+
titleKey: "toolTitle_container_bash",
|
|
11
|
+
icon: _jsx(IconApiOutlineRegular, { size: 14 }),
|
|
12
|
+
summaryKeys: ["description", "command"],
|
|
13
|
+
},
|
|
14
|
+
container_exec: {
|
|
15
|
+
titleKey: "toolTitle_container_exec",
|
|
16
|
+
icon: _jsx(IconApiOutlineRegular, { size: 14 }),
|
|
17
|
+
summaryKeys: ["description", "argv"],
|
|
18
|
+
},
|
|
19
|
+
container_read: {
|
|
20
|
+
titleKey: "toolTitle_container_read",
|
|
21
|
+
icon: _jsx(IconBrowseOutlineRegular, { size: 14 }),
|
|
22
|
+
summaryKeys: ["file_path"],
|
|
23
|
+
},
|
|
24
|
+
container_write: {
|
|
25
|
+
titleKey: "toolTitle_container_write",
|
|
26
|
+
icon: _jsx(IconEditOutlineRegular, { size: 14 }),
|
|
27
|
+
summaryKeys: ["file_path"],
|
|
28
|
+
},
|
|
29
|
+
container_edit: {
|
|
30
|
+
titleKey: "toolTitle_container_edit",
|
|
31
|
+
icon: _jsx(IconEditOutlineRegular, { size: 14 }),
|
|
32
|
+
summaryKeys: ["file_path"],
|
|
33
|
+
},
|
|
34
|
+
container_glob: {
|
|
35
|
+
titleKey: "toolTitle_container_glob",
|
|
36
|
+
icon: _jsx(IconSearchOutlineRegular, { size: 14 }),
|
|
37
|
+
summaryKeys: ["pattern"],
|
|
38
|
+
},
|
|
39
|
+
container_grep: {
|
|
40
|
+
titleKey: "toolTitle_container_grep",
|
|
41
|
+
icon: _jsx(IconSearchOutlineRegular, { size: 14 }),
|
|
42
|
+
summaryKeys: ["pattern"],
|
|
43
|
+
},
|
|
44
|
+
container_list: {
|
|
45
|
+
titleKey: "toolTitle_container_list",
|
|
46
|
+
icon: _jsx(IconDataOutlineRegular, { size: 14 }),
|
|
47
|
+
summaryKeys: [],
|
|
48
|
+
},
|
|
49
|
+
container_start: {
|
|
50
|
+
titleKey: "toolTitle_container_start",
|
|
51
|
+
icon: _jsx(IconPlayOutlineRegular, { size: 14 }),
|
|
52
|
+
summaryKeys: ["container", "image"],
|
|
53
|
+
},
|
|
54
|
+
container_recreate: {
|
|
55
|
+
titleKey: "toolTitle_container_recreate",
|
|
56
|
+
icon: _jsx(IconRefreshOutlineRegular, { size: 14 }),
|
|
57
|
+
summaryKeys: ["container", "image"],
|
|
58
|
+
},
|
|
59
|
+
container_remove: {
|
|
60
|
+
titleKey: "toolTitle_container_remove",
|
|
61
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
62
|
+
summaryKeys: ["container"],
|
|
63
|
+
},
|
|
64
|
+
container_mount_list: {
|
|
65
|
+
titleKey: "toolTitle_container_mount_list",
|
|
66
|
+
icon: _jsx(IconFolderOpenOutlineRegular, { size: 14 }),
|
|
67
|
+
summaryKeys: ["container"],
|
|
68
|
+
},
|
|
69
|
+
container_mount_add: {
|
|
70
|
+
titleKey: "toolTitle_container_mount_add",
|
|
71
|
+
icon: _jsx(IconFolderOpenOutlineRegular, { size: 14 }),
|
|
72
|
+
summaryKeys: ["container", "kind"],
|
|
73
|
+
},
|
|
74
|
+
container_mount_remove: {
|
|
75
|
+
titleKey: "toolTitle_container_mount_remove",
|
|
76
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
77
|
+
summaryKeys: ["container"],
|
|
78
|
+
},
|
|
79
|
+
container_mount_update: {
|
|
80
|
+
titleKey: "toolTitle_container_mount_update",
|
|
81
|
+
icon: _jsx(IconRefreshOutlineRegular, { size: 14 }),
|
|
82
|
+
summaryKeys: ["container", "mode"],
|
|
83
|
+
},
|
|
84
|
+
container_path_set: {
|
|
85
|
+
titleKey: "toolTitle_container_path_set",
|
|
86
|
+
icon: _jsx(IconEditOutlineRegular, { size: 14 }),
|
|
87
|
+
summaryKeys: ["container"],
|
|
88
|
+
},
|
|
89
|
+
container_path_add: {
|
|
90
|
+
titleKey: "toolTitle_container_path_add",
|
|
91
|
+
icon: _jsx(IconPlusOutlineRegular, { size: 14 }),
|
|
92
|
+
summaryKeys: ["container", "path"],
|
|
93
|
+
},
|
|
94
|
+
container_path_remove: {
|
|
95
|
+
titleKey: "toolTitle_container_path_remove",
|
|
96
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
97
|
+
summaryKeys: ["container", "path"],
|
|
98
|
+
},
|
|
99
|
+
container_secret_add: {
|
|
100
|
+
titleKey: "toolTitle_container_secret_add",
|
|
101
|
+
icon: _jsx(IconLinkOutlineRegular, { size: 14 }),
|
|
102
|
+
summaryKeys: ["container", "env"],
|
|
103
|
+
},
|
|
104
|
+
container_secret_remove: {
|
|
105
|
+
titleKey: "toolTitle_container_secret_remove",
|
|
106
|
+
icon: _jsx(IconLinkOutlineRegular, { size: 14 }),
|
|
107
|
+
summaryKeys: ["container", "env"],
|
|
108
|
+
},
|
|
109
|
+
image_list: {
|
|
110
|
+
titleKey: "toolTitle_image_list",
|
|
111
|
+
icon: _jsx(IconArchiveOutlineRegular, { size: 14 }),
|
|
112
|
+
summaryKeys: [],
|
|
113
|
+
},
|
|
114
|
+
image_get: {
|
|
115
|
+
titleKey: "toolTitle_image_get",
|
|
116
|
+
icon: _jsx(IconArchiveOutlineRegular, { size: 14 }),
|
|
117
|
+
summaryKeys: ["imageId"],
|
|
118
|
+
},
|
|
119
|
+
image_build: {
|
|
120
|
+
titleKey: "toolTitle_image_build",
|
|
121
|
+
icon: _jsx(IconPlusOutlineRegular, { size: 14 }),
|
|
122
|
+
summaryKeys: ["imageId", "parent"],
|
|
123
|
+
},
|
|
124
|
+
image_rebuild: {
|
|
125
|
+
titleKey: "toolTitle_image_rebuild",
|
|
126
|
+
icon: _jsx(IconRefreshOutlineRegular, { size: 14 }),
|
|
127
|
+
summaryKeys: ["imageId"],
|
|
128
|
+
},
|
|
129
|
+
image_rebuild_all: {
|
|
130
|
+
titleKey: "toolTitle_image_rebuild_all",
|
|
131
|
+
icon: _jsx(IconRefreshOutlineRegular, { size: 14 }),
|
|
132
|
+
summaryKeys: [],
|
|
133
|
+
},
|
|
134
|
+
image_remove: {
|
|
135
|
+
titleKey: "toolTitle_image_remove",
|
|
136
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
137
|
+
summaryKeys: ["imageId"],
|
|
138
|
+
},
|
|
139
|
+
volume_list: {
|
|
140
|
+
titleKey: "toolTitle_volume_list",
|
|
141
|
+
icon: _jsx(IconDataOutlineRegular, { size: 14 }),
|
|
142
|
+
summaryKeys: [],
|
|
143
|
+
},
|
|
144
|
+
volume_create: {
|
|
145
|
+
titleKey: "toolTitle_volume_create",
|
|
146
|
+
icon: _jsx(IconPlusOutlineRegular, { size: 14 }),
|
|
147
|
+
summaryKeys: ["name"],
|
|
148
|
+
},
|
|
149
|
+
volume_remove: {
|
|
150
|
+
titleKey: "toolTitle_volume_remove",
|
|
151
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
152
|
+
summaryKeys: ["name"],
|
|
153
|
+
},
|
|
154
|
+
secret_list: {
|
|
155
|
+
titleKey: "toolTitle_secret_list",
|
|
156
|
+
icon: _jsx(IconLinkOutlineRegular, { size: 14 }),
|
|
157
|
+
summaryKeys: [],
|
|
158
|
+
},
|
|
159
|
+
secret_create: {
|
|
160
|
+
titleKey: "toolTitle_secret_create",
|
|
161
|
+
icon: _jsx(IconPlusOutlineRegular, { size: 14 }),
|
|
162
|
+
summaryKeys: ["name"],
|
|
163
|
+
},
|
|
164
|
+
secret_remove: {
|
|
165
|
+
titleKey: "toolTitle_secret_remove",
|
|
166
|
+
icon: _jsx(IconTrashOutlineRegular, { size: 14 }),
|
|
167
|
+
summaryKeys: ["name"],
|
|
168
|
+
},
|
|
169
|
+
daemon_start: {
|
|
170
|
+
titleKey: "toolTitle_daemon_start",
|
|
171
|
+
icon: _jsx(IconPlayOutlineRegular, { size: 14 }),
|
|
172
|
+
summaryKeys: ["name"],
|
|
173
|
+
},
|
|
174
|
+
daemon_list: {
|
|
175
|
+
titleKey: "toolTitle_daemon_list",
|
|
176
|
+
icon: _jsx(IconPlayOutlineRegular, { size: 14 }),
|
|
177
|
+
summaryKeys: ["container"],
|
|
178
|
+
},
|
|
179
|
+
daemon_logs: {
|
|
180
|
+
titleKey: "toolTitle_daemon_logs",
|
|
181
|
+
icon: _jsx(IconBrowseOutlineRegular, { size: 14 }),
|
|
182
|
+
summaryKeys: ["name"],
|
|
183
|
+
},
|
|
184
|
+
daemon_restart: {
|
|
185
|
+
titleKey: "toolTitle_daemon_restart",
|
|
186
|
+
icon: _jsx(IconRefreshOutlineRegular, { size: 14 }),
|
|
187
|
+
summaryKeys: ["name"],
|
|
188
|
+
},
|
|
189
|
+
daemon_stop: {
|
|
190
|
+
titleKey: "toolTitle_daemon_stop",
|
|
191
|
+
icon: _jsx(IconStopFillRegular, { size: 14 }),
|
|
192
|
+
summaryKeys: ["name"],
|
|
193
|
+
},
|
|
46
194
|
};
|
|
47
195
|
/** The wire tool names this package owns a row for. */
|
|
48
196
|
export const TOOL_VIEW_KEYS = Object.keys(TOOL_PRESENTATION);
|
|
@@ -90,6 +238,14 @@ function firstLine(text) {
|
|
|
90
238
|
const newline = text.indexOf("\n");
|
|
91
239
|
return newline === -1 ? text : text.slice(0, newline);
|
|
92
240
|
}
|
|
241
|
+
// A hard bound on the output lines a result may render, so a multi-megabyte
|
|
242
|
+
// result cannot create an unbounded number of DOM nodes. Small outputs pass
|
|
243
|
+
// through untouched.
|
|
244
|
+
const MAX_RENDERED_LINES = 2000;
|
|
245
|
+
function capLines(text, max) {
|
|
246
|
+
const lines = text.split("\n");
|
|
247
|
+
return lines.length <= max ? text : lines.slice(0, max).join("\n");
|
|
248
|
+
}
|
|
93
249
|
function parseArgs(argsRaw) {
|
|
94
250
|
try {
|
|
95
251
|
const parsed = JSON.parse(argsRaw);
|
|
@@ -146,12 +302,11 @@ function prettyOutput(text) {
|
|
|
146
302
|
return text;
|
|
147
303
|
}
|
|
148
304
|
function terminalCard(toolName, args, block, cwd) {
|
|
149
|
-
if (toolName !== "container_bash" && toolName !== "container_exec")
|
|
305
|
+
if (toolName !== "container_bash" && toolName !== "container_exec") {
|
|
150
306
|
return null;
|
|
307
|
+
}
|
|
151
308
|
const command = toolName === "container_bash"
|
|
152
|
-
? typeof args.command === "string"
|
|
153
|
-
? args.command
|
|
154
|
-
: ""
|
|
309
|
+
? typeof args.command === "string" ? args.command : ""
|
|
155
310
|
: Array.isArray(args.argv)
|
|
156
311
|
? args.argv.map((word) => String(word)).join(" ")
|
|
157
312
|
: "";
|
|
@@ -185,9 +340,9 @@ function terminalCard(toolName, args, block, cwd) {
|
|
|
185
340
|
return {
|
|
186
341
|
...base,
|
|
187
342
|
output,
|
|
188
|
-
...(signal !== undefined
|
|
189
|
-
?
|
|
190
|
-
|
|
343
|
+
...(signal !== undefined ? { signal } : {
|
|
344
|
+
exitCode: typeof record.exitCode === "number" ? record.exitCode : 0,
|
|
345
|
+
}),
|
|
191
346
|
};
|
|
192
347
|
}
|
|
193
348
|
catch {
|
|
@@ -200,6 +355,7 @@ function terminalLabels(t) {
|
|
|
200
355
|
return {
|
|
201
356
|
signal: (signal) => t("terminalSignal", { signal }),
|
|
202
357
|
exitCode: (code) => t("terminalExitCode", { code }),
|
|
358
|
+
noExitCode: t("terminalNoExitCode"),
|
|
203
359
|
running: t("terminalRunning"),
|
|
204
360
|
failed: t("terminalFailed"),
|
|
205
361
|
done: t("terminalDone"),
|
|
@@ -215,9 +371,11 @@ function terminalLabels(t) {
|
|
|
215
371
|
/** Render one podman tool call as an icon-titled, expandable row. */
|
|
216
372
|
export function PodmanToolRow({ toolName, block, cwd, home, inspect, t, }) {
|
|
217
373
|
const presentation = TOOL_PRESENTATION[toolName];
|
|
218
|
-
const title = presentation === undefined
|
|
374
|
+
const title = presentation === undefined
|
|
375
|
+
? toolName
|
|
376
|
+
: t(presentation.titleKey);
|
|
219
377
|
const settled = "kind" in block;
|
|
220
|
-
const argsRaw = (
|
|
378
|
+
const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? "";
|
|
221
379
|
const args = parseArgs(argsRaw);
|
|
222
380
|
const terminal = terminalCard(toolName, args, block, cwd);
|
|
223
381
|
const state = !settled
|
|
@@ -235,7 +393,15 @@ export function PodmanToolRow({ toolName, block, cwd, home, inspect, t, }) {
|
|
|
235
393
|
argSummary(args, presentation?.summaryKeys ?? [], firstLine(argsRaw) || block.callId);
|
|
236
394
|
const [expanded, setExpanded] = useState(false);
|
|
237
395
|
const expandable = terminal !== null || (output !== null && output !== "");
|
|
238
|
-
const leading = state === "error"
|
|
396
|
+
const leading = state === "error"
|
|
397
|
+
? _jsx(StateDot, { state: "error" })
|
|
398
|
+
: state === "stopped"
|
|
399
|
+
? _jsx(StateDot, { state: "warning" })
|
|
400
|
+
: (presentation?.icon ?? _jsx(IconSparkleRegular, { size: 14 }));
|
|
239
401
|
void inspect;
|
|
240
|
-
return (_jsx(DisclosureRow, { icon: leading, title: title, open: expanded && expandable, expandable: expandable, onToggle: () => setExpanded((value) => !value), expandOnRowClick: true, keepContentWhenOpen: true, collapsedContent: _jsxs(_Fragment, { children: [_jsx("span", { style: sepStyle, "aria-hidden": true }), _jsx("span", { style: failureLine !== null ? errorSummaryStyle : summaryStyle, children: summary })] }), children: terminal !== null
|
|
402
|
+
return (_jsx(DisclosureRow, { icon: leading, title: title, open: expanded && expandable, expandable: expandable, onToggle: () => setExpanded((value) => !value), expandOnRowClick: true, keepContentWhenOpen: true, collapsedContent: _jsxs(_Fragment, { children: [_jsx("span", { style: sepStyle, "aria-hidden": true }), _jsx("span", { style: failureLine !== null ? errorSummaryStyle : summaryStyle, children: summary })] }), children: terminal !== null
|
|
403
|
+
? (_jsx(TerminalBlock, { command: terminal.command, cwd: terminal.cwd, home: home, output: terminal.output, exitCode: terminal.exitCode, signal: terminal.signal, running: terminal.running, maxLines: MAX_RENDERED_LINES, className: TERMINAL_CLASS, labels: terminalLabels(t) }))
|
|
404
|
+
: expandable
|
|
405
|
+
? (_jsx("pre", { style: state === "error" ? errorBodyStyle : bodyStyle, children: capLines(output ?? "", MAX_RENDERED_LINES) }))
|
|
406
|
+
: null }));
|
|
241
407
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function mergeDefaultEnv(defaults: Record<string, string> | undefined, env: Record<string, string> | undefined): Record<string, string>;
|
|
2
|
+
export declare function missingDefaultEnv(defaults: Record<string, string> | undefined, env: Record<string, string> | undefined): Record<string, string>;
|
|
3
|
+
export declare function gitIdentityEnv(name: string, email: string): Record<string, string>;
|
|
4
|
+
export declare function readGitIdentity(env: Record<string, string> | undefined): {
|
|
5
|
+
name: string;
|
|
6
|
+
email: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const GIT_IDENTITY_KEYS: readonly ["GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL"];
|
|
9
|
+
export declare function setGitIdentity(env: Record<string, string>, name: string, email: string): Record<string, string>;
|
|
10
|
+
export declare function gitOnlyDrift(server: Record<string, string>, before: Record<string, string>): boolean;
|
|
11
|
+
export declare function commitIdentityAction(server: Record<string, string>, draft: Record<string, string>, next: Record<string, string>): "discard" | "save" | "draft";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// The container environment defaults: one global map, seeded into a container
|
|
5
|
+
// when it is created (see mergeDefaultEnv) and left alone by recreates, so a
|
|
6
|
+
// value can be removed again by recreating the container without it.
|
|
7
|
+
// The reserved prefix the orchestrator refuses on a client-supplied variable;
|
|
8
|
+
// a default in that namespace would make container creation fail, so it is
|
|
9
|
+
// dropped instead of sent.
|
|
10
|
+
const RESERVED_ENV_PREFIX = "DSH_PODMAN";
|
|
11
|
+
// mergeDefaultEnv layers the defaults under a container's own environment: the
|
|
12
|
+
// caller's entries win per key, and defaults that cannot be applied (an empty
|
|
13
|
+
// key, or one in the reserved namespace) are dropped.
|
|
14
|
+
export function mergeDefaultEnv(defaults, env) {
|
|
15
|
+
const merged = {};
|
|
16
|
+
for (const [key, value] of Object.entries(defaults ?? {})) {
|
|
17
|
+
if (key === "" || key.startsWith(RESERVED_ENV_PREFIX))
|
|
18
|
+
continue;
|
|
19
|
+
merged[key] = value;
|
|
20
|
+
}
|
|
21
|
+
return { ...merged, ...(env ?? {}) };
|
|
22
|
+
}
|
|
23
|
+
// missingDefaultEnv returns the defaults a container does not have yet, so a
|
|
24
|
+
// synchronisation can add exactly those and never overwrite an existing value.
|
|
25
|
+
export function missingDefaultEnv(defaults, env) {
|
|
26
|
+
const missing = {};
|
|
27
|
+
for (const [key, value] of Object.entries(mergeDefaultEnv(defaults, undefined))) {
|
|
28
|
+
if (!Object.prototype.hasOwnProperty.call(env ?? {}, key)) {
|
|
29
|
+
missing[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return missing;
|
|
33
|
+
}
|
|
34
|
+
// gitIdentityEnv turns one name and one email into the four variables git
|
|
35
|
+
// needs: the author and the committer pair are the same identity.
|
|
36
|
+
export function gitIdentityEnv(name, email) {
|
|
37
|
+
return {
|
|
38
|
+
GIT_AUTHOR_NAME: name,
|
|
39
|
+
GIT_AUTHOR_EMAIL: email,
|
|
40
|
+
GIT_COMMITTER_NAME: name,
|
|
41
|
+
GIT_COMMITTER_EMAIL: email,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// readGitIdentity reads the name and email back out of an environment map (the
|
|
45
|
+
// author pair; the popup writes all four together).
|
|
46
|
+
export function readGitIdentity(env) {
|
|
47
|
+
return {
|
|
48
|
+
name: env?.GIT_AUTHOR_NAME ?? "",
|
|
49
|
+
email: env?.GIT_AUTHOR_EMAIL ?? "",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// The four keys gitIdentityEnv owns, in a stable order.
|
|
53
|
+
export const GIT_IDENTITY_KEYS = [
|
|
54
|
+
"GIT_AUTHOR_NAME",
|
|
55
|
+
"GIT_AUTHOR_EMAIL",
|
|
56
|
+
"GIT_COMMITTER_NAME",
|
|
57
|
+
"GIT_COMMITTER_EMAIL",
|
|
58
|
+
];
|
|
59
|
+
// setGitIdentity returns env with the git identity added (name and email
|
|
60
|
+
// non-empty) or removed (either empty).
|
|
61
|
+
export function setGitIdentity(env, name, email) {
|
|
62
|
+
const next = { ...env };
|
|
63
|
+
for (const key of GIT_IDENTITY_KEYS)
|
|
64
|
+
delete next[key];
|
|
65
|
+
if (name === "" || email === "")
|
|
66
|
+
return next;
|
|
67
|
+
return { ...next, ...gitIdentityEnv(name, email) };
|
|
68
|
+
}
|
|
69
|
+
// gitOnlyDrift answers whether `before` differs from the persisted `server`
|
|
70
|
+
// only inside the git identity keys. When that holds, the identity popup can
|
|
71
|
+
// commit its change directly, because no other default is being edited.
|
|
72
|
+
export function gitOnlyDrift(server, before) {
|
|
73
|
+
const identity = new Set(GIT_IDENTITY_KEYS);
|
|
74
|
+
const keys = new Set([...Object.keys(server), ...Object.keys(before)]);
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
if (identity.has(key))
|
|
77
|
+
continue;
|
|
78
|
+
if (server[key] !== before[key])
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// commitIdentityAction decides how the identity popup's result `next` is
|
|
84
|
+
// applied: "discard" when it restores the persisted environment exactly (the
|
|
85
|
+
// popup brought the identity back, so its pending change is dropped), "save"
|
|
86
|
+
// when the only drift from the server is inside the identity keys (committing
|
|
87
|
+
// straight cannot lose any other edit), "draft" otherwise (other defaults are
|
|
88
|
+
// being edited, so the result lands in the draft and the Save button stays in
|
|
89
|
+
// charge). Since setGitIdentity copies every non-identity key, `next` can
|
|
90
|
+
// equal the server only when nothing but the identity was pending, so
|
|
91
|
+
// "discard" never loses another edit.
|
|
92
|
+
export function commitIdentityAction(server, draft, next) {
|
|
93
|
+
if (JSON.stringify(next) === JSON.stringify(server))
|
|
94
|
+
return "discard";
|
|
95
|
+
return gitOnlyDrift(server, draft) ? "save" : "draft";
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { commitIdentityAction, GIT_IDENTITY_KEYS, gitIdentityEnv, gitOnlyDrift, mergeDefaultEnv, missingDefaultEnv, readGitIdentity, setGitIdentity, } from "./container-env.js";
|
|
7
|
+
test("mergeDefaultEnv seeds defaults under the container's own env", () => {
|
|
8
|
+
assert.deepEqual(mergeDefaultEnv({ A: "1", B: "2" }, { B: "own", C: "3" }), {
|
|
9
|
+
A: "1",
|
|
10
|
+
B: "own",
|
|
11
|
+
C: "3",
|
|
12
|
+
});
|
|
13
|
+
assert.deepEqual(mergeDefaultEnv(undefined, { A: "1" }), { A: "1" });
|
|
14
|
+
assert.deepEqual(mergeDefaultEnv({ A: "1" }, undefined), { A: "1" });
|
|
15
|
+
});
|
|
16
|
+
test("mergeDefaultEnv drops empty and reserved defaults", () => {
|
|
17
|
+
assert.deepEqual(mergeDefaultEnv({ "": "x", DSH_PODMAN_GUEST_TOKEN: "x", KEEP: "1" }, {}), { KEEP: "1" });
|
|
18
|
+
// An explicit value in the reserved namespace is the caller's own doing and
|
|
19
|
+
// is left to the orchestrator's validation.
|
|
20
|
+
assert.deepEqual(mergeDefaultEnv({}, { DSH_PODMAN_GUEST_TOKEN: "x" }), {
|
|
21
|
+
DSH_PODMAN_GUEST_TOKEN: "x",
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
test("missingDefaultEnv reports only the absent keys", () => {
|
|
25
|
+
assert.deepEqual(missingDefaultEnv({ A: "1", B: "2" }, { B: "own" }), {
|
|
26
|
+
A: "1",
|
|
27
|
+
});
|
|
28
|
+
assert.deepEqual(missingDefaultEnv({ A: "1" }, { A: "own" }), {});
|
|
29
|
+
assert.deepEqual(missingDefaultEnv(undefined, {}), {});
|
|
30
|
+
});
|
|
31
|
+
test("git identity fills the author and committer pairs", () => {
|
|
32
|
+
assert.deepEqual(gitIdentityEnv("John Doe", "john.doe@git.example"), {
|
|
33
|
+
GIT_AUTHOR_NAME: "John Doe",
|
|
34
|
+
GIT_AUTHOR_EMAIL: "john.doe@git.example",
|
|
35
|
+
GIT_COMMITTER_NAME: "John Doe",
|
|
36
|
+
GIT_COMMITTER_EMAIL: "john.doe@git.example",
|
|
37
|
+
});
|
|
38
|
+
assert.deepEqual(readGitIdentity(gitIdentityEnv("John Doe", "john.doe@git.example")), {
|
|
39
|
+
name: "John Doe",
|
|
40
|
+
email: "john.doe@git.example",
|
|
41
|
+
});
|
|
42
|
+
assert.deepEqual(readGitIdentity(undefined), { name: "", email: "" });
|
|
43
|
+
});
|
|
44
|
+
test("setGitIdentity adds, replaces and clears the four keys", () => {
|
|
45
|
+
const base = { KEEP: "1", GIT_AUTHOR_NAME: "old" };
|
|
46
|
+
const set = setGitIdentity(base, "new", "new@example.com");
|
|
47
|
+
assert.deepEqual(set, {
|
|
48
|
+
KEEP: "1",
|
|
49
|
+
...gitIdentityEnv("new", "new@example.com"),
|
|
50
|
+
});
|
|
51
|
+
for (const key of GIT_IDENTITY_KEYS)
|
|
52
|
+
assert.ok(key in set);
|
|
53
|
+
// An empty field clears the whole identity, leaving other defaults alone.
|
|
54
|
+
assert.deepEqual(setGitIdentity(set, "", "new@example.com"), { KEEP: "1" });
|
|
55
|
+
assert.deepEqual(setGitIdentity(set, "new", ""), { KEEP: "1" });
|
|
56
|
+
// The input map is not mutated.
|
|
57
|
+
assert.deepEqual(base, { KEEP: "1", GIT_AUTHOR_NAME: "old" });
|
|
58
|
+
});
|
|
59
|
+
test("gitOnlyDrift tells an identity-only change apart from other edits", () => {
|
|
60
|
+
const identity = gitIdentityEnv("John Doe", "john.doe@git.example");
|
|
61
|
+
// No drift at all qualifies (there is nothing else to lose by committing).
|
|
62
|
+
assert.equal(gitOnlyDrift(identity, identity), true);
|
|
63
|
+
// Changing or removing the identity alone is git-only.
|
|
64
|
+
assert.equal(gitOnlyDrift(identity, setGitIdentity(identity, "New", "new@example.com")), true);
|
|
65
|
+
assert.equal(gitOnlyDrift(identity, setGitIdentity(identity, "", "")), true);
|
|
66
|
+
// Any other default drift means the Save button must stay in charge.
|
|
67
|
+
assert.equal(gitOnlyDrift(identity, { ...identity, KEEP: "1" }), false);
|
|
68
|
+
assert.equal(gitOnlyDrift({ KEEP: "1" }, { KEEP: "2" }), false);
|
|
69
|
+
// Removing a non-identity key, or an identity key drifting in the server map
|
|
70
|
+
// while another edit exists, both count as a real diff.
|
|
71
|
+
assert.equal(gitOnlyDrift({ KEEP: "1", ...identity }, identity), false);
|
|
72
|
+
});
|
|
73
|
+
test("commitIdentityAction applies the popup result without losing edits", () => {
|
|
74
|
+
const saved = gitIdentityEnv("John Doe", "john.doe@git.example");
|
|
75
|
+
const changed = setGitIdentity(saved, "New", "new@example.com");
|
|
76
|
+
// The popup restores the saved identity while the draft still holds the
|
|
77
|
+
// changed one: the pending identity edit is discarded, not kept (regression:
|
|
78
|
+
// the old code returned early and left the changed identity in the draft).
|
|
79
|
+
assert.equal(commitIdentityAction(saved, changed, saved), "discard");
|
|
80
|
+
// No drift at all and an unchanged popup is a no-op discard too.
|
|
81
|
+
assert.equal(commitIdentityAction(saved, saved, saved), "discard");
|
|
82
|
+
// An identity-only change commits straight: nothing else can be lost.
|
|
83
|
+
assert.equal(commitIdentityAction(saved, changed, changed), "save");
|
|
84
|
+
assert.equal(commitIdentityAction(saved, saved, changed), "save");
|
|
85
|
+
// Clearing the identity alone is also git-only.
|
|
86
|
+
assert.equal(commitIdentityAction(saved, changed, {}), "save");
|
|
87
|
+
// Another pending default keeps the result in the draft, whatever the popup
|
|
88
|
+
// does to the identity: change, restore, or clear.
|
|
89
|
+
const withKeep = { ...saved, KEEP: "1" };
|
|
90
|
+
assert.equal(commitIdentityAction(saved, withKeep, { ...withKeep, ...changed }), "draft");
|
|
91
|
+
assert.equal(commitIdentityAction(saved, withKeep, withKeep), "draft");
|
|
92
|
+
assert.equal(commitIdentityAction(saved, withKeep, { KEEP: "1" }), "draft");
|
|
93
|
+
});
|