@mycadet/cli 0.12.11 → 0.13.0-dev.202610171617.9ea363e
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 +13 -0
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/package.json +8 -3
- package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
- package/src/__tests__/backup-ops-matching.test.ts +15 -13
- package/src/__tests__/backup-ops.test.ts +105 -0
- package/src/__tests__/backup.test.ts +114 -673
- package/src/__tests__/cloud-releases.test.ts +82 -0
- package/src/__tests__/commands-mock.ts +101 -0
- package/src/__tests__/debug.test.ts +17 -91
- package/src/__tests__/devices.test.ts +2 -2
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/nginx-ingress.test.ts +536 -23
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/paired-lifecycle.test.ts +2 -2
- package/src/__tests__/ps-cloud-status.test.ts +6 -6
- package/src/__tests__/root-backup-docker.test.ts +294 -0
- package/src/__tests__/root-backup.test.ts +527 -0
- package/src/__tests__/root-restore-command.test.ts +147 -0
- package/src/__tests__/root-signing-key.test.ts +50 -0
- package/src/__tests__/setup.test.ts +3 -3
- package/src/__tests__/shell-mode.test.ts +83 -0
- package/src/__tests__/sleep.test.ts +11 -0
- package/src/__tests__/teleport.test.ts +68 -2996
- package/src/__tests__/tunnel.test.ts +10 -2
- package/src/__tests__/upgrade-local.test.ts +21 -11
- package/src/__tests__/upgrade-replay-env.test.ts +2 -2
- package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
- package/src/__tests__/wake.test.ts +4 -1
- package/src/__tests__/windows-lifecycle.test.ts +25 -0
- package/src/commands/backup.ts +44 -399
- package/src/commands/client.ts +3 -3
- package/src/commands/completions.ts +0 -2
- package/src/commands/debug.ts +75 -131
- package/src/commands/devices.ts +51 -17
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/pair.ts +2 -2
- package/src/commands/restore.ts +77 -444
- package/src/commands/retire.ts +2 -3
- package/src/commands/sleep.ts +15 -2
- package/src/commands/teleport.ts +47 -1559
- package/src/commands/tunnel.ts +48 -7
- package/src/commands/upgrade.ts +78 -156
- package/src/commands/wake.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/__tests__/http-client-version.test.ts +29 -0
- package/src/lib/__tests__/runtime-url.test.ts +3 -75
- package/src/lib/__tests__/web-dist.test.ts +27 -1
- package/src/lib/assistant-client.ts +1 -1
- package/src/lib/assistant-config.ts +23 -18
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login.ts +2 -1
- package/src/lib/cloud-releases.ts +54 -10
- package/src/lib/cloud-values.ts +1 -1
- package/src/lib/confirm-action.ts +2 -2
- package/src/lib/docker.ts +59 -22
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +17 -1
- package/src/lib/host-teleport.ts +506 -0
- package/src/lib/http-client.ts +25 -0
- package/src/lib/ingress-config.ts +31 -0
- package/src/lib/live-voice/tunnel.test.ts +1 -1
- package/src/lib/live-voice/tunnel.ts +1 -1
- package/src/lib/local.ts +4 -2
- package/src/lib/nginx-ingress.ts +454 -79
- package/src/lib/process.ts +13 -0
- package/src/lib/root-backup.ts +917 -0
- package/src/lib/root-signing-key.ts +31 -0
- package/src/lib/runtime-url.ts +0 -29
- package/src/lib/teleport-backup.ts +3 -16
- package/src/lib/tunnel.ts +1 -0
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- package/src/__tests__/bundle-staging.test.ts +0 -268
- package/src/__tests__/unpair.test.ts +0 -160
- package/src/commands/rollback.ts +0 -479
- package/src/commands/unpair.ts +0 -122
- package/src/lib/__tests__/job-polling.test.ts +0 -278
- package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
- package/src/lib/bundle-staging.ts +0 -220
- package/src/lib/job-polling.ts +0 -195
- package/src/lib/local-runtime-client.ts +0 -374
|
@@ -102,13 +102,13 @@ describe("paired lifecycle guards", () => {
|
|
|
102
102
|
expect(errors).not.toContain("only works with local and docker");
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
test("retire refuses a paired entry and points to
|
|
105
|
+
test("retire refuses a paired entry and points to the host", async () => {
|
|
106
106
|
process.argv = ["bun", "cadet", "retire", "px", "--yes"];
|
|
107
107
|
const { exited, errors } = await runGuarded(retire);
|
|
108
108
|
|
|
109
109
|
expect(exited).toBe(true);
|
|
110
110
|
expect(errors).toContain("paired from another machine");
|
|
111
|
-
expect(errors).toContain("cadet
|
|
111
|
+
expect(errors).toContain("cadet devices");
|
|
112
112
|
// It must NOT fall through to the generic "Unknown cloud type" path.
|
|
113
113
|
expect(errors).not.toContain("Unknown cloud type");
|
|
114
114
|
});
|
|
@@ -230,13 +230,13 @@ describe("cadet ps — platform status line", () => {
|
|
|
230
230
|
assistantId: "assistant-123",
|
|
231
231
|
name: "Alice",
|
|
232
232
|
runtimeUrl: "https://platform.example",
|
|
233
|
-
cloud: "
|
|
233
|
+
cloud: "cloud",
|
|
234
234
|
},
|
|
235
235
|
{
|
|
236
236
|
assistantId: "assistant-456",
|
|
237
237
|
name: "Bob",
|
|
238
238
|
runtimeUrl: "https://platform.example/b",
|
|
239
|
-
cloud: "
|
|
239
|
+
cloud: "cloud",
|
|
240
240
|
},
|
|
241
241
|
]);
|
|
242
242
|
getActiveAssistantMock.mockReturnValue(null);
|
|
@@ -278,7 +278,7 @@ describe("cadet ps — platform status line", () => {
|
|
|
278
278
|
assistantId: "assistant-123",
|
|
279
279
|
name: "Alice",
|
|
280
280
|
runtimeUrl: "https://platform.example",
|
|
281
|
-
cloud: "
|
|
281
|
+
cloud: "cloud",
|
|
282
282
|
},
|
|
283
283
|
]);
|
|
284
284
|
getActiveAssistantMock.mockReturnValue("assistant-123");
|
|
@@ -287,7 +287,7 @@ describe("cadet ps — platform status line", () => {
|
|
|
287
287
|
|
|
288
288
|
const output = stdout.join("\n");
|
|
289
289
|
expect(output).toContain("* Alice");
|
|
290
|
-
expect(output).toContain("remote:
|
|
290
|
+
expect(output).toContain("remote: cloud");
|
|
291
291
|
expect(output).toContain("https://platform.example");
|
|
292
292
|
expect(output).not.toContain("* assistant-123");
|
|
293
293
|
});
|
|
@@ -298,13 +298,13 @@ describe("cadet ps — platform status line", () => {
|
|
|
298
298
|
assistantId: "assistant-123",
|
|
299
299
|
name: "Alice",
|
|
300
300
|
runtimeUrl: "https://platform.example/a",
|
|
301
|
-
cloud: "
|
|
301
|
+
cloud: "cloud",
|
|
302
302
|
},
|
|
303
303
|
{
|
|
304
304
|
assistantId: "assistant-456",
|
|
305
305
|
name: "Bob",
|
|
306
306
|
runtimeUrl: "https://platform.example/b",
|
|
307
|
-
cloud: "
|
|
307
|
+
cloud: "cloud",
|
|
308
308
|
},
|
|
309
309
|
]);
|
|
310
310
|
getActiveAssistantMock.mockReturnValue("assistant-123");
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, test } from "bun:test";
|
|
2
|
+
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import * as tar from "tar";
|
|
8
|
+
import {
|
|
9
|
+
DockerRootStorage,
|
|
10
|
+
packRoot,
|
|
11
|
+
restoreRootBackup,
|
|
12
|
+
stageRootArchive,
|
|
13
|
+
STATE_ROOTS,
|
|
14
|
+
} from "../lib/root-backup.js";
|
|
15
|
+
import {
|
|
16
|
+
dockerResourceNames,
|
|
17
|
+
sleepContainers,
|
|
18
|
+
startContainers,
|
|
19
|
+
} from "../lib/docker.js";
|
|
20
|
+
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
21
|
+
import { mockCommands } from "./commands-mock.js";
|
|
22
|
+
|
|
23
|
+
const entry: AssistantEntry = {
|
|
24
|
+
assistantId: "docker-backup",
|
|
25
|
+
cloud: "docker",
|
|
26
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
27
|
+
};
|
|
28
|
+
const names = dockerResourceNames(entry.assistantId);
|
|
29
|
+
const containers = [
|
|
30
|
+
names.assistantContainer,
|
|
31
|
+
names.gatewayContainer,
|
|
32
|
+
names.cesContainer,
|
|
33
|
+
];
|
|
34
|
+
const rootContainers = {
|
|
35
|
+
workspace: names.assistantContainer,
|
|
36
|
+
"gateway-security": names.gatewayContainer,
|
|
37
|
+
"ces-security": names.cesContainer,
|
|
38
|
+
"ces-data": names.cesContainer,
|
|
39
|
+
};
|
|
40
|
+
let directory: string;
|
|
41
|
+
let commands: ReturnType<typeof mockCommands>;
|
|
42
|
+
const running = new Map<string, boolean>();
|
|
43
|
+
let writes = 0;
|
|
44
|
+
let failWrite = 0;
|
|
45
|
+
|
|
46
|
+
beforeEach(async () => {
|
|
47
|
+
directory = await mkdtemp(join(tmpdir(), "docker-root-mock-"));
|
|
48
|
+
commands = mockCommands();
|
|
49
|
+
writes = 0;
|
|
50
|
+
failWrite = 0;
|
|
51
|
+
for (const container of containers) running.set(container, true);
|
|
52
|
+
for (const root of STATE_ROOTS) {
|
|
53
|
+
await mkdir(join(directory, root), { mode: 0o700 });
|
|
54
|
+
await writeFile(join(directory, root, "value"), "original", {
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
await writeFile(
|
|
59
|
+
join(directory, "gateway-security", "actor-token-signing-key"),
|
|
60
|
+
Buffer.alloc(32, 1),
|
|
61
|
+
{ mode: 0o600 },
|
|
62
|
+
);
|
|
63
|
+
commands.on("docker", ["info"], {});
|
|
64
|
+
commands.on(
|
|
65
|
+
"docker",
|
|
66
|
+
(args) => args[0] === "inspect",
|
|
67
|
+
({ args }) => {
|
|
68
|
+
const container = args.at(-1)!;
|
|
69
|
+
if (args.includes("--format"))
|
|
70
|
+
return {
|
|
71
|
+
stdout:
|
|
72
|
+
args[2] === "{{.State.Running}}"
|
|
73
|
+
? String(running.get(container))
|
|
74
|
+
: running.get(container)
|
|
75
|
+
? "running"
|
|
76
|
+
: "exited",
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
stdout: JSON.stringify([
|
|
80
|
+
{
|
|
81
|
+
Image: "sha256:service-image",
|
|
82
|
+
State: {
|
|
83
|
+
Running: running.get(container),
|
|
84
|
+
Status: running.get(container) ? "running" : "exited",
|
|
85
|
+
},
|
|
86
|
+
Config: {
|
|
87
|
+
Env: ["RUNTIME_HTTP_PORT=3001", "GATEWAY_PORT=7830"],
|
|
88
|
+
User: "1001:1001",
|
|
89
|
+
},
|
|
90
|
+
Mounts: STATE_ROOTS.filter(
|
|
91
|
+
(root) => rootContainers[root] === container,
|
|
92
|
+
).map((root) => ({
|
|
93
|
+
Type: "volume",
|
|
94
|
+
Name: `remote-${root}`,
|
|
95
|
+
Source: `/remote/${root}`,
|
|
96
|
+
Destination: `/${root}`,
|
|
97
|
+
RW: true,
|
|
98
|
+
})),
|
|
99
|
+
},
|
|
100
|
+
]),
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
commands.on(
|
|
105
|
+
"docker",
|
|
106
|
+
(args) => args[0] === "kill",
|
|
107
|
+
({ args }) => {
|
|
108
|
+
expect(args[1]).toBe("--signal=SIGTERM");
|
|
109
|
+
running.set(args.at(-1)!, false);
|
|
110
|
+
return {};
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
commands.on(
|
|
114
|
+
"docker",
|
|
115
|
+
(args) => args[0] === "start",
|
|
116
|
+
({ args }) => {
|
|
117
|
+
running.set(args[1]!, true);
|
|
118
|
+
return {};
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
commands.on(
|
|
122
|
+
"docker",
|
|
123
|
+
(args) => args[0] === "cp",
|
|
124
|
+
async ({ args }) => {
|
|
125
|
+
const root = args[1]!.split(":/")[1]!.split("/")[0]!;
|
|
126
|
+
await cp(join(directory, root), args[2]!, { recursive: true });
|
|
127
|
+
return {};
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
commands.on(
|
|
131
|
+
"docker",
|
|
132
|
+
(args) => args[0] === "run" && args.includes("-i"),
|
|
133
|
+
async ({ args, input }) => {
|
|
134
|
+
writes++;
|
|
135
|
+
const mount = args[args.indexOf("--mount") + 1]!;
|
|
136
|
+
const root = mount.split("src=remote-")[1]!.split(",")[0]!;
|
|
137
|
+
expect(args).toContain("--network=none");
|
|
138
|
+
expect(args).toContain("--user=0");
|
|
139
|
+
expect(args).toContain("1001:1001");
|
|
140
|
+
expect(mount).not.toContain(directory);
|
|
141
|
+
expect(args[args.indexOf("-ec") + 1]).toContain('chown -R "$1" /restore');
|
|
142
|
+
await rm(join(directory, root), { recursive: true });
|
|
143
|
+
await mkdir(join(directory, root));
|
|
144
|
+
if (writes === failWrite)
|
|
145
|
+
return { code: 1, stderr: "injected volume replacement failure" };
|
|
146
|
+
await pipeline(
|
|
147
|
+
Readable.from(input),
|
|
148
|
+
tar.x({ cwd: join(directory, root), preserveOwner: false }),
|
|
149
|
+
);
|
|
150
|
+
return {};
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
afterEach(async () => {
|
|
155
|
+
commands.restore();
|
|
156
|
+
await rm(directory, { recursive: true, force: true });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
async function archive() {
|
|
160
|
+
const capture = join(directory, "capture");
|
|
161
|
+
await mkdir(capture);
|
|
162
|
+
const storage = new DockerRootStorage(entry);
|
|
163
|
+
await sleepContainers(names, { timeoutMs: 100 });
|
|
164
|
+
await storage.capture(capture);
|
|
165
|
+
const file = join(directory, "backup.tar.gz");
|
|
166
|
+
await packRoot(capture, file);
|
|
167
|
+
return { storage, file };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
test("Docker subprocess round-trip preserves every root, private files and clears stale entries", async () => {
|
|
171
|
+
const { storage, file } = await archive();
|
|
172
|
+
const stage = await stageRootArchive(file, directory);
|
|
173
|
+
for (const root of STATE_ROOTS)
|
|
174
|
+
await writeFile(join(directory, root, "stale"), "delete");
|
|
175
|
+
await storage.replace(join(stage, "state"));
|
|
176
|
+
for (const root of STATE_ROOTS) {
|
|
177
|
+
expect(await readFile(join(directory, root, "value"), "utf8")).toBe(
|
|
178
|
+
"original",
|
|
179
|
+
);
|
|
180
|
+
await expect(readFile(join(directory, root, "stale"))).rejects.toThrow();
|
|
181
|
+
}
|
|
182
|
+
expect(writes).toBe(4);
|
|
183
|
+
expect(commands.calls.filter((call) => call.args[0] === "kill")).toHaveLength(
|
|
184
|
+
3,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("failure during Docker replacement recovers prior roots before restarting", async () => {
|
|
189
|
+
const { storage, file } = await archive();
|
|
190
|
+
for (const container of containers) running.set(container, true);
|
|
191
|
+
for (const root of STATE_ROOTS)
|
|
192
|
+
await writeFile(join(directory, root, "value"), "destination");
|
|
193
|
+
failWrite = 2;
|
|
194
|
+
const recovery = join(directory, "recovery.tar.gz");
|
|
195
|
+
await expect(
|
|
196
|
+
restoreRootBackup(
|
|
197
|
+
entry,
|
|
198
|
+
file,
|
|
199
|
+
{ recoveryPath: recovery },
|
|
200
|
+
{
|
|
201
|
+
storage,
|
|
202
|
+
sleep: () => sleepContainers(names, { timeoutMs: 100 }),
|
|
203
|
+
wake: async () => {
|
|
204
|
+
for (const container of containers) running.set(container, true);
|
|
205
|
+
},
|
|
206
|
+
save: () => {},
|
|
207
|
+
},
|
|
208
|
+
),
|
|
209
|
+
).rejects.toThrow("injected volume replacement failure");
|
|
210
|
+
expect(await storage.running()).toBe(true);
|
|
211
|
+
const previous = await stageRootArchive(recovery, directory);
|
|
212
|
+
for (const root of STATE_ROOTS) {
|
|
213
|
+
expect(await readFile(join(directory, root, "value"), "utf8")).toBe(
|
|
214
|
+
"destination",
|
|
215
|
+
);
|
|
216
|
+
expect(await readFile(join(previous, "state", root, "value"), "utf8")).toBe(
|
|
217
|
+
"destination",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("restart probes gateway and assistant on their own service ports", async () => {
|
|
223
|
+
const storage = new DockerRootStorage(entry);
|
|
224
|
+
expect(await storage.running()).toBe(true);
|
|
225
|
+
for (const container of containers) running.set(container, false);
|
|
226
|
+
commands.on(
|
|
227
|
+
"docker",
|
|
228
|
+
(args) => args[0] === "exec",
|
|
229
|
+
() => ({ stdout: "ready" }),
|
|
230
|
+
);
|
|
231
|
+
await storage.resume();
|
|
232
|
+
const probes = commands.calls.filter((call) => call.args[0] === "exec");
|
|
233
|
+
expect(
|
|
234
|
+
probes.find((call) => call.args[1] === names.gatewayContainer)?.args.at(-1),
|
|
235
|
+
).toBe("http://127.0.0.1:7830/healthz");
|
|
236
|
+
expect(
|
|
237
|
+
probes
|
|
238
|
+
.find((call) => call.args[1] === names.assistantContainer)
|
|
239
|
+
?.args.at(-1),
|
|
240
|
+
).toBe("http://127.0.0.1:3001/readyz");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("default Docker sleep aborts a graceful timeout without forcing termination", async () => {
|
|
244
|
+
commands.on(
|
|
245
|
+
"docker",
|
|
246
|
+
(args) => args[0] === "kill",
|
|
247
|
+
() => ({}),
|
|
248
|
+
);
|
|
249
|
+
await expect(sleepContainers(names, { timeoutMs: 0 })).rejects.toThrow(
|
|
250
|
+
"did not stop gracefully",
|
|
251
|
+
);
|
|
252
|
+
expect(running.get(names.assistantContainer)).toBe(true);
|
|
253
|
+
expect(commands.calls.some((call) => call.args[0] === "stop")).toBe(false);
|
|
254
|
+
expect(
|
|
255
|
+
commands.calls
|
|
256
|
+
.filter((call) => call.args[0] === "kill")
|
|
257
|
+
.every((call) => call.args.includes("--signal=SIGTERM")),
|
|
258
|
+
).toBe(true);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("create-only provisioning creates every target container without starting services", async () => {
|
|
262
|
+
commands.on(
|
|
263
|
+
"docker",
|
|
264
|
+
(args) => args[0] === "create",
|
|
265
|
+
({ args }) => {
|
|
266
|
+
running.set(args[args.indexOf("--name") + 1]!, false);
|
|
267
|
+
return { stdout: "created" };
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
await startContainers(
|
|
271
|
+
{
|
|
272
|
+
createOnly: true,
|
|
273
|
+
gatewayPort: 7830,
|
|
274
|
+
assistantPort: 3001,
|
|
275
|
+
imageTags: {
|
|
276
|
+
assistant: "assistant:target",
|
|
277
|
+
gateway: "gateway:target",
|
|
278
|
+
"credential-executor": "ces:target",
|
|
279
|
+
},
|
|
280
|
+
instanceName: entry.assistantId,
|
|
281
|
+
res: names,
|
|
282
|
+
},
|
|
283
|
+
() => {},
|
|
284
|
+
);
|
|
285
|
+
const creates = commands.calls.filter((call) => call.args[0] === "create");
|
|
286
|
+
expect(creates).toHaveLength(3);
|
|
287
|
+
expect(creates.every((call) => !call.args.includes("-d"))).toBe(true);
|
|
288
|
+
expect(
|
|
289
|
+
commands.calls.some(
|
|
290
|
+
(call) => call.args[0] === "start" || call.args[0] === "run",
|
|
291
|
+
),
|
|
292
|
+
).toBe(false);
|
|
293
|
+
expect([...running.values()].every((value) => !value)).toBe(true);
|
|
294
|
+
});
|