@jango-blockchained/hoox-cli 0.5.1 → 0.5.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.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/commands/check/check-command.ts +8 -65
  3. package/src/commands/check/prerequisites-command.ts +7 -6
  4. package/src/commands/clone/clone-command.test.ts +8 -10
  5. package/src/commands/config/config-command.test.ts +4 -4
  6. package/src/commands/config/config-command.ts +10 -11
  7. package/src/commands/config/env-command.test.ts +2 -2
  8. package/src/commands/config/env-command.ts +40 -43
  9. package/src/commands/config/kv-command.ts +60 -60
  10. package/src/commands/dashboard/dashboard-command.ts +32 -30
  11. package/src/commands/db/db-command.ts +79 -80
  12. package/src/commands/deploy/deploy-command.test.ts +54 -34
  13. package/src/commands/deploy/deploy-command.ts +3 -84
  14. package/src/commands/dev/dev-command.test.ts +84 -62
  15. package/src/commands/disclaimer/disclaimer-command.ts +21 -0
  16. package/src/commands/disclaimer/index.ts +1 -0
  17. package/src/commands/init/init-command.test.ts +69 -91
  18. package/src/commands/init/init-command.ts +19 -15
  19. package/src/commands/logs/logs-command.test.ts +0 -1
  20. package/src/commands/monitor/monitor-command.test.ts +37 -29
  21. package/src/commands/repair/repair-command.test.ts +60 -41
  22. package/src/commands/repair/repair-service.ts +26 -0
  23. package/src/commands/schema/index.ts +1 -0
  24. package/src/commands/schema/schema-command.ts +137 -0
  25. package/src/commands/test/test-command.test.ts +2 -3
  26. package/src/commands/update/update-command.ts +6 -65
  27. package/src/commands/waf/waf-command.ts +56 -59
  28. package/src/index.ts +5 -13
  29. package/src/services/config/config-service.ts +1 -6
  30. package/src/services/db/db-service.test.ts +13 -1
  31. package/src/services/env/env-service.test.ts +41 -18
  32. package/src/services/env/env-service.ts +45 -59
  33. package/src/services/kv/kv-sync-service.ts +14 -5
  34. package/src/services/schema/index.ts +1 -0
  35. package/src/services/schema/schema-service.ts +99 -0
  36. package/src/services/secrets/secrets-service.test.ts +42 -35
  37. package/src/services/secrets/types.ts +1 -1
  38. package/src/ui/menu.ts +1 -1
  39. package/src/utils/error-handler.ts +62 -0
  40. package/src/utils/errors.ts +1 -0
  41. package/src/utils/git.ts +134 -0
  42. package/src/utils/theme.ts +0 -2
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * Unit tests for the dev command.
4
3
  *
