@jango-blockchained/hoox-cli 0.4.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jango-blockchained/hoox-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@jango-blockchained/hoox-cli)",
5
5
  "type": "module",
6
6
  "main": "./bin/hoox.js",
@@ -79,10 +79,11 @@ function updateWranglerVars(
79
79
  return;
80
80
  }
81
81
 
82
- const edits = jsonc.modify(content, ["vars"], vars, {
82
+ const fresh = readFileSync(filePath, "utf-8");
83
+ const edits = jsonc.modify(fresh, ["vars"], vars, {
83
84
  formattingOptions: { tabSize: 2, insertSpaces: true },
84
85
  });
85
- const updated = jsonc.applyEdits(content, edits);
86
+ const updated = jsonc.applyEdits(fresh, edits);
86
87
  writeFileSync(filePath, updated, "utf-8");
87
88
 
88
89
  formatSuccess(
@@ -697,10 +697,11 @@ async function doUpdateInternalUrls(fmt: FormatOptions): Promise<void> {
697
697
  return;
698
698
  }
699
699
 
700
- const edits = jsonc.modify(content, ["vars"], vars, {
700
+ const fresh = readFileSync(filePath, "utf-8");
701
+ const edits = jsonc.modify(fresh, ["vars"], vars, {
701
702
  formattingOptions: { tabSize: 2, insertSpaces: true },
702
703
  });
703
- writeFileSync(filePath, jsonc.applyEdits(content, edits), "utf-8");
704
+ writeFileSync(filePath, jsonc.applyEdits(fresh, edits), "utf-8");
704
705
  formatSuccess(
705
706
  `Updated ${changesCount} service URL(s) in dashboard wrangler.jsonc`,
706
707
  fmt
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { CLIProvisioner } from "../cli-provisioner";
3
+
4
+ describe("CLIProvisioner", () => {
5
+ it("check returns expected resources", async () => {
6
+ const provisioner = new CLIProvisioner();
7
+ const result = await provisioner.check({
8
+ d1Databases: ["hoox-db"],
9
+ kvNamespaces: ["CONFIG_KV"],
10
+ r2Buckets: [],
11
+ queues: [],
12
+ });
13
+ expect(result.success).toBe(true);
14
+ expect(result.created).toContain("D1:hoox-db");
15
+ expect(result.created).toContain("KV:CONFIG_KV");
16
+ });
17
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * CLI implementation of the Provisioner interface.
3
+ * Uses `wrangler` CLI via Bun.spawn to create Cloudflare resources.
4
+ */
5
+ import type {
6
+ Provisioner,
7
+ ProvisioningPlan,
8
+ ProvisionResult,
9
+ } from "@jango-blockchained/hoox-shared";
10
+
11
+ export class CLIProvisioner implements Provisioner {
12
+ async provision(plan: ProvisioningPlan): Promise<ProvisionResult> {
13
+ const created: string[] = [];
14
+ const errors: string[] = [];
15
+
16
+ for (const db of plan.d1Databases) {
17
+ try {
18
+ const proc = Bun.spawn(["wrangler", "d1", "create", db], {
19
+ stdout: "pipe",
20
+ stderr: "pipe",
21
+ });
22
+ await new Response(proc.stdout).text();
23
+ const exitCode = await proc.exited;
24
+ if (exitCode === 0) {
25
+ created.push(`D1:${db}`);
26
+ } else {
27
+ const err = await new Response(proc.stderr).text();
28
+ errors.push(`D1:${db} — ${err.trim() || `exit code ${exitCode}`}`);
29
+ }
30
+ } catch (e) {
31
+ errors.push(`D1:${db} — ${(e as Error).message}`);
32
+ }
33
+ }
34
+
35
+ for (const ns of plan.kvNamespaces) {
36
+ try {
37
+ const proc = Bun.spawn(["wrangler", "kv", "namespace", "create", ns], {
38
+ stdout: "pipe",
39
+ stderr: "pipe",
40
+ });
41
+ await new Response(proc.stdout).text();
42
+ const exitCode = await proc.exited;
43
+ if (exitCode === 0) {
44
+ created.push(`KV:${ns}`);
45
+ } else {
46
+ const err = await new Response(proc.stderr).text();
47
+ errors.push(`KV:${ns} — ${err.trim() || `exit code ${exitCode}`}`);
48
+ }
49
+ } catch (e) {
50
+ errors.push(`KV:${ns} — ${(e as Error).message}`);
51
+ }
52
+ }
53
+
54
+ return {
55
+ success: errors.length === 0,
56
+ created,
57
+ errors,
58
+ };
59
+ }
60
+
61
+ async check(plan: ProvisioningPlan): Promise<ProvisionResult> {
62
+ // Dry-run: just report what would be created
63
+ const created: string[] = [
64
+ ...plan.d1Databases.map((d) => `D1:${d}`),
65
+ ...plan.kvNamespaces.map((k) => `KV:${k}`),
66
+ ...plan.r2Buckets.map((b) => `R2:${b}`),
67
+ ...plan.queues.map((q) => `Queue:${q}`),
68
+ ];
69
+ return { success: true, created, errors: [] };
70
+ }
71
+ }
@@ -35,6 +35,7 @@ interface CapturedCalls {
35
35
  passwordMessages: string[];
36
36
  textMessages: string[];
37
37
  multiselectMessages: string[];
38
+ selectMessages: string[];
38
39
  confirmMessages: string[];
39
40
  cancelMessages: string[];
40
41
  logInfo: string[];
@@ -54,6 +55,7 @@ function makeCapture(): CapturedCalls {
54
55
  passwordMessages: [],
55
56
  textMessages: [],
56
57
  multiselectMessages: [],
58
+ selectMessages: [],
57
59
  confirmMessages: [],
58
60
  cancelMessages: [],
59
61
  logInfo: [],
@@ -72,7 +74,9 @@ interface PromptResponses {
72
74
  password?: string | symbol;
73
75
  text?: string | symbol;
74
76
  multiselect?: string[] | symbol;
75
- confirm?: boolean | symbol;
77
+ select?: string | symbol;
78
+ /** Confirm responses returned in order (call-by-call). Defaults are `[true, false, false]`. */
79
+ confirmSequence?: boolean[];
76
80
  /** Group responses keyed by field name */
77
81
  group?: Record<string, string>;
78
82
  }
@@ -81,7 +85,8 @@ const defaultResponses: Required<PromptResponses> = {
81
85
  password: "test-token",
82
86
  text: "test-account",
83
87
  multiselect: [],
84
- confirm: true,
88
+ select: "minimal",
89
+ confirmSequence: [true, false, false], // risk → provisioning → deploy
85
90
  group: {},
86
91
  };
87
92
 
@@ -92,6 +97,7 @@ interface CallCounters {
92
97
  password: number;
93
98
  text: number;
94
99
  multiselect: number;
100
+ select: number;
95
101
  confirm: number;
96
102
  }
97
103
 
@@ -99,11 +105,12 @@ let counters: CallCounters = {
99
105
  password: 0,
100
106
  text: 0,
101
107
  multiselect: 0,
108
+ select: 0,
102
109
  confirm: 0,
103
110
  };
104
111
 
105
112
  function resetCounters(): void {
106
- counters = { password: 0, text: 0, multiselect: 0, confirm: 0 };
113
+ counters = { password: 0, text: 0, multiselect: 0, select: 0, confirm: 0 };
107
114
  }
108
115
 
109
116
  /** Whether to simulate cancellation on next check. */
@@ -182,13 +189,26 @@ function installClackSpies(): void {
182
189
  : (responses.multiselect ?? defaultResponses.multiselect);
183
190
  }
184
191
  );
192
+ spyOn(clack, "select").mockImplementation(
193
+ async (opts: {
194
+ message: string;
195
+ options: { value: string; label: string; hint?: string }[];
196
+ }) => {
197
+ captured.selectMessages.push(opts.message);
198
+ counters.select++;
199
+ return simulateCancel
200
+ ? Symbol.for("clack.cancel")
201
+ : (responses.select ?? defaultResponses.select);
202
+ }
203
+ );
185
204
  spyOn(clack, "confirm").mockImplementation(
186
205
  async (opts: { message: string; initialValue?: boolean }) => {
187
206
  captured.confirmMessages.push(opts.message);
188
- counters.confirm++;
189
- return simulateCancel
190
- ? Symbol.for("clack.cancel")
191
- : (responses.confirm ?? defaultResponses.confirm);
207
+ const idx = counters.confirm++;
208
+ const seq = responses.confirmSequence ?? defaultResponses.confirmSequence;
209
+ const val = idx < seq.length ? seq[idx] : seq[seq.length - 1];
210
+ if (simulateCancel) return Symbol.for("clack.cancel");
211
+ return val;
192
212
  }
193
213
  );
194
214
  spyOn(clack, "group").mockImplementation(
@@ -244,8 +264,7 @@ function installClackSpies(): void {
244
264
  // Mock: Bun.write & Bun.file
245
265
  // ---------------------------------------------------------------------------
246
266
 
247
- const originalWrite = Bun.write;
248
- const originalFile = Bun.file;
267
+ const origWhoamiPrototype = CloudflareService.prototype.whoami;
249
268
 
250
269
  beforeEach(() => {
251
270
  captured = makeCapture();
@@ -302,7 +321,7 @@ beforeEach(() => {
302
321
 
303
322
  afterEach(() => {
304
323
  mock.restore();
305
- CloudflareService.prototype.whoami = origWhoami;
324
+ CloudflareService.prototype.whoami = origWhoamiPrototype;
306
325
  });
307
326
 
308
327
  // ---------------------------------------------------------------------------
@@ -320,18 +339,19 @@ describe("init command", () => {
320
339
  password: "valid-token",
321
340
  text: "test-account-id",
322
341
  multiselect: [],
323
- confirm: false,
342
+ select: "minimal",
343
+ confirmSequence: [true, false, false],
324
344
  };
325
345
 
326
- // Prevent collectBaseSecrets confirm from breaking
327
346
  await runInitCommand({}, { json: false, quiet: true }, false);
328
347
 
329
348
  expect(captured.intro.length).toBeGreaterThan(0);
330
349
  expect(captured.intro.some((m) => m.includes("Hoox Setup Wizard"))).toBe(
331
350
  true
332
351
  );
333
- // Outro may or may not fire depending on flow. If confirm returns false,
334
- // the base secrets confirm gets captured but the flow continues.
352
+ expect(captured.outro.some((m) => m.includes("Setup complete"))).toBe(
353
+ true
354
+ );
335
355
  });
336
356
 
337
357
  it("collects Cloudflare API token with validation", async () => {
@@ -339,7 +359,8 @@ describe("init command", () => {
339
359
  password: "valid-token",
340
360
  text: "test-account-id",
341
361
  multiselect: [],
342
- confirm: false,
362
+ select: "minimal",
363
+ confirmSequence: [true, false, false],
343
364
  };
344
365
 
345
366
  await runInitCommand({}, { json: false, quiet: true }, false);
@@ -369,7 +390,8 @@ describe("init command", () => {
369
390
  password: "",
370
391
  text: "test-account-id",
371
392
  multiselect: [],
372
- confirm: false,
393
+ select: "minimal",
394
+ confirmSequence: [true, false, false],
373
395
  };
374
396
 
375
397
  await runInitCommand({}, { json: false, quiet: true }, false);
@@ -417,7 +439,8 @@ describe("init command", () => {
417
439
  password: "valid-token",
418
440
  text: "existing-account-id-123",
419
441
  multiselect: [],
420
- confirm: false,
442
+ select: "minimal",
443
+ confirmSequence: [true, false, false],
421
444
  };
422
445
 
423
446
  await runInitCommand({}, { json: false, quiet: true }, false);
@@ -434,7 +457,8 @@ describe("init command", () => {
434
457
  password: "valid-token",
435
458
  text: "test-account-id",
436
459
  multiselect: ["binance", "telegram"],
437
- confirm: false,
460
+ select: "full", // full preset includes integrations
461
+ confirmSequence: [true, false, false],
438
462
  group: {
439
463
  BINANCE_API_KEY: "binance-key-123",
440
464
  BINANCE_API_SECRET: "binance-secret-123",
@@ -445,8 +469,8 @@ describe("init command", () => {
445
469
  await runInitCommand({}, { json: false, quiet: true }, false);
446
470
 
447
471
  expect(
448
- captured.multiselectMessages.some((m) =>
449
- m.includes("Select integrations")
472
+ captured.selectMessages.some((m) =>
473
+ m.includes("Select a worker preset")
450
474
  )
451
475
  ).toBe(true);
452
476
  });
@@ -456,7 +480,8 @@ describe("init command", () => {
456
480
  password: "valid-token",
457
481
  text: "test-account-id",
458
482
  multiselect: ["telegram"],
459
- confirm: false,
483
+ select: "full",
484
+ confirmSequence: [true, false, false],
460
485
  group: {
461
486
  TELEGRAM_BOT_TOKEN: "tg-token-abc",
462
487
  },
@@ -510,7 +535,7 @@ describe("init command", () => {
510
535
 
511
536
  const options: InitOptions = {
512
537
  token: "bad-token",
513
- account: "account-123",
538
+ account: "abc123def456abc123def456abc123de",
514
539
  };
515
540
 
516
541
  const promise = runInitCommand(
@@ -569,12 +594,14 @@ describe("init command", () => {
569
594
  password: "valid-token",
570
595
  text: "test-account",
571
596
  multiselect: [],
572
- confirm: false,
597
+ select: "minimal",
598
+ confirmSequence: [true, false, false],
573
599
  };
574
600
 
575
601
  await runInitCommand({}, { json: false, quiet: true }, false);
576
602
 
577
603
  const workersJsonc = captured.writes["wrangler.jsonc"];
604
+ expect(workersJsonc).toBeDefined();
578
605
  expect(workersJsonc).toContain('"d1-worker"');
579
606
  expect(workersJsonc).toContain('"hoox"');
580
607
  expect(workersJsonc).toContain('"agent-worker"');
@@ -585,8 +612,9 @@ describe("init command", () => {
585
612
  responses = {
586
613
  password: "valid-token",
587
614
  text: "test-account",
588
- multiselect: ["binance", "telegram"],
589
- confirm: false,
615
+ multiselect: [],
616
+ select: "full",
617
+ confirmSequence: [true, false, false],
590
618
  group: {
591
619
  BINANCE_API_KEY: "bk",
592
620
  BINANCE_API_SECRET: "bs",
@@ -597,6 +625,7 @@ describe("init command", () => {
597
625
  await runInitCommand({}, { json: false, quiet: true }, false);
598
626
 
599
627
  const workersJsonc = captured.writes["wrangler.jsonc"];
628
+ expect(workersJsonc).toBeDefined();
600
629
  expect(workersJsonc).toContain('"trade-worker"');
601
630
  expect(workersJsonc).toContain('"BINANCE_API_KEY"');
602
631
  expect(workersJsonc).toContain('"BINANCE_API_SECRET"');
@@ -604,52 +633,53 @@ describe("init command", () => {
604
633
  expect(workersJsonc).toContain('"TELEGRAM_BOT_TOKEN"');
605
634
  });
606
635
 
607
- it("includes AI provider integrations when selected", async () => {
636
+ it("includes AI provider (OpenAI) integration from full preset", async () => {
608
637
  responses = {
609
638
  password: "valid-token",
610
639
  text: "test-account",
611
- multiselect: ["openai", "anthropic", "google-ai"],
612
- confirm: false,
640
+ multiselect: [],
641
+ select: "full",
642
+ confirmSequence: [true, false, false],
613
643
  group: {
614
644
  AGENT_OPENAI_KEY: "sk-openai-123",
615
- AGENT_ANTHROPIC_KEY: "sk-ant-123",
616
- AGENT_GOOGLE_KEY: "google-ai-key-456",
617
645
  },
618
646
  };
619
647
 
620
648
  await runInitCommand({}, { json: false, quiet: true }, false);
621
649
 
622
650
  const workersJsonc = captured.writes["wrangler.jsonc"];
651
+ expect(workersJsonc).toBeDefined();
623
652
  expect(workersJsonc).toContain('"agent-worker"');
624
653
  expect(workersJsonc).toContain('"AGENT_OPENAI_KEY"');
625
- expect(workersJsonc).toContain('"AGENT_ANTHROPIC_KEY"');
626
- expect(workersJsonc).toContain('"AGENT_GOOGLE_KEY"');
627
654
  });
628
655
 
629
- it("includes Home Assistant integration when selected", async () => {
656
+ it("includes wallet integration from full preset", async () => {
630
657
  responses = {
631
658
  password: "valid-token",
632
659
  text: "test-account",
633
- multiselect: ["home-assistant"],
634
- confirm: false,
660
+ multiselect: [],
661
+ select: "full",
662
+ confirmSequence: [true, false, false],
635
663
  group: {
636
- HA_TOKEN_BINDING: "ha-token-789",
664
+ WALLET_MNEMONIC_SECRET: "seed-phrase-789",
637
665
  },
638
666
  };
639
667
 
640
668
  await runInitCommand({}, { json: false, quiet: true }, false);
641
669
 
642
670
  const workersJsonc = captured.writes["wrangler.jsonc"];
643
- expect(workersJsonc).toContain('"hoox"');
644
- expect(workersJsonc).toContain('"HA_TOKEN_BINDING"');
671
+ expect(workersJsonc).toBeDefined();
672
+ expect(workersJsonc).toContain('"web3-wallet-worker"');
673
+ expect(workersJsonc).toContain('"WALLET_MNEMONIC_SECRET"');
645
674
  });
646
675
 
647
676
  it("merges exchange secrets into single trade-worker", async () => {
648
677
  responses = {
649
678
  password: "valid-token",
650
679
  text: "test-account",
651
- multiselect: ["binance", "mexc"],
652
- confirm: false,
680
+ multiselect: [],
681
+ select: "full",
682
+ confirmSequence: [true, false, false],
653
683
  group: {
654
684
  BINANCE_API_KEY: "bk",
655
685
  BINANCE_API_SECRET: "bs",
@@ -661,6 +691,7 @@ describe("init command", () => {
661
691
  await runInitCommand({}, { json: false, quiet: true }, false);
662
692
 
663
693
  const workersJsonc = captured.writes["wrangler.jsonc"];
694
+ expect(workersJsonc).toBeDefined();
664
695
  expect(workersJsonc).toContain('"trade-worker"');
665
696
  expect(workersJsonc).toContain('"BINANCE_API_KEY"');
666
697
  expect(workersJsonc).toContain('"MEXC_API_KEY"');
@@ -675,12 +706,14 @@ describe("init command", () => {
675
706
  password: "my-cf-token",
676
707
  text: "my-account-id",
677
708
  multiselect: [],
678
- confirm: false,
709
+ select: "minimal",
710
+ confirmSequence: [true, false, false],
679
711
  };
680
712
 
681
713
  await runInitCommand({}, { json: false, quiet: true }, false);
682
714
 
683
715
  const workersJsonc = captured.writes["wrangler.jsonc"];
716
+ expect(workersJsonc).toBeDefined();
684
717
  expect(workersJsonc).toContain('"global"');
685
718
  expect(workersJsonc).toContain('"cloudflare_api_token"');
686
719
  expect(workersJsonc).toContain('"cloudflare_account_id"');
@@ -692,12 +725,14 @@ describe("init command", () => {
692
725
  password: "valid-token",
693
726
  text: "test-account",
694
727
  multiselect: [],
695
- confirm: false,
728
+ select: "minimal",
729
+ confirmSequence: [true, false, false],
696
730
  };
697
731
 
698
732
  await runInitCommand({}, { json: false, quiet: true }, false);
699
733
 
700
734
  const workersJsonc = captured.writes["wrangler.jsonc"];
735
+ expect(workersJsonc).toBeDefined();
701
736
  expect(workersJsonc).toContain("// Hoox Workspace Configuration");
702
737
  expect(workersJsonc).toContain("// Generated by `hoox init`");
703
738
  });
@@ -712,8 +747,9 @@ describe("init command", () => {
712
747
  responses = {
713
748
  password: "valid-token",
714
749
  text: "test-account",
715
- multiselect: ["telegram"],
716
- confirm: false,
750
+ multiselect: [],
751
+ select: "full",
752
+ confirmSequence: [true, false, false],
717
753
  group: {
718
754
  TELEGRAM_BOT_TOKEN: "tg-secret-value",
719
755
  },
@@ -723,6 +759,7 @@ describe("init command", () => {
723
759
 
724
760
  const devVarsPath = "workers/telegram-worker/.dev.vars";
725
761
  const devVars = captured.writes[devVarsPath];
762
+ expect(devVars).toBeDefined();
726
763
  expect(devVars).toContain("TELEGRAM_BOT_TOKEN=tg-secret-value");
727
764
  expect(devVars).toContain("NEVER commit this file");
728
765
  });
@@ -732,12 +769,13 @@ describe("init command", () => {
732
769
  password: "valid-token",
733
770
  text: "test-account",
734
771
  multiselect: [],
735
- confirm: false,
772
+ select: "minimal",
773
+ confirmSequence: [true, false, false],
736
774
  };
737
775
 
738
776
  await runInitCommand({}, { json: false, quiet: true }, false);
739
777
 
740
- // d1-worker has no user-collected secrets in interactive mode
778
+ // d1-worker has no user-collected secrets
741
779
  const d1DevVars = captured.writes["workers/d1-worker/.dev.vars"];
742
780
  expect(d1DevVars).toBeUndefined();
743
781
  });
@@ -746,8 +784,9 @@ describe("init command", () => {
746
784
  responses = {
747
785
  password: "valid-token",
748
786
  text: "test-account",
749
- multiselect: ["openai"],
750
- confirm: false,
787
+ multiselect: [],
788
+ select: "full",
789
+ confirmSequence: [true, false, false],
751
790
  group: {
752
791
  AGENT_OPENAI_KEY: "sk-openai-real-value",
753
792
  },
@@ -757,26 +796,29 @@ describe("init command", () => {
757
796
 
758
797
  const devVarsPath = "workers/agent-worker/.dev.vars";
759
798
  const devVars = captured.writes[devVarsPath];
799
+ expect(devVars).toBeDefined();
760
800
  expect(devVars).toContain("AGENT_OPENAI_KEY=sk-openai-real-value");
761
801
  expect(devVars).toContain("NEVER commit this file");
762
802
  });
763
803
 
764
- it("creates .dev.vars for hoox when Home Assistant selected", async () => {
804
+ it("creates .dev.vars for web3-wallet-worker when wallet integration selected", async () => {
765
805
  responses = {
766
806
  password: "valid-token",
767
807
  text: "test-account",
768
- multiselect: ["home-assistant"],
769
- confirm: false,
808
+ multiselect: [],
809
+ select: "full",
810
+ confirmSequence: [true, false, false],
770
811
  group: {
771
- HA_TOKEN_BINDING: "ha-token-real-value",
812
+ WALLET_MNEMONIC_SECRET: "mnemonic-real-value",
772
813
  },
773
814
  };
774
815
 
775
816
  await runInitCommand({}, { json: false, quiet: true }, false);
776
817
 
777
- const devVarsPath = "workers/hoox/.dev.vars";
818
+ const devVarsPath = "workers/web3-wallet-worker/.dev.vars";
778
819
  const devVars = captured.writes[devVarsPath];
779
- expect(devVars).toContain("HA_TOKEN_BINDING=ha-token-real-value");
820
+ expect(devVars).toBeDefined();
821
+ expect(devVars).toContain("WALLET_MNEMONIC_SECRET=mnemonic-real-value");
780
822
  expect(devVars).toContain("NEVER commit this file");
781
823
  });
782
824
  });
@@ -786,19 +828,24 @@ describe("init command", () => {
786
828
  // ------------------------------------------------------------------
787
829
 
788
830
  describe("cancellation handling", () => {
789
- it("exits on cancel during token prompt", async () => {
790
- // Override the process.exit mock to track calls
831
+ it("exits on cancel during risk acknowledgment", async () => {
791
832
  let exitCalled = false;
792
833
  let exitCode = -1;
793
834
  const exitMock = mock((code?: number) => {
794
835
  exitCalled = true;
795
836
  exitCode = code ?? -1;
837
+ throw new Error("EXIT"); // Must throw to stop execution flow
796
838
  });
797
839
  process.exit = exitMock as unknown as typeof process.exit;
798
840
 
841
+ // First confirm (risk) is canceled
799
842
  simulateCancel = true;
800
843
 
801
- await runInitCommand({}, { json: false, quiet: true }, false);
844
+ try {
845
+ await runInitCommand({}, { json: false, quiet: true }, false);
846
+ } catch {
847
+ // expected — process.exit throws
848
+ }
802
849
 
803
850
  expect(exitCalled).toBe(true);
804
851
  expect(exitCode).toBe(0);
@@ -815,13 +862,15 @@ describe("init command", () => {
815
862
  password: "valid-token",
816
863
  text: "test-account",
817
864
  multiselect: [],
818
- confirm: false,
865
+ select: "minimal",
866
+ confirmSequence: [true, false, false],
819
867
  };
820
868
 
821
869
  await runInitCommand({}, { json: false, quiet: true }, false);
822
870
 
823
- // In quiet mode, the intro/outro patterns are still called but
824
- // our formatSuccess/formatter functions respect quiet mode.
871
+ // In quiet mode, formatSuccess/formatter functions respect quiet mode.
872
+ // The flow should complete without throwing.
873
+ expect(captured.writes["wrangler.jsonc"]).toBeDefined();
825
874
  });
826
875
  });
827
876
  });