@mycadet/cli 0.12.10 → 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.
Files changed (111) hide show
  1. package/README.md +13 -0
  2. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  3. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  4. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  5. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  6. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  7. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  8. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  9. package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  10. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  11. package/package.json +8 -3
  12. package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
  13. package/src/__tests__/backup-ops-matching.test.ts +15 -13
  14. package/src/__tests__/backup-ops.test.ts +105 -0
  15. package/src/__tests__/backup.test.ts +114 -673
  16. package/src/__tests__/cloud-login-poll.test.ts +69 -0
  17. package/src/__tests__/cloud-releases.test.ts +82 -0
  18. package/src/__tests__/commands-mock.ts +101 -0
  19. package/src/__tests__/debug.test.ts +17 -91
  20. package/src/__tests__/devices.test.ts +64 -2
  21. package/src/__tests__/ensure-binary.test.ts +52 -0
  22. package/src/__tests__/gateway-token.test.ts +267 -0
  23. package/src/__tests__/hatch-next-steps.test.ts +38 -0
  24. package/src/__tests__/host-teleport-docker.test.ts +371 -0
  25. package/src/__tests__/host-teleport.test.ts +405 -0
  26. package/src/__tests__/multi-local.test.ts +3 -3
  27. package/src/__tests__/nginx-ingress.test.ts +587 -36
  28. package/src/__tests__/orphan-detection.test.ts +1 -1
  29. package/src/__tests__/paired-lifecycle.test.ts +2 -2
  30. package/src/__tests__/ps-cloud-status.test.ts +8 -7
  31. package/src/__tests__/root-backup-docker.test.ts +294 -0
  32. package/src/__tests__/root-backup.test.ts +527 -0
  33. package/src/__tests__/root-restore-command.test.ts +147 -0
  34. package/src/__tests__/root-signing-key.test.ts +50 -0
  35. package/src/__tests__/setup.test.ts +17 -7
  36. package/src/__tests__/shell-mode.test.ts +83 -0
  37. package/src/__tests__/sleep.test.ts +11 -0
  38. package/src/__tests__/teleport.test.ts +68 -2996
  39. package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
  40. package/src/__tests__/tunnel-stop.test.ts +226 -0
  41. package/src/__tests__/tunnel.test.ts +22 -4
  42. package/src/__tests__/upgrade-local.test.ts +21 -11
  43. package/src/__tests__/upgrade-replay-env.test.ts +2 -2
  44. package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
  45. package/src/__tests__/wake.test.ts +4 -1
  46. package/src/__tests__/windows-lifecycle.test.ts +25 -0
  47. package/src/commands/backup.ts +44 -399
  48. package/src/commands/client.ts +3 -3
  49. package/src/commands/completions.ts +0 -2
  50. package/src/commands/debug.ts +75 -131
  51. package/src/commands/devices.ts +117 -47
  52. package/src/commands/gateway/token.ts +77 -23
  53. package/src/commands/hatch.ts +10 -52
  54. package/src/commands/pair.ts +9 -3
  55. package/src/commands/ps.ts +4 -2
  56. package/src/commands/restore.ts +77 -444
  57. package/src/commands/retire.ts +2 -3
  58. package/src/commands/sleep.ts +15 -2
  59. package/src/commands/teleport.ts +47 -1559
  60. package/src/commands/tunnel.ts +91 -11
  61. package/src/commands/upgrade.ts +84 -157
  62. package/src/commands/wake.ts +1 -1
  63. package/src/index.ts +0 -4
  64. package/src/lib/__tests__/http-client-version.test.ts +29 -0
  65. package/src/lib/__tests__/runtime-url.test.ts +3 -75
  66. package/src/lib/__tests__/web-dist.test.ts +27 -1
  67. package/src/lib/assistant-client.ts +1 -1
  68. package/src/lib/assistant-config.ts +43 -23
  69. package/src/lib/backup-ops.ts +37 -251
  70. package/src/lib/cloud-client.ts +10 -13
  71. package/src/lib/cloud-login-poll.ts +75 -0
  72. package/src/lib/cloud-login.ts +5 -2
  73. package/src/lib/cloud-releases.ts +54 -10
  74. package/src/lib/cloud-values.ts +1 -1
  75. package/src/lib/confirm-action.ts +2 -2
  76. package/src/lib/docker.ts +65 -34
  77. package/src/lib/ensure-binary.ts +65 -6
  78. package/src/lib/github-repository.ts +2 -0
  79. package/src/lib/guardian-token.ts +1 -23
  80. package/src/lib/hatch-local.ts +18 -52
  81. package/src/lib/hatch-next-steps.ts +15 -2
  82. package/src/lib/host-teleport.ts +506 -0
  83. package/src/lib/http-client.ts +25 -0
  84. package/src/lib/ingress-config.ts +31 -0
  85. package/src/lib/live-voice/tunnel.test.ts +1 -1
  86. package/src/lib/live-voice/tunnel.ts +3 -3
  87. package/src/lib/local.ts +10 -8
  88. package/src/lib/nginx-ingress.ts +572 -95
  89. package/src/lib/process.ts +13 -0
  90. package/src/lib/root-backup.ts +917 -0
  91. package/src/lib/root-signing-key.ts +31 -0
  92. package/src/lib/runtime-url.ts +0 -29
  93. package/src/lib/teleport-backup.ts +3 -16
  94. package/src/lib/tunnel.ts +87 -34
  95. package/src/lib/tunnels/cadet.ts +12 -1
  96. package/src/lib/tunnels/cloudflare.ts +36 -1
  97. package/src/lib/tunnels/ngrok.ts +16 -2
  98. package/src/lib/tunnels/process-lifecycle.ts +26 -0
  99. package/src/lib/tunnels/tailscale.ts +79 -1
  100. package/src/lib/upgrade-lifecycle.ts +63 -141
  101. package/src/lib/web-dist.ts +79 -1
  102. package/src/lib/xdg-log.ts +11 -0
  103. package/src/__tests__/bundle-staging.test.ts +0 -268
  104. package/src/__tests__/unpair.test.ts +0 -160
  105. package/src/commands/rollback.ts +0 -479
  106. package/src/commands/unpair.ts +0 -122
  107. package/src/lib/__tests__/job-polling.test.ts +0 -278
  108. package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
  109. package/src/lib/bundle-staging.ts +0 -220
  110. package/src/lib/job-polling.ts +0 -195
  111. package/src/lib/local-runtime-client.ts +0 -374