@@ -64,39 +63,39 @@ beforeEach(() => {
64
63
  process.exitCode = undefined;
65
64
 
66
65
  // Restore prototypes to originals
67
- (ConfigService.prototype as Record<string, unknown>).load = origLoad;
68
- (ConfigService.prototype as Record<string, unknown>).listWorkers =
66
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
67
+ origLoad;
68
+ (ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
69
69
  origListWorkers;
70
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
71
- origListEnabled;
72
- (ConfigService.prototype as Record<string, unknown>).getWorker =
70
+ (
71
+ ConfigService.prototype as unknown as Record<string, unknown>
72
+ ).listEnabledWorkers = origListEnabled;
73
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
73
74
  origGetWorker;
74
- (ConfigService.prototype as Record<string, unknown>).validate = origValidate;
75
- (CloudflareService.prototype as Record<string, unknown>).dev = origDev;
75
+ (ConfigService.prototype as unknown as Record<string, unknown>).validate =
76
+ origValidate;
77
+ (CloudflareService.prototype as unknown as Record<string, unknown>).dev =
78
+ origDev;
76
79
 
77
80
  // Stub Bun.spawn to prevent actual process spawning during tests.
78
81
  // Uses individual property assignment (not globalThis.Bun = {...}) to
79
82
  // avoid "readonly property" errors in Bun 1.3.x.
80
- (Bun as unknown as Record<string, unknown>).spawn = mock(() => ({
83
+ (Bun as unknown as unknown as Record<string, unknown>).spawn = mock(() => ({
81
84
  stdout: new Blob([]),
82
85
  stderr: new Blob([]),
83
86
  exited: Promise.resolve(0),
84
87
  stdin: { write: mock(() => {}), end: mock(() => {}) },
85
88
  kill: mock(() => {}),
86
89
  }));
87
- (Bun as unknown as Record<string, unknown>).file = mock((_path: string) => ({
88
- exists: mock(async () => true),
89
- }));
90
-
91
90
  // Fresh mocks
92
91
  devMock = mock(async (_path: string, _port?: number) => ({
93
92
  ok: true as const,
94
- data: { port: _port ?? 8787 },
93
+ value: { port: _port ?? 8787 },
95
94
  }));
96
95
 
97
- loadMock = mock(async function () {
96
+ loadMock = mock(async function (this: ConfigService) {
98
97
  // Use a regular function so `this` refers to the ConfigService instance
99
- (this as Record<string, unknown>).config = {};
98
+ (this as unknown as Record<string, unknown>).config = {};
100
99
  return {} as Record<string, unknown>;
101
100
  });
102
101
  listWorkersMock = mock(() => ["hoox", "trade-worker", "d1-worker"]);
@@ -114,52 +113,66 @@ beforeEach(() => {
114
113
  composeExistsMock = mock(async () => false);
115
114
 
116
115
  // Install mocks on prototypes
117
- (ConfigService.prototype as Record<string, unknown>).load = loadMock;
118
- (ConfigService.prototype as Record<string, unknown>).listWorkers =
116
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
117
+ loadMock;
118
+ (ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
119
119
  listWorkersMock;
120
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
121
- listEnabledWorkersMock;
122
- (ConfigService.prototype as Record<string, unknown>).getWorker =
120
+ (
121
+ ConfigService.prototype as unknown as Record<string, unknown>
122
+ ).listEnabledWorkers = listEnabledWorkersMock;
123
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
123
124
  getWorkerMock;
124
- (ConfigService.prototype as Record<string, unknown>).validate = validateMock;
125
- (ConfigService.prototype as Record<string, unknown>).getDevRuntime =
126
- getDevRuntimeMock;
127
- (CloudflareService.prototype as Record<string, unknown>).dev = devMock;
125
+ (ConfigService.prototype as unknown as Record<string, unknown>).validate =
126
+ validateMock;
127
+ (
128
+ ConfigService.prototype as unknown as Record<string, unknown>
129
+ ).getDevRuntime = getDevRuntimeMock;
130
+ (CloudflareService.prototype as unknown as Record<string, unknown>).dev =
131
+ devMock;
128
132
  (
129
- PrerequisitesService.prototype as Record<string, unknown>
133
+ PrerequisitesService.prototype as unknown as Record<string, unknown>
130
134
  ).checkWranglerVersion = checkWranglerMock;
131
- (DockerService.prototype as Record<string, unknown>).checkAvailability =
132
- dockerCheckMock;
133
- (DockerService.prototype as Record<string, unknown>).composeFileExists =
134
- composeExistsMock;
135
+ (
136
+ DockerService.prototype as unknown as Record<string, unknown>
137
+ ).checkAvailability = dockerCheckMock;
138
+ (
139
+ DockerService.prototype as unknown as Record<string, unknown>
140
+ ).composeFileExists = composeExistsMock;
135
141
  });
136
142
 
137
143
  afterEach(() => {
138
144
  mock.restore();
139
145
 
140
146
  // Restore originals
141
- (ConfigService.prototype as Record<string, unknown>).load = origLoad;
142
- (ConfigService.prototype as Record<string, unknown>).listWorkers =
147
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
148
+ origLoad;
149
+ (ConfigService.prototype as unknown as Record<string, unknown>).listWorkers =
143
150
  origListWorkers;
144
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
145
- origListEnabled;
146
- (ConfigService.prototype as Record<string, unknown>).getWorker =
151
+ (
152
+ ConfigService.prototype as unknown as Record<string, unknown>
153
+ ).listEnabledWorkers = origListEnabled;
154
+ (ConfigService.prototype as unknown as Record<string, unknown>).getWorker =
147
155
  origGetWorker;
148
- (ConfigService.prototype as Record<string, unknown>).validate = origValidate;
149
- (ConfigService.prototype as Record<string, unknown>).getDevRuntime =
150
- origGetDevRuntime;
151
- (CloudflareService.prototype as Record<string, unknown>).dev = origDev;
156
+ (ConfigService.prototype as unknown as Record<string, unknown>).validate =
157
+ origValidate;
158
+ (
159
+ ConfigService.prototype as unknown as Record<string, unknown>
160
+ ).getDevRuntime = origGetDevRuntime;
161
+ (CloudflareService.prototype as unknown as Record<string, unknown>).dev =
162
+ origDev;
152
163
  (
153
- PrerequisitesService.prototype as Record<string, unknown>
164
+ PrerequisitesService.prototype as unknown as Record<string, unknown>
154
165
  ).checkWranglerVersion = origCheckWrangler;
155
- (DockerService.prototype as Record<string, unknown>).checkAvailability =
156
- origDockerCheck;
157
- (DockerService.prototype as Record<string, unknown>).composeFileExists =
158
- origComposeExists;
166
+ (
167
+ DockerService.prototype as unknown as Record<string, unknown>
168
+ ).checkAvailability = origDockerCheck;
169
+ (
170
+ DockerService.prototype as unknown as Record<string, unknown>
171
+ ).composeFileExists = origComposeExists;
159
172
 
160
173
  // Restore Bun globals (individual property restore)
161
- (Bun as unknown as Record<string, unknown>).spawn = origBunSpawn;
162
- (Bun as unknown as Record<string, unknown>).file = origBunFile;
174
+ (Bun as unknown as unknown as Record<string, unknown>).spawn = origBunSpawn;
175
+ (Bun as unknown as unknown as Record<string, unknown>).file = origBunFile;
163
176
  });
164
177
 
165
178
  // ---------------------------------------------------------------------------
@@ -187,7 +200,8 @@ async function createProgram(): Promise<Command> {
187
200
  /** Make devMock return a failure for all subsequent calls. */
188
201
  function makeDevFail(error: string): void {
189
202
  devMock = mock(async () => ({ ok: false as const, error }));
190
- (CloudflareService.prototype as Record<string, unknown>).dev = devMock;
203
+ (CloudflareService.prototype as unknown as Record<string, unknown>).dev =
204
+ devMock;
191
205
  }
192
206
 
193
207
  // ---------------------------------------------------------------------------
@@ -237,7 +251,7 @@ describe("registerDevCommand", () => {
237
251
  valid: false,
238
252
  errors: ["global.cloudflare_account_id is required"],
239
253
  }));
240
- (ConfigService.prototype as Record<string, unknown>).validate =
254
+ (ConfigService.prototype as unknown as Record<string, unknown>).validate =
241
255
  validateMock;
242
256
 
243
257
  const program = await createProgram();
@@ -248,8 +262,9 @@ describe("registerDevCommand", () => {
248
262
 
249
263
  it("handles no enabled workers gracefully", async () => {
250
264
  listEnabledWorkersMock = mock(() => []);
251
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
252
- listEnabledWorkersMock;
265
+ (
266
+ ConfigService.prototype as unknown as Record<string, unknown>
267
+ ).listEnabledWorkers = listEnabledWorkersMock;
253
268
 
254
269
  const program = await createProgram();
255
270
  await program.parseAsync(["dev", "start"], { from: "user" });
@@ -259,8 +274,9 @@ describe("registerDevCommand", () => {
259
274
 
260
275
  it("starts all enabled workers on assigned ports", async () => {
261
276
  listEnabledWorkersMock = mock(() => ["hoox", "trade-worker"]);
262
- (ConfigService.prototype as Record<string, unknown>).listEnabledWorkers =
263
- listEnabledWorkersMock;
277
+ (
278
+ ConfigService.prototype as unknown as Record<string, unknown>
279
+ ).listEnabledWorkers = listEnabledWorkersMock;
264
280
 
265
281
  const program = await createProgram();
266
282
  await program.parseAsync(["dev", "start"], { from: "user" });
@@ -306,8 +322,9 @@ describe("registerDevCommand", () => {
306
322
 
307
323
  it("handles unknown worker name", async () => {
308
324
  getWorkerMock = mock(() => undefined);
309
- (ConfigService.prototype as Record<string, unknown>).getWorker =
310
- getWorkerMock;
325
+ (
326
+ ConfigService.prototype as unknown as Record<string, unknown>
327
+ ).getWorker = getWorkerMock;
311
328
 
312
329
  const program = await createProgram();
313
330
  await program.parseAsync(["dev", "worker", "nonexistent"], {
@@ -323,8 +340,9 @@ describe("registerDevCommand", () => {
323
340
  enabled: false,
324
341
  path: "workers/disabled-worker",
325
342
  }));
326
- (ConfigService.prototype as Record<string, unknown>).getWorker =
327
- getWorkerMock;
343
+ (
344
+ ConfigService.prototype as unknown as Record<string, unknown>
345
+ ).getWorker = getWorkerMock;
328
346
 
329
347
  const program = await createProgram();
330
348
  await program.parseAsync(["dev", "worker", "disabled-worker"], {
@@ -341,8 +359,9 @@ describe("registerDevCommand", () => {
341
359
  enabled: true,
342
360
  path: "workers/hoox",
343
361
  }));
344
- (ConfigService.prototype as Record<string, unknown>).getWorker =
345
- getWorkerMock;
362
+ (
363
+ ConfigService.prototype as unknown as Record<string, unknown>
364
+ ).getWorker = getWorkerMock;
346
365
 
347
366
  const program = await createProgram();
348
367
  await program.parseAsync(["dev", "worker", "hoox"], { from: "user" });
@@ -356,9 +375,11 @@ describe("registerDevCommand", () => {
356
375
  describe("dev dashboard", () => {
357
376
  it("handles missing dashboard directory", async () => {
358
377
  // Override Bun.file for this test to say dashboard doesn't exist
359
- (Bun as unknown as Record<string, unknown>).file = mock((_p: string) => ({
360
- exists: mock(async () => false),
361
- }));
378
+ (Bun as unknown as unknown as Record<string, unknown>).file = mock(
379
+ (_p: string) => ({
380
+ exists: mock(async () => false),
381
+ })
382
+ );
362
383
 
363
384
  const program = await createProgram();
364
385
  await program.parseAsync(["dev", "dashboard"], { from: "user" });
@@ -373,7 +394,8 @@ describe("registerDevCommand", () => {
373
394
  loadMock = mock(async () => {
374
395
  throw new Error("Config file not found");
375
396
  });
376
- (ConfigService.prototype as Record<string, unknown>).load = loadMock;
397
+ (ConfigService.prototype as unknown as Record<string, unknown>).load =
398
+ loadMock;
377
399
 
378
400
  const program = await createProgram();
379
401
  await program.parseAsync(["dev", "start"], { from: "user" });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `hoox disclaimer` command — displays legal disclaimers and trademark information.
3
+ *
4
+ * Uses the centralized legal notices from @jango-blockchained/hoox-shared/legal
5
+ * so all surfaces (CLI, HTTP, Dashboard) stay consistent.
6
+ */
7
+
8
+ import type { Command } from "commander";
9
+ import { FULL_LEGAL_NOTICE } from "@jango-blockchained/hoox-shared/legal";
10
+
11
+ /**
12
+ * Registers the `hoox disclaimer` command on the given Commander.js program instance.
13
+ */
14
+ export function registerDisclaimerCommand(program: Command): void {
15
+ program
16
+ .command("disclaimer")
17
+ .description("Display legal disclaimers and trademark information")
18
+ .action(() => {
19
+ console.log(FULL_LEGAL_NOTICE);
20
+ });
21
+ }
@@ -0,0 +1 @@
1
+ export { registerDisclaimerCommand } from "./disclaimer-command.js";
@@ -8,7 +8,6 @@
8
8
  * - process.exit → captured exit codes
9
9
  */
10
10
 
11
- // @ts-nocheck — spyOn on module namespace requires type bypasses for strict signatures
12
11
  import {
13
12
  afterEach,
14
13
  beforeEach,
@@ -123,8 +122,6 @@ let customPasswordResponder: ((msg: string) => string | symbol) | null = null;
123
122
  // Mock: CloudflareService (prototype based — no mock.module, no leakage)
124
123
  // ---------------------------------------------------------------------------
125
124
 
126
- const origWhoami = CloudflareService.prototype.whoami;
127
-
128
125
  const mockWhoami = mock(
129
126
  async (): Promise<
130
127
  { ok: true; value: string } | { ok: false; error: string }
@@ -141,16 +138,19 @@ const mockWhoami = mock(
141
138
  import * as clack from "@clack/prompts";
142
139
 
143
140
  function installClackSpies(): void {
144
- spyOn(clack, "intro").mockImplementation((msg: string) => {
145
- captured.intro.push(msg);
146
- });
147
- spyOn(clack, "outro").mockImplementation((msg: string) => {
148
- captured.outro.push(msg);
141
+ spyOn(clack, "intro").mockImplementation((msg?: string) => {
142
+ captured.intro.push(msg ?? "");
149
143
  });
150
- spyOn(clack, "note").mockImplementation((content: string, title?: string) => {
151
- captured.note.push({ title: title ?? "", content });
144
+ spyOn(clack, "outro").mockImplementation((msg?: string) => {
145
+ captured.outro.push(msg ?? "");
152
146
  });
147
+ spyOn(clack, "note").mockImplementation(
148
+ (content?: string, title?: string) => {
149
+ captured.note.push({ title: title ?? "", content: content ?? "" });
150
+ }
151
+ );
153
152
  spyOn(clack, "password").mockImplementation(
153
+ // @ts-expect-error — mock validate fn signature simplified vs clack's PasswordOptions
154
154
  async (opts: {
155
155
  message: string;
156
156
  validate?: (v: string) => string | void;
@@ -163,6 +163,7 @@ function installClackSpies(): void {
163
163
  }
164
164
  );
165
165
  spyOn(clack, "text").mockImplementation(
166
+ // @ts-expect-error — mock validate fn signature simplified vs clack's TextOptions
166
167
  async (opts: {
167
168
  message: string;
168
169
  placeholder?: string;
@@ -177,6 +178,7 @@ function installClackSpies(): void {
177
178
  }
178
179
  );
179
180
  spyOn(clack, "multiselect").mockImplementation(
181
+ // @ts-expect-error — clack's multiselect is generic, mock uses concrete types
180
182
  async (opts: {
181
183
  message: string;
182
184
  options: { value: string; label: string; hint?: string }[];
@@ -190,6 +192,7 @@ function installClackSpies(): void {
190
192
  }
191
193
  );
192
194
  spyOn(clack, "select").mockImplementation(
195
+ // @ts-expect-error — clack's select is generic, mock uses concrete types
193
196
  async (opts: {
194
197
  message: string;
195
198
  options: { value: string; label: string; hint?: string }[];
@@ -212,6 +215,7 @@ function installClackSpies(): void {
212
215
  }
213
216
  );
214
217
  spyOn(clack, "group").mockImplementation(
218
+ // @ts-expect-error — clack's group uses PromptGroup<T>, not Record<string, () => Promise<...>>
215
219
  async (
216
220
  fields: Record<string, () => Promise<string | symbol>>,
217
221
  groupOpts?: { onCancel?: () => void }
@@ -233,31 +237,33 @@ function installClackSpies(): void {
233
237
  return results;
234
238
  }
235
239
  );
236
- spyOn(clack, "isCancel").mockImplementation((value: unknown) => {
237
- return (
238
- simulateCancel ||
239
- (typeof value === "symbol" &&
240
- Symbol.keyFor(value as symbol) === "clack.cancel")
241
- );
240
+ spyOn(clack, "isCancel").mockImplementation(
241
+ (value: unknown): value is symbol => {
242
+ return (
243
+ simulateCancel ||
244
+ (typeof value === "symbol" &&
245
+ Symbol.keyFor(value as symbol) === "clack.cancel")
246
+ );
247
+ }
248
+ );
249
+ spyOn(clack, "cancel").mockImplementation((msg?: string) => {
250
+ captured.cancelMessages.push(msg ?? "");
242
251
  });
243
- spyOn(clack, "cancel").mockImplementation((msg: string) => {
244
- captured.cancelMessages.push(msg);
252
+ spyOn(clack.log, "info").mockImplementation((msg?: string) => {
253
+ captured.logInfo.push(msg ?? "");
254
+ });
255
+ spyOn(clack.log, "step").mockImplementation((msg?: string) => {
256
+ captured.logStep.push(msg ?? "");
257
+ });
258
+ spyOn(clack.log, "success").mockImplementation((msg?: string) => {
259
+ captured.logSuccess.push(msg ?? "");
260
+ });
261
+ spyOn(clack.log, "warn").mockImplementation((msg?: string) => {
262
+ captured.logWarn.push(msg ?? "");
263
+ });
264
+ spyOn(clack.log, "error").mockImplementation((msg?: string) => {
265
+ captured.logError.push(msg ?? "");
245
266
  });
246
- spyOn(clack.log, "info").mockImplementation((msg: string) =>
247
- captured.logInfo.push(msg)
248
- );
249
- spyOn(clack.log, "step").mockImplementation((msg: string) =>
250
- captured.logStep.push(msg)
251
- );
252
- spyOn(clack.log, "success").mockImplementation((msg: string) =>
253
- captured.logSuccess.push(msg)
254
- );
255
- spyOn(clack.log, "warn").mockImplementation((msg: string) =>
256
- captured.logWarn.push(msg)
257
- );
258
- spyOn(clack.log, "error").mockImplementation((msg: string) =>
259
- captured.logError.push(msg)
260
- );
261
267
  }
262
268
 
263
269
  // ---------------------------------------------------------------------------
@@ -274,7 +280,7 @@ beforeEach(() => {
274
280
  customPasswordResponder = null;
275
281
  mockWhoami.mockImplementation(async () => ({
276
282
  ok: true,
277
- data: "user@example.com",
283
+ value: "user@example.com",
278
284
  }));
279
285
 
280
286
  // Mock: CloudflareService prototype
@@ -376,7 +382,7 @@ describe("init command", () => {
376
382
  if (callCount === 1) {
377
383
  return { ok: false as const, error: "Invalid credentials" };
378
384
  }
379
- return { ok: true as const, data: "user@example.com" };
385
+ return { ok: true as const, value: "user@example.com" };
380
386
  });
381
387
 
382
388
  // First password call returns bad token, second returns good token
@@ -460,9 +466,9 @@ describe("init command", () => {
460
466
  select: "full", // full preset includes integrations
461
467
  confirmSequence: [true, false, false],
462
468
  group: {
463
- BINANCE_API_KEY: "binance-key-123",
464
- BINANCE_API_SECRET: "binance-secret-123",
465
- TELEGRAM_BOT_TOKEN: "tg-bot-token-123",
469
+ BINANCE_KEY_BINDING: "binance-key-123",
470
+ BINANCE_SECRET_BINDING: "binance-secret-123",
471
+ TG_BOT_TOKEN_BINDING: "tg-bot-token-123",
466
472
  },
467
473
  };
468
474
 
@@ -483,7 +489,7 @@ describe("init command", () => {
483
489
  select: "full",
484
490
  confirmSequence: [true, false, false],
485
491
  group: {
486
- TELEGRAM_BOT_TOKEN: "tg-token-abc",
492
+ TG_BOT_TOKEN_BINDING: "tg-token-abc",
487
493
  },
488
494
  };
489
495
 
@@ -526,28 +532,14 @@ describe("init command", () => {
526
532
  error: "Bad token",
527
533
  }));
528
534
 
529
- // Override process.exit to capture and throw (halts execution)
530
- let exitCode = -1;
531
- process.exit = mock((code?: number) => {
532
- exitCode = code ?? -1;
533
- throw new Error("EXIT");
534
- }) as unknown as typeof process.exit;
535
-
536
535
  const options: InitOptions = {
537
536
  token: "bad-token",
538
537
  account: "abc123def456abc123def456abc123de",
539
538
  };
540
539
 
541
- const promise = runInitCommand(
542
- options,
543
- { json: false, quiet: true },
544
- true
545
- );
546
- await promise.catch(() => {
547
- /* expected */
548
- });
540
+ await runInitCommand(options, { json: false, quiet: true }, true);
549
541
 
550
- expect(exitCode).toBe(ExitCode.ERROR);
542
+ expect(process.exitCode).toBe(ExitCode.ERROR);
551
543
  });
552
544
 
553
545
  it("writes wrangler.jsonc in non-interactive mode", async () => {
@@ -616,9 +608,9 @@ describe("init command", () => {
616
608
  select: "full",
617
609
  confirmSequence: [true, false, false],
618
610
  group: {
619
- BINANCE_API_KEY: "bk",
620
- BINANCE_API_SECRET: "bs",
621
- TELEGRAM_BOT_TOKEN: "tgt",
611
+ BINANCE_KEY_BINDING: "bk",
612
+ BINANCE_SECRET_BINDING: "bs",
613
+ TG_BOT_TOKEN_BINDING: "tgt",
622
614
  },
623
615
  };
624
616
 
@@ -627,10 +619,10 @@ describe("init command", () => {
627
619
  const workersJsonc = captured.writes["wrangler.jsonc"];
628
620
  expect(workersJsonc).toBeDefined();
629
621
  expect(workersJsonc).toContain('"trade-worker"');
630
- expect(workersJsonc).toContain('"BINANCE_API_KEY"');
631
- expect(workersJsonc).toContain('"BINANCE_API_SECRET"');
622
+ expect(workersJsonc).toContain('"BINANCE_KEY_BINDING"');
623
+ expect(workersJsonc).toContain('"BINANCE_SECRET_BINDING"');
632
624
  expect(workersJsonc).toContain('"telegram-worker"');
633
- expect(workersJsonc).toContain('"TELEGRAM_BOT_TOKEN"');
625
+ expect(workersJsonc).toContain('"TG_BOT_TOKEN_BINDING"');
634
626
  });
635
627
 
636
628
  it("includes AI provider (OpenAI) integration from full preset", async () => {
@@ -641,7 +633,7 @@ describe("init command", () => {
641
633
  select: "full",
642
634
  confirmSequence: [true, false, false],
643
635
  group: {
644
- AGENT_OPENAI_KEY: "sk-openai-123",
636
+ AGENT_INTERNAL_KEY: "sk-openai-123",
645
637
  },
646
638
  };
647
639
 
@@ -650,7 +642,7 @@ describe("init command", () => {
650
642
  const workersJsonc = captured.writes["wrangler.jsonc"];
651
643
  expect(workersJsonc).toBeDefined();
652
644
  expect(workersJsonc).toContain('"agent-worker"');
653
- expect(workersJsonc).toContain('"AGENT_OPENAI_KEY"');
645
+ expect(workersJsonc).toContain('"AGENT_INTERNAL_KEY"');
654
646
  });
655
647
 
656
648
  it("includes wallet integration from full preset", async () => {
@@ -681,10 +673,10 @@ describe("init command", () => {
681
673
  select: "full",
682
674
  confirmSequence: [true, false, false],
683
675
  group: {
684
- BINANCE_API_KEY: "bk",
685
- BINANCE_API_SECRET: "bs",
686
- MEXC_API_KEY: "mk",
687
- MEXC_API_SECRET: "ms",
676
+ BINANCE_KEY_BINDING: "bk",
677
+ BINANCE_SECRET_BINDING: "bs",
678
+ MEXC_KEY_BINDING: "mk",
679
+ MEXC_SECRET_BINDING: "ms",
688
680
  },
689
681
  };
690
682
 
@@ -693,8 +685,8 @@ describe("init command", () => {
693
685
  const workersJsonc = captured.writes["wrangler.jsonc"];
694
686
  expect(workersJsonc).toBeDefined();
695
687
  expect(workersJsonc).toContain('"trade-worker"');
696
- expect(workersJsonc).toContain('"BINANCE_API_KEY"');
697
- expect(workersJsonc).toContain('"MEXC_API_KEY"');
688
+ expect(workersJsonc).toContain('"BINANCE_KEY_BINDING"');
689
+ expect(workersJsonc).toContain('"MEXC_KEY_BINDING"');
698
690
  // Should only appear once
699
691
  const tradeWorkerCount =
700
692
  workersJsonc.match(/"trade-worker"/g)?.length ?? 0;
@@ -751,7 +743,7 @@ describe("init command", () => {
751
743
  select: "full",
752
744
  confirmSequence: [true, false, false],
753
745
  group: {
754
- TELEGRAM_BOT_TOKEN: "tg-secret-value",
746
+ TG_BOT_TOKEN_BINDING: "tg-secret-value",
755
747
  },
756
748
  };
757
749
 
@@ -760,7 +752,7 @@ describe("init command", () => {
760
752
  const devVarsPath = "workers/telegram-worker/.dev.vars";
761
753
  const devVars = captured.writes[devVarsPath];
762
754
  expect(devVars).toBeDefined();
763
- expect(devVars).toContain("TELEGRAM_BOT_TOKEN=tg-secret-value");
755
+ expect(devVars).toContain("TG_BOT_TOKEN_BINDING=tg-secret-value");
764
756
  expect(devVars).toContain("NEVER commit this file");
765
757
  });
766
758
 
@@ -788,7 +780,7 @@ describe("init command", () => {
788
780
  select: "full",
789
781
  confirmSequence: [true, false, false],
790
782
  group: {
791
- AGENT_OPENAI_KEY: "sk-openai-real-value",
783
+ AGENT_INTERNAL_KEY: "sk-openai-real-value",
792
784
  },
793
785
  };
794
786
 
@@ -797,7 +789,7 @@ describe("init command", () => {
797
789
  const devVarsPath = "workers/agent-worker/.dev.vars";
798
790
  const devVars = captured.writes[devVarsPath];
799
791
  expect(devVars).toBeDefined();
800
- expect(devVars).toContain("AGENT_OPENAI_KEY=sk-openai-real-value");
792
+ expect(devVars).toContain("AGENT_INTERNAL_KEY=sk-openai-real-value");
801
793
  expect(devVars).toContain("NEVER commit this file");
802
794
  });
803
795
 
@@ -829,26 +821,12 @@ describe("init command", () => {
829
821
 
830
822
  describe("cancellation handling", () => {
831
823
  it("exits on cancel during risk acknowledgment", async () => {
832
- let exitCalled = false;
833
- let exitCode = -1;
834
- const exitMock = mock((code?: number) => {
835
- exitCalled = true;
836
- exitCode = code ?? -1;
837
- throw new Error("EXIT"); // Must throw to stop execution flow
838
- });
839
- process.exit = exitMock as unknown as typeof process.exit;
824
+ // Make the first confirm return false (user declines the risk terms)
825
+ responses.confirmSequence = [false];
840
826
 
841
- // First confirm (risk) is canceled
842
- simulateCancel = true;
843
-
844
- try {
845
- await runInitCommand({}, { json: false, quiet: true }, false);
846
- } catch {
847
- // expected — process.exit throws
848
- }
827
+ await runInitCommand({}, { json: false, quiet: true }, false);
849
828
 
850
- expect(exitCalled).toBe(true);
851
- expect(exitCode).toBe(0);
829
+ expect(process.exitCode).toBe(0);
852
830
  });
853
831
  });
854
832