@exagone313/dsh-podman 0.2.0-rc.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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/README.zh.md +41 -0
- package/cordis.patch.yml +17 -0
- package/dist/approval-reasons.d.ts +129 -0
- package/dist/approval-reasons.js +253 -0
- package/dist/approval-reasons.test.d.ts +1 -0
- package/dist/approval-reasons.test.js +115 -0
- package/dist/approval.d.ts +29 -0
- package/dist/approval.js +412 -0
- package/dist/approval.test.d.ts +1 -0
- package/dist/approval.test.js +462 -0
- package/dist/card-route.d.ts +15 -0
- package/dist/card-route.js +478 -0
- package/dist/card-route.test.d.ts +1 -0
- package/dist/card-route.test.js +181 -0
- package/dist/card-test-support.d.ts +15 -0
- package/dist/card-test-support.js +160 -0
- package/dist/client/ContainerCard.d.ts +6 -0
- package/dist/client/ContainerCard.js +79 -0
- package/dist/client/card-client.d.ts +6 -0
- package/dist/client/card-client.js +47 -0
- package/dist/client/card-protocol.d.ts +101 -0
- package/dist/client/card-protocol.js +10 -0
- package/dist/client/container-card-caches.d.ts +10 -0
- package/dist/client/container-card-caches.js +31 -0
- package/dist/client/container-card-controller.d.ts +107 -0
- package/dist/client/container-card-controller.js +207 -0
- package/dist/client/container-card-create-modal.d.ts +24 -0
- package/dist/client/container-card-create-modal.js +109 -0
- package/dist/client/container-card-directory-styles.d.ts +5 -0
- package/dist/client/container-card-directory-styles.js +246 -0
- package/dist/client/container-card-directory.d.ts +12 -0
- package/dist/client/container-card-directory.js +144 -0
- package/dist/client/container-card-editors.d.ts +30 -0
- package/dist/client/container-card-editors.js +116 -0
- package/dist/client/container-card-images.d.ts +37 -0
- package/dist/client/container-card-images.js +35 -0
- package/dist/client/container-card-paths.d.ts +20 -0
- package/dist/client/container-card-paths.js +88 -0
- package/dist/client/container-card-row.d.ts +30 -0
- package/dist/client/container-card-row.js +54 -0
- package/dist/client/container-card-secrets.d.ts +21 -0
- package/dist/client/container-card-secrets.js +71 -0
- package/dist/client/container-card-shared.d.ts +55 -0
- package/dist/client/container-card-shared.js +152 -0
- package/dist/client/container-card-styles.d.ts +21 -0
- package/dist/client/container-card-styles.js +143 -0
- package/dist/client/container-card-volumes.d.ts +12 -0
- package/dist/client/container-card-volumes.js +37 -0
- package/dist/client/container-card-workspace.d.ts +31 -0
- package/dist/client/container-card-workspace.js +24 -0
- package/dist/client/directory-picker.d.ts +12 -0
- package/dist/client/directory-picker.js +30 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +4419 -0
- package/dist/client/locales.d.ts +9 -0
- package/dist/client/locales.js +370 -0
- package/dist/client/read-only-approval.d.ts +8 -0
- package/dist/client/read-only-approval.js +63 -0
- package/dist/client/slot-contract.d.ts +12 -0
- package/dist/client/slot-contract.js +4 -0
- package/dist/client/terminal-styles.d.ts +5 -0
- package/dist/client/terminal-styles.js +31 -0
- package/dist/client/tool-views.d.ts +8 -0
- package/dist/client/tool-views.js +241 -0
- package/dist/containers.test.d.ts +1 -0
- package/dist/containers.test.js +443 -0
- package/dist/daemons.test.d.ts +1 -0
- package/dist/daemons.test.js +153 -0
- package/dist/fs-provider.d.ts +23 -0
- package/dist/fs-provider.js +184 -0
- package/dist/fs-provider.test.d.ts +1 -0
- package/dist/fs-provider.test.js +260 -0
- package/dist/generated/version.d.ts +2 -0
- package/dist/generated/version.js +6 -0
- package/dist/grpc/proto/dshctl/v1/control.proto +133 -0
- package/dist/grpc/proto/dshguest/v1/guest.proto +79 -0
- package/dist/grpc/runtime-client.d.ts +5 -0
- package/dist/grpc/runtime-client.js +43 -0
- package/dist/guest-rpc.d.ts +62 -0
- package/dist/guest-rpc.js +391 -0
- package/dist/images.test.d.ts +1 -0
- package/dist/images.test.js +79 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +140 -0
- package/dist/misc.test.d.ts +1 -0
- package/dist/misc.test.js +141 -0
- package/dist/mount-enums.d.ts +22 -0
- package/dist/mount-enums.js +81 -0
- package/dist/mount-input.d.ts +14 -0
- package/dist/mount-input.js +64 -0
- package/dist/mounts.test.d.ts +1 -0
- package/dist/mounts.test.js +408 -0
- package/dist/output-reader.d.ts +34 -0
- package/dist/output-reader.js +87 -0
- package/dist/paths.test.d.ts +1 -0
- package/dist/paths.test.js +74 -0
- package/dist/preferences.d.ts +6 -0
- package/dist/preferences.js +27 -0
- package/dist/project-path.d.ts +6 -0
- package/dist/project-path.js +73 -0
- package/dist/project-path.test.d.ts +1 -0
- package/dist/project-path.test.js +52 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +149 -0
- package/dist/public.d.ts +4 -0
- package/dist/public.js +63 -0
- package/dist/read-only-shell.d.ts +14 -0
- package/dist/read-only-shell.js +150 -0
- package/dist/read-only-shell.test.d.ts +1 -0
- package/dist/read-only-shell.test.js +127 -0
- package/dist/secrets.test.d.ts +1 -0
- package/dist/secrets.test.js +181 -0
- package/dist/settings-commands.test.d.ts +1 -0
- package/dist/settings-commands.test.js +549 -0
- package/dist/settings-create.test.d.ts +1 -0
- package/dist/settings-create.test.js +394 -0
- package/dist/settings-mounts.test.d.ts +1 -0
- package/dist/settings-mounts.test.js +467 -0
- package/dist/settings-paths.test.d.ts +1 -0
- package/dist/settings-paths.test.js +65 -0
- package/dist/settings-schema.d.ts +14 -0
- package/dist/settings-schema.js +14 -0
- package/dist/settings-workspaces.test.d.ts +1 -0
- package/dist/settings-workspaces.test.js +265 -0
- package/dist/spill-store.d.ts +17 -0
- package/dist/spill-store.js +54 -0
- package/dist/spill-store.test.d.ts +1 -0
- package/dist/spill-store.test.js +59 -0
- package/dist/subprocess.d.ts +9 -0
- package/dist/subprocess.js +369 -0
- package/dist/subprocess.test.d.ts +1 -0
- package/dist/subprocess.test.js +234 -0
- package/dist/test-support.d.ts +328 -0
- package/dist/test-support.js +437 -0
- package/dist/tool-defs.d.ts +26 -0
- package/dist/tool-defs.js +188 -0
- package/dist/tool-handlers.d.ts +2 -0
- package/dist/tool-handlers.js +546 -0
- package/dist/tool-params.d.ts +803 -0
- package/dist/tool-params.js +494 -0
- package/dist/tool-schemas.d.ts +2 -0
- package/dist/tool-schemas.js +5 -0
- package/dist/tool-views.d.ts +2 -0
- package/dist/tool-views.js +56 -0
- package/dist/views.test.d.ts +1 -0
- package/dist/views.test.js +23 -0
- package/dist/volumes.test.d.ts +1 -0
- package/dist/volumes.test.js +40 -0
- package/dist/workspace-binding.d.ts +40 -0
- package/dist/workspace-binding.js +275 -0
- package/dist/workspace-binding.test.d.ts +1 -0
- package/dist/workspace-binding.test.js +337 -0
- package/docs/architecture.md +173 -0
- package/docs/architecture.zh.md +129 -0
- package/docs/configuration.md +160 -0
- package/docs/configuration.zh.md +147 -0
- package/docs/development.md +142 -0
- package/docs/development.zh.md +135 -0
- package/docs/install-dsh-and-dsh-podman.md +165 -0
- package/docs/install-dsh-and-dsh-podman.zh.md +145 -0
- package/docs/usage.md +393 -0
- package/docs/usage.zh.md +315 -0
- package/package.json +119 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import { strict as assert } from "node:assert";
|
|
6
|
+
import { baseValue, fakeContext, fakeScope, installedMountScope, mountCommand, } from "./card-test-support.js";
|
|
7
|
+
import { installCardCommandDriver } from "./card-test-support.js";
|
|
8
|
+
test("create command drives createWorkspace with mounts and image", async () => {
|
|
9
|
+
const scope = fakeScope(baseValue());
|
|
10
|
+
const calls = [];
|
|
11
|
+
const resolver = {
|
|
12
|
+
getConfig: () => ({}),
|
|
13
|
+
setConfig: () => { },
|
|
14
|
+
async control(method, request) {
|
|
15
|
+
calls.push([method, request]);
|
|
16
|
+
if (method === "listContainers")
|
|
17
|
+
return { containers: [] };
|
|
18
|
+
if (method === "listImages")
|
|
19
|
+
return { images: [] };
|
|
20
|
+
if (method === "listWorkspaces")
|
|
21
|
+
return { workspaces: [] };
|
|
22
|
+
return {};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
await installCardCommandDriver(fakeContext(scope), resolver);
|
|
26
|
+
await scope.update({
|
|
27
|
+
command: {
|
|
28
|
+
op: "create",
|
|
29
|
+
workspace: "w1",
|
|
30
|
+
projectName: "team",
|
|
31
|
+
image: "img1",
|
|
32
|
+
at: 1,
|
|
33
|
+
mounts: [{ kind: "project", project: "team", mode: "read_write", destination: "", volume: "", secret: "" }],
|
|
34
|
+
env: {},
|
|
35
|
+
container: "",
|
|
36
|
+
secretEnvMap: {},
|
|
37
|
+
mount: null,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
41
|
+
const createCall = calls.find(([method]) => method === "createWorkspace");
|
|
42
|
+
assert.deepEqual(createCall?.[1], {
|
|
43
|
+
workspaceSlug: "w1",
|
|
44
|
+
projectName: "team",
|
|
45
|
+
imageId: "img1",
|
|
46
|
+
mounts: [{ projectName: "team", kind: "MOUNT_KIND_PROJECT", mode: "MOUNT_MODE_READ_WRITE" }],
|
|
47
|
+
});
|
|
48
|
+
assert.equal(scope.value.command, null);
|
|
49
|
+
});
|
|
50
|
+
test("container_mount_add command drives addContainerMount for each kind", async () => {
|
|
51
|
+
const scope = fakeScope(baseValue());
|
|
52
|
+
const calls = [];
|
|
53
|
+
const resolver = {
|
|
54
|
+
getConfig: () => ({}),
|
|
55
|
+
setConfig: () => { },
|
|
56
|
+
async control(method, request) {
|
|
57
|
+
calls.push([method, request]);
|
|
58
|
+
if (method === "listContainers")
|
|
59
|
+
return { containers: [] };
|
|
60
|
+
if (method === "listImages")
|
|
61
|
+
return { images: [] };
|
|
62
|
+
if (method === "listWorkspaces")
|
|
63
|
+
return { workspaces: [] };
|
|
64
|
+
return {};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
await installCardCommandDriver(fakeContext(scope), resolver);
|
|
68
|
+
await scope.update({
|
|
69
|
+
command: {
|
|
70
|
+
op: "container_mount_add",
|
|
71
|
+
workspace: "w1",
|
|
72
|
+
container: "web",
|
|
73
|
+
image: "",
|
|
74
|
+
at: 1,
|
|
75
|
+
mount: {
|
|
76
|
+
kind: "project",
|
|
77
|
+
project: "team/src",
|
|
78
|
+
destination: "/workspace/team",
|
|
79
|
+
mode: "read_only",
|
|
80
|
+
volume: "",
|
|
81
|
+
secret: "",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
86
|
+
await scope.update({
|
|
87
|
+
command: {
|
|
88
|
+
op: "container_mount_add",
|
|
89
|
+
workspace: "w1",
|
|
90
|
+
container: "web",
|
|
91
|
+
image: "",
|
|
92
|
+
at: 2,
|
|
93
|
+
mount: {
|
|
94
|
+
kind: "volume",
|
|
95
|
+
project: "",
|
|
96
|
+
destination: "/data",
|
|
97
|
+
mode: "read_write",
|
|
98
|
+
volume: "valkey-data",
|
|
99
|
+
secret: "",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
104
|
+
await scope.update({
|
|
105
|
+
command: {
|
|
106
|
+
op: "container_mount_add",
|
|
107
|
+
workspace: "w1",
|
|
108
|
+
container: "web",
|
|
109
|
+
image: "",
|
|
110
|
+
at: 3,
|
|
111
|
+
mount: {
|
|
112
|
+
kind: "tmpfs",
|
|
113
|
+
project: "",
|
|
114
|
+
destination: "/dev/shm",
|
|
115
|
+
mode: "read_write",
|
|
116
|
+
volume: "",
|
|
117
|
+
secret: "",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
122
|
+
await scope.update({
|
|
123
|
+
command: {
|
|
124
|
+
op: "container_mount_add",
|
|
125
|
+
workspace: "w1",
|
|
126
|
+
container: "",
|
|
127
|
+
image: "",
|
|
128
|
+
at: 4,
|
|
129
|
+
mount: {
|
|
130
|
+
kind: "secret",
|
|
131
|
+
project: "",
|
|
132
|
+
destination: "/run/secrets/tls",
|
|
133
|
+
mode: "read_write",
|
|
134
|
+
volume: "",
|
|
135
|
+
secret: "valkey-tls",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
140
|
+
const addCalls = calls.filter(([method]) => method === "addContainerMount");
|
|
141
|
+
assert.deepEqual(addCalls.map(([, request]) => request), [
|
|
142
|
+
{
|
|
143
|
+
workspaceSlug: "w1",
|
|
144
|
+
container: "web",
|
|
145
|
+
kind: "MOUNT_KIND_PROJECT",
|
|
146
|
+
project: "team/src",
|
|
147
|
+
mode: "MOUNT_MODE_READ_ONLY",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
workspaceSlug: "w1",
|
|
151
|
+
container: "web",
|
|
152
|
+
kind: "MOUNT_KIND_VOLUME",
|
|
153
|
+
volume: "valkey-data",
|
|
154
|
+
destination: "/data",
|
|
155
|
+
mode: "MOUNT_MODE_READ_WRITE",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
workspaceSlug: "w1",
|
|
159
|
+
container: "web",
|
|
160
|
+
kind: "MOUNT_KIND_TMPFS",
|
|
161
|
+
destination: "/dev/shm",
|
|
162
|
+
mode: "MOUNT_MODE_READ_WRITE",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
workspaceSlug: "w1",
|
|
166
|
+
container: "default",
|
|
167
|
+
kind: "MOUNT_KIND_SECRET",
|
|
168
|
+
secret: "valkey-tls",
|
|
169
|
+
destination: "/run/secrets/tls",
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
assert.equal(scope.value.command, null);
|
|
173
|
+
});
|
|
174
|
+
test("container_mount_remove command drives removeContainerMount for each kind", async () => {
|
|
175
|
+
const scope = fakeScope(baseValue());
|
|
176
|
+
const calls = [];
|
|
177
|
+
const resolver = {
|
|
178
|
+
getConfig: () => ({}),
|
|
179
|
+
setConfig: () => { },
|
|
180
|
+
async control(method, request) {
|
|
181
|
+
calls.push([method, request]);
|
|
182
|
+
if (method === "listContainers")
|
|
183
|
+
return { containers: [] };
|
|
184
|
+
if (method === "listImages")
|
|
185
|
+
return { images: [] };
|
|
186
|
+
if (method === "listWorkspaces")
|
|
187
|
+
return { workspaces: [] };
|
|
188
|
+
return {};
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
await installCardCommandDriver(fakeContext(scope), resolver);
|
|
192
|
+
await scope.update({
|
|
193
|
+
command: {
|
|
194
|
+
op: "container_mount_remove",
|
|
195
|
+
workspace: "w1",
|
|
196
|
+
container: "web",
|
|
197
|
+
image: "",
|
|
198
|
+
at: 1,
|
|
199
|
+
mount: {
|
|
200
|
+
kind: "project",
|
|
201
|
+
project: "team/src",
|
|
202
|
+
destination: "/workspace/team",
|
|
203
|
+
mode: "read_write",
|
|
204
|
+
volume: "",
|
|
205
|
+
secret: "",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
210
|
+
await scope.update({
|
|
211
|
+
command: {
|
|
212
|
+
op: "container_mount_remove",
|
|
213
|
+
workspace: "w1",
|
|
214
|
+
container: "web",
|
|
215
|
+
image: "",
|
|
216
|
+
at: 2,
|
|
217
|
+
mount: {
|
|
218
|
+
kind: "volume",
|
|
219
|
+
project: "",
|
|
220
|
+
destination: "",
|
|
221
|
+
mode: "read_write",
|
|
222
|
+
volume: "valkey-data",
|
|
223
|
+
secret: "",
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
228
|
+
await scope.update({
|
|
229
|
+
command: {
|
|
230
|
+
op: "container_mount_remove",
|
|
231
|
+
workspace: "w1",
|
|
232
|
+
container: "",
|
|
233
|
+
image: "",
|
|
234
|
+
at: 3,
|
|
235
|
+
mount: {
|
|
236
|
+
kind: "tmpfs",
|
|
237
|
+
project: "",
|
|
238
|
+
destination: "/dev/shm",
|
|
239
|
+
mode: "read_write",
|
|
240
|
+
volume: "",
|
|
241
|
+
secret: "",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
246
|
+
await scope.update({
|
|
247
|
+
command: {
|
|
248
|
+
op: "container_mount_remove",
|
|
249
|
+
workspace: "w1",
|
|
250
|
+
container: "web",
|
|
251
|
+
image: "",
|
|
252
|
+
at: 4,
|
|
253
|
+
mount: {
|
|
254
|
+
kind: "secret",
|
|
255
|
+
project: "",
|
|
256
|
+
destination: "/run/secrets/tls",
|
|
257
|
+
mode: "read_write",
|
|
258
|
+
volume: "",
|
|
259
|
+
secret: "valkey-tls",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
264
|
+
const removeCalls = calls.filter(([method]) => method === "removeContainerMount");
|
|
265
|
+
assert.deepEqual(removeCalls.map(([, request]) => request), [
|
|
266
|
+
{
|
|
267
|
+
workspaceSlug: "w1",
|
|
268
|
+
container: "web",
|
|
269
|
+
kind: "MOUNT_KIND_PROJECT",
|
|
270
|
+
project: "team/src",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
workspaceSlug: "w1",
|
|
274
|
+
container: "web",
|
|
275
|
+
kind: "MOUNT_KIND_VOLUME",
|
|
276
|
+
volume: "valkey-data",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
workspaceSlug: "w1",
|
|
280
|
+
container: "default",
|
|
281
|
+
kind: "MOUNT_KIND_TMPFS",
|
|
282
|
+
destination: "/dev/shm",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
workspaceSlug: "w1",
|
|
286
|
+
container: "web",
|
|
287
|
+
kind: "MOUNT_KIND_SECRET",
|
|
288
|
+
secret: "valkey-tls",
|
|
289
|
+
destination: "/run/secrets/tls",
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
assert.equal(scope.value.command, null);
|
|
293
|
+
});
|
|
294
|
+
test("container_mount_update command drives updateContainerMount", async () => {
|
|
295
|
+
const { scope, calls } = await installedMountScope();
|
|
296
|
+
await scope.update({
|
|
297
|
+
command: mountCommand("container_mount_update", {
|
|
298
|
+
kind: "project",
|
|
299
|
+
project: "team",
|
|
300
|
+
mode: "read_only",
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
304
|
+
await scope.update({
|
|
305
|
+
command: mountCommand("container_mount_update", {
|
|
306
|
+
kind: "volume",
|
|
307
|
+
volume: "valkey-data",
|
|
308
|
+
destination: "/data",
|
|
309
|
+
mode: "read_write",
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
312
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
313
|
+
const updateCalls = calls.filter(([method]) => method === "updateContainerMount");
|
|
314
|
+
assert.deepEqual(updateCalls.map(([, request]) => request), [
|
|
315
|
+
{
|
|
316
|
+
workspaceSlug: "w1",
|
|
317
|
+
container: "web",
|
|
318
|
+
kind: "MOUNT_KIND_PROJECT",
|
|
319
|
+
project: "team",
|
|
320
|
+
mode: "MOUNT_MODE_READ_ONLY",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
workspaceSlug: "w1",
|
|
324
|
+
container: "web",
|
|
325
|
+
kind: "MOUNT_KIND_VOLUME",
|
|
326
|
+
volume: "valkey-data",
|
|
327
|
+
destination: "/data",
|
|
328
|
+
mode: "MOUNT_MODE_READ_WRITE",
|
|
329
|
+
},
|
|
330
|
+
]);
|
|
331
|
+
assert.equal(scope.value.command, null);
|
|
332
|
+
});
|
|
333
|
+
test("container_mount_add command reports an unknown kind as a notice", async () => {
|
|
334
|
+
const { scope, calls } = await installedMountScope();
|
|
335
|
+
await scope.update({
|
|
336
|
+
command: mountCommand("container_mount_add", {
|
|
337
|
+
kind: "bind",
|
|
338
|
+
project: "team",
|
|
339
|
+
mode: "read_write",
|
|
340
|
+
}),
|
|
341
|
+
});
|
|
342
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
343
|
+
assert.equal(scope.value.notice, "unknown mount kind: bind");
|
|
344
|
+
assert.equal(scope.value.command, null);
|
|
345
|
+
assert.equal(calls.some(([method]) => method === "addContainerMount"), false, "a rejected mount must not reach the orchestrator");
|
|
346
|
+
});
|
|
347
|
+
test("container_mount_add command reports an unknown mode as a notice", async () => {
|
|
348
|
+
const { scope, calls } = await installedMountScope();
|
|
349
|
+
await scope.update({
|
|
350
|
+
command: mountCommand("container_mount_add", {
|
|
351
|
+
kind: "project",
|
|
352
|
+
project: "team",
|
|
353
|
+
mode: "rw",
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
356
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
357
|
+
assert.equal(scope.value.notice, "unknown mount mode: rw");
|
|
358
|
+
assert.equal(scope.value.command, null);
|
|
359
|
+
assert.equal(calls.some(([method]) => method === "addContainerMount"), false, "a rejected mount must not reach the orchestrator");
|
|
360
|
+
});
|
|
361
|
+
test("container_mount_remove command reports an unknown kind as a notice", async () => {
|
|
362
|
+
const { scope, calls } = await installedMountScope();
|
|
363
|
+
await scope.update({
|
|
364
|
+
command: mountCommand("container_mount_remove", {
|
|
365
|
+
kind: "bind",
|
|
366
|
+
project: "team",
|
|
367
|
+
}),
|
|
368
|
+
});
|
|
369
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
370
|
+
assert.equal(scope.value.notice, "unknown mount kind: bind");
|
|
371
|
+
assert.equal(scope.value.command, null);
|
|
372
|
+
assert.equal(calls.some(([method]) => method === "removeContainerMount"), false, "a rejected mount must not reach the orchestrator");
|
|
373
|
+
});
|
|
374
|
+
test("create command reports an unknown mount kind as a notice", async () => {
|
|
375
|
+
const { scope, calls } = await installedMountScope();
|
|
376
|
+
await scope.update({
|
|
377
|
+
command: {
|
|
378
|
+
op: "create",
|
|
379
|
+
workspace: "w1",
|
|
380
|
+
image: "",
|
|
381
|
+
at: 1,
|
|
382
|
+
mounts: [{
|
|
383
|
+
kind: "bind",
|
|
384
|
+
project: "team",
|
|
385
|
+
destination: "",
|
|
386
|
+
mode: "read_write",
|
|
387
|
+
volume: "",
|
|
388
|
+
secret: "",
|
|
389
|
+
}],
|
|
390
|
+
env: {},
|
|
391
|
+
container: "",
|
|
392
|
+
secretEnvMap: {},
|
|
393
|
+
mount: null,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
397
|
+
assert.equal(scope.value.notice, "unknown mount kind: bind");
|
|
398
|
+
assert.equal(scope.value.command, null);
|
|
399
|
+
assert.equal(calls.some(([method]) => method === "createWorkspace"), false, "a rejected mount must not reach the orchestrator");
|
|
400
|
+
});
|
|
401
|
+
test("create command reports an unknown mount mode as a notice", async () => {
|
|
402
|
+
const { scope, calls } = await installedMountScope();
|
|
403
|
+
await scope.update({
|
|
404
|
+
command: {
|
|
405
|
+
op: "create",
|
|
406
|
+
workspace: "w1",
|
|
407
|
+
image: "",
|
|
408
|
+
at: 1,
|
|
409
|
+
mounts: [{
|
|
410
|
+
kind: "project",
|
|
411
|
+
project: "team",
|
|
412
|
+
destination: "",
|
|
413
|
+
mode: "rw",
|
|
414
|
+
volume: "",
|
|
415
|
+
secret: "",
|
|
416
|
+
}],
|
|
417
|
+
env: {},
|
|
418
|
+
container: "",
|
|
419
|
+
secretEnvMap: {},
|
|
420
|
+
mount: null,
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
424
|
+
assert.equal(scope.value.notice, "unknown mount mode: rw");
|
|
425
|
+
assert.equal(scope.value.command, null);
|
|
426
|
+
assert.equal(calls.some(([method]) => method === "createWorkspace"), false, "a rejected mount must not reach the orchestrator");
|
|
427
|
+
});
|
|
428
|
+
test("listContainers container mounts carry the raw proto fields", async () => {
|
|
429
|
+
const scope = fakeScope(baseValue());
|
|
430
|
+
const resolver = {
|
|
431
|
+
getConfig: () => ({}),
|
|
432
|
+
setConfig: () => { },
|
|
433
|
+
async control(method) {
|
|
434
|
+
if (method === "listContainers") {
|
|
435
|
+
return {
|
|
436
|
+
containers: [{
|
|
437
|
+
containerName: "c1",
|
|
438
|
+
workspaceSlug: "w1",
|
|
439
|
+
mounts: [{
|
|
440
|
+
projectName: "team/src",
|
|
441
|
+
destination: "/x",
|
|
442
|
+
kind: "MOUNT_KIND_PROJECT",
|
|
443
|
+
mode: "MOUNT_MODE_READ_WRITE",
|
|
444
|
+
volume: "",
|
|
445
|
+
secret: "",
|
|
446
|
+
}],
|
|
447
|
+
}],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (method === "listImages")
|
|
451
|
+
return { images: [] };
|
|
452
|
+
if (method === "listWorkspaces")
|
|
453
|
+
return { workspaces: [] };
|
|
454
|
+
return {};
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
await installCardCommandDriver(fakeContext(scope), resolver);
|
|
458
|
+
await scope.update({});
|
|
459
|
+
assert.deepEqual(scope.value.containers[0].mounts[0], {
|
|
460
|
+
projectName: "team/src",
|
|
461
|
+
destination: "/x",
|
|
462
|
+
kind: "MOUNT_KIND_PROJECT",
|
|
463
|
+
mode: "MOUNT_MODE_READ_WRITE",
|
|
464
|
+
volume: "",
|
|
465
|
+
secret: "",
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
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 { baseValue, fakeContext, fakeScope } from "./card-test-support.js";
|
|
7
|
+
import { installCardCommandDriver } from "./card-test-support.js";
|
|
8
|
+
import { WORKSPACE_ID } from "./test-support.js";
|
|
9
|
+
test("container_path_set command persists the list and pushes it to the guest", async () => {
|
|
10
|
+
const scope = fakeScope(baseValue());
|
|
11
|
+
const calls = [];
|
|
12
|
+
const pushed = [];
|
|
13
|
+
const guest = {
|
|
14
|
+
setPaths: (request, _metadata, callback) => {
|
|
15
|
+
pushed.push(request.paths);
|
|
16
|
+
callback(null, { paths: request.paths });
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const binding = { guest, token: "t", socket: "/run/x.sock" };
|
|
20
|
+
const resolver = {
|
|
21
|
+
getConfig: () => ({}),
|
|
22
|
+
setConfig: () => { },
|
|
23
|
+
resolve: async () => binding,
|
|
24
|
+
containerBinding: async () => binding,
|
|
25
|
+
async control(method, request) {
|
|
26
|
+
calls.push([method, request]);
|
|
27
|
+
if (method === "listContainers")
|
|
28
|
+
return { containers: [] };
|
|
29
|
+
if (method === "listImages")
|
|
30
|
+
return { images: [] };
|
|
31
|
+
if (method === "listWorkspaces")
|
|
32
|
+
return { workspaces: [] };
|
|
33
|
+
return {};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
await installCardCommandDriver(fakeContext(scope), resolver, {
|
|
37
|
+
list: () => [{ id: WORKSPACE_ID, path: "/projects/team" }],
|
|
38
|
+
});
|
|
39
|
+
await scope.update({});
|
|
40
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
41
|
+
calls.length = 0;
|
|
42
|
+
await scope.update({
|
|
43
|
+
command: {
|
|
44
|
+
op: "container_path_set",
|
|
45
|
+
workspace: WORKSPACE_ID,
|
|
46
|
+
container: "default",
|
|
47
|
+
image: "",
|
|
48
|
+
at: 1,
|
|
49
|
+
paths: ["/opt/bin", "/usr/local/bin"],
|
|
50
|
+
mounts: [],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
54
|
+
assert.deepEqual(calls
|
|
55
|
+
.filter(([method]) => method === "setContainerPaths")
|
|
56
|
+
.map(([, request]) => request), [
|
|
57
|
+
{
|
|
58
|
+
workspaceSlug: WORKSPACE_ID,
|
|
59
|
+
container: "default",
|
|
60
|
+
paths: ["/opt/bin", "/usr/local/bin"],
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
assert.deepEqual(pushed, [["/opt/bin", "/usr/local/bin"]]);
|
|
64
|
+
assert.equal(scope.value.command, null);
|
|
65
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
export declare const CONTAINER_NS = "podman";
|
|
3
|
+
export declare const settingsSchema: z<ContainerSettings>;
|
|
4
|
+
export interface ContainerSettings {
|
|
5
|
+
defaultImage: string;
|
|
6
|
+
socketsRoot: string;
|
|
7
|
+
uiLocale: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ContainerSettingsScope {
|
|
10
|
+
get(): ContainerSettings;
|
|
11
|
+
watch(callback: (next: ContainerSettings, prev: ContainerSettings) => void | Promise<void>): () => void;
|
|
12
|
+
update(patch: object): Promise<void>;
|
|
13
|
+
replace(section: object): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
|
+
export const CONTAINER_NS = "podman";
|
|
6
|
+
// Only real preferences live in the settings namespace, so the settings
|
|
7
|
+
// document holds user choices and nothing derived. The live orchestrator state
|
|
8
|
+
// and the card's commands travel over the card route instead (see
|
|
9
|
+
// card-protocol.ts).
|
|
10
|
+
export const settingsSchema = z.object({
|
|
11
|
+
defaultImage: z.string().default(""),
|
|
12
|
+
socketsRoot: z.string().default(""),
|
|
13
|
+
uiLocale: z.string().default(""),
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|