@@ -0,0 +1,405 @@
1
+ import { afterEach, beforeEach, expect, mock, spyOn, test } from "bun:test";
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ findAssistantByName,
7
+ saveAssistantEntry,
8
+ type AssistantEntry,
9
+ } from "../lib/assistant-config.js";
10
+ import {
11
+ teleportHostInstallation,
12
+ TeleportRecoveryError,
13
+ type HostTeleportOps,
14
+ } from "../lib/host-teleport.js";
15
+ import {
16
+ createRootBackup,
17
+ LocalRootStorage,
18
+ restoreRootBackup,
19
+ STATE_ROOTS,
20
+ } from "../lib/root-backup.js";
21
+
22
+ let directory: string,
23
+ source: AssistantEntry,
24
+ target: AssistantEntry,
25
+ ops: HostTeleportOps;
26
+ let order: string[],
27
+ running: Map<string, boolean>,
28
+ stores: Map<string, LocalRootStorage>;
29
+ let log: ReturnType<typeof spyOn>, previousLockDir: string | undefined;
30
+ const options = { dryRun: false, keepSource: false };
31
+ async function instance(
32
+ name: string,
33
+ cloud: "local" | "docker",
34
+ byte: number,
35
+ ): Promise<AssistantEntry> {
36
+ const root = join(directory, name);
37
+ for (const state of STATE_ROOTS) {
38
+ await mkdir(join(root, state), { recursive: true });
39
+ await writeFile(join(root, state, "state"), `${name}:${state}`, {
40
+ mode: 0o600,
41
+ });
42
+ }
43
+ await writeFile(
44
+ join(root, "gateway-security", "actor-token-signing-key"),
45
+ Buffer.alloc(32, byte),
46
+ );
47
+ await writeFile(
48
+ join(root, "ces-security", "keys.enc"),
49
+ Buffer.from([byte, 0, 255]),
50
+ );
51
+ await writeFile(
52
+ join(root, "ces-security", "store.key"),
53
+ Buffer.alloc(32, byte + 1),
54
+ );
55
+ return {
56
+ assistantId: name,
57
+ cloud,
58
+ runtimeUrl: "http://127.0.0.1:7830",
59
+ resources: {
60
+ instanceDir: root,
61
+ daemonPort: 1,
62
+ gatewayPort: 2,
63
+ cesPort: 3,
64
+ qdrantPort: 4,
65
+ signingKey: byte.toString(16).padStart(2, "0").repeat(32),
66
+ },
67
+ };
68
+ }
69
+ const previousDataHome = process.env.XDG_DATA_HOME;
70
+ beforeEach(async () => {
71
+ directory = await mkdtemp(join(tmpdir(), "host-teleport-"));
72
+ process.env.XDG_DATA_HOME = directory;
73
+ previousLockDir = process.env.CADET_LOCKFILE_DIR;
74
+ process.env.CADET_LOCKFILE_DIR = join(directory, "registry");
75
+ source = await instance("source", "docker", 7);
76
+ target = await instance("target", "local", 3);
77
+ saveAssistantEntry(source);
78
+ saveAssistantEntry(target);
79
+ order = [];
80
+ running = new Map([
81
+ ["source", true],
82
+ ["target", true],
83
+ ]);
84
+ stores = new Map();
85
+ log = spyOn(console, "log").mockImplementation(() => {});
86
+ ops = {
87
+ storage: (entry) => {
88
+ let storage = stores.get(entry.assistantId);
89
+ if (!storage) {
90
+ storage = new LocalRootStorage(entry);
91
+ storage.running = async () => {
92
+ storage!.initialRunning ??= new Set(
93
+ running.get(entry.assistantId)
94
+ ? ["assistant", "gateway", "ces"]
95
+ : [],
96
+ );
97
+ return running.get(entry.assistantId) ?? false;
98
+ };
99
+ storage.resume = async () => {
100
+ order.push(`wake:${entry.assistantId}`);
101
+ running.set(entry.assistantId, true);
102
+ };
103
+ stores.set(entry.assistantId, storage);
104
+ }
105
+ return storage;
106
+ },
107
+ sleep: mock(async (entry) => {
108
+ order.push(`sleep:${entry.assistantId}`);
109
+ running.set(entry.assistantId, false);
110
+ }),
111
+ backup: mock(async (entry) => {
112
+ const file = join(directory, `${entry.assistantId}.tar.gz`),
113
+ storage = ops.storage(entry);
114
+ await createRootBackup({ ...entry, cloud: "local" }, file, {
115
+ storage,
116
+ sleep: () => ops.sleep(entry),
117
+ wake: () => storage.resume(),
118
+ save: saveAssistantEntry,
119
+ });
120
+ order.push(`backup:${entry.assistantId}`);
121
+ return file;
122
+ }),
123
+ version: mock(async () => "0.12.9"),
124
+ hatch: mock(async () => target),
125
+ restore: mock(async (entry, archive) => {
126
+ const storage = ops.storage(entry);
127
+ await restoreRootBackup(
128
+ entry,
129
+ archive,
130
+ { resume: false, recoveryPath: join(directory, "pre-restore.tar.gz") },
131
+ {
132
+ storage,
133
+ sleep: () => ops.sleep(entry),
134
+ wake: () => storage.resume(),
135
+ save: saveAssistantEntry,
136
+ },
137
+ );
138
+ await storage.resume();
139
+ order.push(`restore:${entry.assistantId}`);
140
+ }),
141
+ retire: mock(async (entry) => {
142
+ order.push(`retire:${entry.assistantId}`);
143
+ }),
144
+ };
145
+ });
146
+ afterEach(async () => {
147
+ if (previousDataHome === undefined) {
148
+ delete process.env.XDG_DATA_HOME;
149
+ } else {
150
+ process.env.XDG_DATA_HOME = previousDataHome;
151
+ }
152
+ log.mockRestore();
153
+ if (previousLockDir === undefined) {
154
+ delete process.env.CADET_LOCKFILE_DIR;
155
+ } else {
156
+ process.env.CADET_LOCKFILE_DIR = previousLockDir;
157
+ }
158
+ await rm(directory, { recursive: true, force: true });
159
+ });
160
+
161
+ test("moves all four roots and encrypted key pairs using the exact stopped-source safety tar", async () => {
162
+ await teleportHostInstallation(source, "local", "target", options, ops);
163
+ for (const state of STATE_ROOTS) {
164
+ expect(
165
+ await readFile(
166
+ join(target.resources!.instanceDir, state, "state"),
167
+ "utf8",
168
+ ),
169
+ ).toBe(`source:${state}`);
170
+ }
171
+ expect(
172
+ await readFile(
173
+ join(target.resources!.instanceDir, "ces-security", "keys.enc"),
174
+ ),
175
+ ).toEqual(Buffer.from([7, 0, 255]));
176
+ expect(
177
+ await readFile(
178
+ join(target.resources!.instanceDir, "ces-security", "store.key"),
179
+ ),
180
+ ).toEqual(Buffer.alloc(32, 8));
181
+ expect(findAssistantByName("target")!.resources!.signingKey).toBe(
182
+ "07".repeat(32),
183
+ );
184
+ expect(order).toEqual([
185
+ "sleep:source",
186
+ "backup:source",
187
+ "sleep:target",
188
+ "backup:target",
189
+ "wake:target",
190
+ "restore:target",
191
+ "retire:source",
192
+ ]);
193
+ expect(ops.restore).toHaveBeenCalledWith(
194
+ expect.objectContaining({ assistantId: "target" }),
195
+ join(directory, "source.tar.gz"),
196
+ join(directory, "target.tar.gz"),
197
+ );
198
+ });
199
+ test("dry run creates no archives and does not stop or hatch services", async () => {
200
+ await teleportHostInstallation(
201
+ source,
202
+ "local",
203
+ "target",
204
+ { ...options, dryRun: true },
205
+ ops,
206
+ );
207
+ expect(order).toEqual([]);
208
+ expect(ops.version).not.toHaveBeenCalled();
209
+ expect(ops.backup).not.toHaveBeenCalled();
210
+ expect(ops.hatch).not.toHaveBeenCalled();
211
+ });
212
+ test("keep-source retains the stopped original", async () => {
213
+ await teleportHostInstallation(
214
+ source,
215
+ "local",
216
+ "target",
217
+ { ...options, keepSource: true },
218
+ ops,
219
+ );
220
+ expect(running.get("source")).toBe(false);
221
+ expect(ops.retire).not.toHaveBeenCalled();
222
+ });
223
+ test("source backup failure wakes source without touching target", async () => {
224
+ ops.backup = mock(async () => {
225
+ throw new Error("unreadable credential store");
226
+ });
227
+ await expect(
228
+ teleportHostInstallation(source, "local", "target", options, ops),
229
+ ).rejects.toThrow("unreadable credential store");
230
+ expect(order).toEqual(["sleep:source", "wake:source"]);
231
+ expect(ops.restore).not.toHaveBeenCalled();
232
+ expect(ops.retire).not.toHaveBeenCalled();
233
+ });
234
+ test("graceful shutdown failure never captures a live installation", async () => {
235
+ ops.sleep = mock(async () => {
236
+ throw new Error("graceful timeout");
237
+ });
238
+ await expect(
239
+ teleportHostInstallation(source, "local", "target", options, ops),
240
+ ).rejects.toThrow("graceful timeout");
241
+ expect(ops.backup).not.toHaveBeenCalled();
242
+ expect(ops.retire).not.toHaveBeenCalled();
243
+ });
244
+ test("source that fails to stop is rejected even if sleep returned", async () => {
245
+ ops.sleep = mock(async () => {});
246
+ await expect(
247
+ teleportHostInstallation(source, "local", "target", options, ops),
248
+ ).rejects.toThrow("still running");
249
+ expect(ops.backup).not.toHaveBeenCalled();
250
+ });
251
+ test("target capture failure resumes both original installations", async () => {
252
+ const backup = ops.backup;
253
+ ops.backup = mock(async (entry) => {
254
+ if (entry.assistantId === target.assistantId) {
255
+ throw new Error("target capture failed");
256
+ }
257
+ return backup(entry);
258
+ });
259
+ await expect(
260
+ teleportHostInstallation(source, "local", "target", options, ops),
261
+ ).rejects.toThrow("target capture failed");
262
+ expect(order.slice(-2)).toEqual(["wake:target", "wake:source"]);
263
+ expect(ops.restore).not.toHaveBeenCalled();
264
+ });
265
+ test("failed import retains source and resumes recovered target", async () => {
266
+ ops.restore = mock(async () => {
267
+ throw new Error("import failed; original data recovered");
268
+ });
269
+ await expect(
270
+ teleportHostInstallation(source, "local", "target", options, ops),
271
+ ).rejects.toThrow("import failed");
272
+ expect(ops.retire).not.toHaveBeenCalled();
273
+ expect(order.slice(-2)).toEqual(["wake:target", "wake:source"]);
274
+ });
275
+ test("failed recovery leaves damaged target stopped", async () => {
276
+ ops.restore = mock(async () => {
277
+ throw new TeleportRecoveryError([], "recovery failed");
278
+ });
279
+ await expect(
280
+ teleportHostInstallation(source, "local", "target", options, ops),
281
+ ).rejects.toThrow("recovery failed");
282
+ expect(running.get("target")).toBe(false);
283
+ expect(order).not.toContain("wake:target");
284
+ expect(ops.retire).not.toHaveBeenCalled();
285
+ });
286
+ test("originally sleeping source stays asleep on capture failure", async () => {
287
+ running.set("source", false);
288
+ ops.backup = mock(async () => {
289
+ throw new Error("capture failed");
290
+ });
291
+ await expect(
292
+ teleportHostInstallation(source, "local", "target", options, ops),
293
+ ).rejects.toThrow("capture failed");
294
+ expect(order).not.toContain("wake:source");
295
+ });
296
+ test("new target receives safety tar at hatch and becomes ready before source retirement", async () => {
297
+ await teleportHostInstallation(source, "local", "fresh-target", options, ops);
298
+ expect(ops.hatch).toHaveBeenCalledWith(
299
+ "local",
300
+ "fresh-target",
301
+ join(directory, "source.tar.gz"),
302
+ );
303
+ expect(order.indexOf("wake:target")).toBeLessThan(
304
+ order.indexOf("retire:source"),
305
+ );
306
+ expect(ops.restore).not.toHaveBeenCalled();
307
+ });
308
+ test("failed new target readiness cleans it up before waking source", async () => {
309
+ ops.storage(target).resume = async () => {
310
+ throw new Error("migration failed");
311
+ };
312
+ await expect(
313
+ teleportHostInstallation(source, "local", "fresh-target", options, ops),
314
+ ).rejects.toThrow("migration failed");
315
+ expect(order.slice(-3)).toEqual([
316
+ "sleep:target",
317
+ "retire:target",
318
+ "wake:source",
319
+ ]);
320
+ expect(order).not.toContain("retire:source");
321
+ });
322
+ test("hatch failure retains tar and wakes source", async () => {
323
+ ops.hatch = mock(async () => {
324
+ throw new Error("image pull failed");
325
+ });
326
+ await expect(
327
+ teleportHostInstallation(source, "local", "fresh-target", options, ops),
328
+ ).rejects.toThrow("image pull failed");
329
+ expect(order.at(-1)).toBe("wake:source");
330
+ expect(await readFile(join(directory, "source.tar.gz"))).not.toHaveLength(0);
331
+ expect(ops.retire).not.toHaveBeenCalled();
332
+ });
333
+ test("multi-patch downgrade fails before shutdown", async () => {
334
+ ops.version = mock(async (entry) => (entry === source ? "0.12.9" : "0.12.5"));
335
+ await expect(
336
+ teleportHostInstallation(source, "local", "target", options, ops),
337
+ ).rejects.toThrow("older than source");
338
+ expect(order).toEqual([]);
339
+ });
340
+ test("target environment mismatch fails before shutdown", async () => {
341
+ await expect(
342
+ teleportHostInstallation(source, "docker", "target", options, ops),
343
+ ).rejects.toThrow("not docker");
344
+ expect(order).toEqual([]);
345
+ });
346
+ test("source restarted during transfer is retained to preserve newer writes", async () => {
347
+ const restore = ops.restore;
348
+ ops.restore = mock(async (entry, archive, recovery) => {
349
+ await restore(entry, archive, recovery);
350
+ running.set("source", true);
351
+ });
352
+ await expect(
353
+ teleportHostInstallation(source, "local", "target", options, ops),
354
+ ).rejects.toThrow("restarted during teleport");
355
+ expect(ops.retire).not.toHaveBeenCalled();
356
+ expect(order).not.toContain("wake:source");
357
+ });
358
+
359
+ test("holds source and target operation locks through import and releases them afterward", async () => {
360
+ let release!: () => void;
361
+ const blocked = new Promise<void>((resolve) => {
362
+ release = resolve;
363
+ });
364
+ let entered!: () => void;
365
+ const importing = new Promise<void>((resolve) => {
366
+ entered = resolve;
367
+ });
368
+ const restore = ops.restore;
369
+ ops.restore = mock(async (entry, archive, recovery) => {
370
+ entered();
371
+ await blocked;
372
+ await restore(entry, archive, recovery);
373
+ });
374
+ const transfer = teleportHostInstallation(
375
+ source,
376
+ "local",
377
+ "target",
378
+ options,
379
+ ops,
380
+ );
381
+ await importing;
382
+ try {
383
+ for (const entry of [source, target]) {
384
+ const storage = ops.storage(entry);
385
+ await expect(
386
+ createRootBackup(entry, join(directory, "competing.tar.gz"), {
387
+ storage,
388
+ sleep: () => ops.sleep(entry),
389
+ wake: () => storage.resume(),
390
+ save: saveAssistantEntry,
391
+ }),
392
+ ).rejects.toThrow("Another backup/restore owns");
393
+ }
394
+ } finally {
395
+ release();
396
+ }
397
+ await transfer;
398
+ const storage = ops.storage(source);
399
+ await createRootBackup(source, join(directory, "after.tar.gz"), {
400
+ storage,
401
+ sleep: () => ops.sleep(source),
402
+ wake: () => storage.resume(),
403
+ save: saveAssistantEntry,
404
+ });
405
+ });
@@ -43,14 +43,14 @@ afterAll(() => {
43
43
 
44
44
  function writeLockfile(data: unknown): void {
45
45
  writeFileSync(
46
- join(testDir, ".cadet.lock.json"),
46
+ join(testDir, "lockfile.json"),
47
47
  JSON.stringify(data, null, 2),
48
48
  );
49
49
  }
50
50
 
51
51
  function readLockfileRaw(): Record<string, unknown> {
52
52
  return JSON.parse(
53
- readFileSync(join(testDir, ".cadet.lock.json"), "utf-8"),
53
+ readFileSync(join(testDir, "lockfile.json"), "utf-8"),
54
54
  ) as Record<string, unknown>;
55
55
  }
56
56
 
@@ -67,7 +67,7 @@ const makeEntry = (
67
67
 
68
68
  function resetLockfile(): void {
69
69
  try {
70
- rmSync(join(testDir, ".cadet.lock.json"));
70
+ rmSync(join(testDir, "lockfile.json"));
71
71
  } catch {
72
72
  // file may not exist
73
73
  }