@jango-blockchained/hoox-cli 0.5.1 → 0.5.6

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 (106) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32545 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1144
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -96
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -442
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -897
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -295
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -128
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -312
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -304
  27. package/src/commands/deploy/deploy-command.ts +0 -1135
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -383
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/infra/index.ts +0 -5
  35. package/src/commands/infra/infra-command.test.ts +0 -719
  36. package/src/commands/infra/infra-command.ts +0 -974
  37. package/src/commands/infra/types.ts +0 -23
  38. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  39. package/src/commands/init/cli-provisioner.ts +0 -71
  40. package/src/commands/init/index.ts +0 -1
  41. package/src/commands/init/init-command.test.ts +0 -876
  42. package/src/commands/init/init-command.ts +0 -593
  43. package/src/commands/init/types.ts +0 -23
  44. package/src/commands/logs/index.ts +0 -1
  45. package/src/commands/logs/logs-command.test.ts +0 -294
  46. package/src/commands/logs/logs-command.ts +0 -452
  47. package/src/commands/monitor/index.ts +0 -2
  48. package/src/commands/monitor/monitor-command.test.ts +0 -263
  49. package/src/commands/monitor/monitor-command.ts +0 -269
  50. package/src/commands/monitor/monitor-service.ts +0 -65
  51. package/src/commands/monitor/types.ts +0 -13
  52. package/src/commands/repair/index.ts +0 -2
  53. package/src/commands/repair/repair-command.test.ts +0 -224
  54. package/src/commands/repair/repair-command.ts +0 -220
  55. package/src/commands/repair/repair-service.ts +0 -138
  56. package/src/commands/repair/types.ts +0 -13
  57. package/src/commands/test/index.ts +0 -2
  58. package/src/commands/test/test-command.test.ts +0 -319
  59. package/src/commands/test/test-command.ts +0 -406
  60. package/src/commands/tui/index.ts +0 -1
  61. package/src/commands/tui/tui-command.ts +0 -87
  62. package/src/commands/update/index.ts +0 -1
  63. package/src/commands/update/update-command.ts +0 -168
  64. package/src/commands/waf/index.ts +0 -2
  65. package/src/commands/waf/types.ts +0 -48
  66. package/src/commands/waf/waf-command.test.ts +0 -506
  67. package/src/commands/waf/waf-command.ts +0 -548
  68. package/src/index.ts +0 -269
  69. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  70. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  71. package/src/services/cloudflare/index.ts +0 -2
  72. package/src/services/cloudflare/types.ts +0 -30
  73. package/src/services/config/config-service.test.ts +0 -395
  74. package/src/services/config/config-service.ts +0 -207
  75. package/src/services/config/index.ts +0 -9
  76. package/src/services/config/types.ts +0 -66
  77. package/src/services/db/db-service.test.ts +0 -51
  78. package/src/services/db/db-service.ts +0 -131
  79. package/src/services/db/index.ts +0 -1
  80. package/src/services/docker/docker-service.ts +0 -155
  81. package/src/services/docker/index.ts +0 -1
  82. package/src/services/env/env-service.test.ts +0 -236
  83. package/src/services/env/env-service.ts +0 -386
  84. package/src/services/env/index.ts +0 -1
  85. package/src/services/kv/index.ts +0 -1
  86. package/src/services/kv/kv-sync-service.test.ts +0 -38
  87. package/src/services/kv/kv-sync-service.ts +0 -234
  88. package/src/services/prerequisites/index.ts +0 -1
  89. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  90. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  91. package/src/services/prerequisites/types.ts +0 -48
  92. package/src/services/secrets/index.ts +0 -11
  93. package/src/services/secrets/secrets-service.test.ts +0 -495
  94. package/src/services/secrets/secrets-service.ts +0 -287
  95. package/src/services/secrets/types.ts +0 -50
  96. package/src/services/update/index.ts +0 -2
  97. package/src/services/update/update-service.test.ts +0 -76
  98. package/src/services/update/update-service.ts +0 -193
  99. package/src/ui/banner.ts +0 -141
  100. package/src/ui/index.ts +0 -8
  101. package/src/ui/menu.ts +0 -490
  102. package/src/utils/errors.test.ts +0 -69
  103. package/src/utils/errors.ts +0 -23
  104. package/src/utils/formatters.test.ts +0 -180
  105. package/src/utils/formatters.ts +0 -269
  106. package/src/utils/theme.ts +0 -94
@@ -1,583 +0,0 @@
1
- /**
2
- * Unit tests for `config-command.ts` — the `hoox config` command group.
3
- *
4
- * Tests verify:
5
- * - Subcommand registration (show, set, secrets, keys)
6
- * - Output formatting (human-readable vs --json)
7
- * - Error handling for missing config files, invalid paths, etc.
8
- */
9
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
10
- import { Command } from "commander";
11
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
- import { join } from "node:path";
13
- import { tmpdir } from "node:os";
14
-
15
- import { registerConfigCommand } from "./config-command.js";
16
-
17
- // ---------------------------------------------------------------------------
18
- // Helpers
19
- // ---------------------------------------------------------------------------
20
-
21
- /** Minimal valid wrangler.jsonc content for testing. */
22
- const VALID_CONFIG = `{
23
- "global": {
24
- "cloudflare_account_id": "abc123",
25
- "cloudflare_api_token": "token-placeholder"
26
- },
27
- "workers": {
28
- "d1-worker": {
29
- "enabled": true,
30
- "path": "workers/d1-worker",
31
- "vars": {
32
- "database_name": "my-database"
33
- },
34
- "secrets": ["SECRET_ONE"]
35
- },
36
- "trade-worker": {
37
- "enabled": true,
38
- "path": "workers/trade-worker",
39
- "vars": {},
40
- "secrets": ["API_KEY", "API_SECRET"]
41
- }
42
- }
43
- }`;
44
-
45
- /** Create a real wrangler.jsonc in a temp dir and cd into it. */
46
- function setupTempConfig(name = "hoox-cli-test"): string {
47
- const dir = mkdtempSync(join(tmpdir(), `${name}-`));
48
- writeFileSync(join(dir, "wrangler.jsonc"), VALID_CONFIG);
49
- return dir;
50
- }
51
-
52
- /** Capture all writes to process.stdout for assertion. */
53
- class OutputCapture {
54
- private lines: string[] = [];
55
- private original: typeof process.stdout.write;
56
-
57
- constructor() {
58
- this.original = process.stdout.write.bind(process.stdout);
59
- process.stdout.write = ((chunk: string | Uint8Array, cb?: () => void) => {
60
- const str =
61
- typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
62
- this.lines.push(str);
63
- if (cb) cb();
64
- return true;
65
- }) as typeof process.stdout.write;
66
- }
67
-
68
- text(): string {
69
- return this.lines.join("");
70
- }
71
-
72
- json<T = unknown>(): T {
73
- const text = this.text();
74
- // Find the first JSON object/array in the output
75
- for (const line of text.split("\n")) {
76
- const trimmed = line.trim();
77
- if (
78
- (trimmed.startsWith("{") || trimmed.startsWith("[")) &&
79
- (trimmed.endsWith("}") || trimmed.endsWith("]"))
80
- ) {
81
- try {
82
- return JSON.parse(trimmed) as T;
83
- } catch {
84
- // try next line
85
- }
86
- }
87
- }
88
- // Fallback: try parsing full output
89
- return JSON.parse(text) as T;
90
- }
91
-
92
- restore(): void {
93
- process.stdout.write = this.original;
94
- }
95
- }
96
-
97
- /** Create a fresh Commander program with global options for testing. */
98
- function makeProgram(): Command {
99
- const program = new Command();
100
- program.exitOverride();
101
- program.option("--json", "JSON output");
102
- program.option("--quiet", "Quiet mode");
103
- return program;
104
- }
105
-
106
- /**
107
- * Parse args and wait for all async actions to complete.
108
- * Uses Commander's internal handling: when exitOverride is set,
109
- * async action rejections surface as errors.
110
- */
111
- async function runCommand(
112
- program: Command,
113
- args: string[]
114
- ): Promise<OutputCapture> {
115
- const capture = new OutputCapture();
116
- try {
117
- // Commander v13 parseAsync waits for async actions
118
- await (
119
- program as Command & {
120
- parseAsync?: (a: string[], o: { from: string }) => Promise<void>;
121
- }
122
- ).parseAsync?.([...args], { from: "user" });
123
-
124
- // Fallback: if parseAsync isn't available, parse synchronously and wait
125
- if (
126
- typeof (program as Command & { parseAsync?: unknown }).parseAsync !==
127
- "function"
128
- ) {
129
- program.parse([...args], { from: "user" });
130
- // Give async actions a tick to complete
131
- await new Promise((r) => setTimeout(r, 50));
132
- }
133
- } catch (_err) {
134
- // Commander throws on help, version, etc. — capture handles it
135
- }
136
- return capture;
137
- }
138
-
139
- // ---------------------------------------------------------------------------
140
- // Tests: Command registration
141
- // ---------------------------------------------------------------------------
142
-
143
- describe("registerConfigCommand", () => {
144
- it("registers the `config` command on the program", () => {
145
- const program = makeProgram();
146
- registerConfigCommand(program);
147
-
148
- const cmd = program.commands.find((c) => c.name() === "config");
149
- expect(cmd).toBeDefined();
150
- expect(cmd?.summary()).toContain("Manage configuration");
151
- });
152
-
153
- it("registers the `show` subcommand", () => {
154
- const program = makeProgram();
155
- registerConfigCommand(program);
156
-
157
- const configCmd = program.commands.find((c) => c.name() === "config");
158
- const showCmd = configCmd?.commands.find((c) => c.name() === "show");
159
- expect(showCmd).toBeDefined();
160
- expect(showCmd?.description()).toContain("Display");
161
- });
162
-
163
- it("registers the `set` subcommand with key and value arguments", () => {
164
- const program = makeProgram();
165
- registerConfigCommand(program);
166
-
167
- const configCmd = program.commands.find((c) => c.name() === "config");
168
- const setCmd = configCmd?.commands.find((c) => c.name() === "set");
169
- expect(setCmd).toBeDefined();
170
- expect(setCmd?.description()).toContain("Update");
171
- });
172
-
173
- it("registers the `secrets` subcommand group with all sub-subcommands", () => {
174
- const program = makeProgram();
175
- registerConfigCommand(program);
176
-
177
- const configCmd = program.commands.find((c) => c.name() === "config");
178
- const secretsCmd = configCmd?.commands.find((c) => c.name() === "secrets");
179
- expect(secretsCmd).toBeDefined();
180
- expect(secretsCmd?.description()).toContain("Cloudflare");
181
-
182
- const subNames = secretsCmd?.commands.map((c) => c.name()) ?? [];
183
- expect(subNames).toContain("list");
184
- expect(subNames).toContain("set");
185
- expect(subNames).toContain("delete");
186
- expect(subNames).toContain("sync");
187
- });
188
-
189
- it("registers the `keys` subcommand group with generate and list", () => {
190
- const program = makeProgram();
191
- registerConfigCommand(program);
192
-
193
- const configCmd = program.commands.find((c) => c.name() === "config");
194
- const keysCmd = configCmd?.commands.find((c) => c.name() === "keys");
195
- expect(keysCmd).toBeDefined();
196
- expect(keysCmd?.description()).toContain("auth");
197
-
198
- const subNames = keysCmd?.commands.map((c) => c.name()) ?? [];
199
- expect(subNames).toContain("generate");
200
- expect(subNames).toContain("list");
201
- });
202
- });
203
-
204
- // ---------------------------------------------------------------------------
205
- // Tests: config show
206
- // ---------------------------------------------------------------------------
207
-
208
- describe("config show", () => {
209
- let tmpDir: string;
210
- let originalCwd: string;
211
-
212
- beforeEach(() => {
213
- tmpDir = setupTempConfig();
214
- originalCwd = process.cwd();
215
- process.chdir(tmpDir);
216
- });
217
-
218
- afterEach(() => {
219
- process.chdir(originalCwd);
220
- rmSync(tmpDir, { recursive: true, force: true });
221
- });
222
-
223
- it("displays global config as key-value pairs in human-readable mode", async () => {
224
- const program = makeProgram();
225
- registerConfigCommand(program);
226
-
227
- const capture = await runCommand(program, ["config", "show"]);
228
- const text = capture.text();
229
- capture.restore();
230
-
231
- expect(text).toContain("Global Configuration");
232
- expect(text).toContain("cloudflare_account_id");
233
- expect(text).toContain("abc123");
234
- expect(text).toContain("Workers");
235
- expect(text).toContain("d1-worker");
236
- });
237
-
238
- it("outputs JSON when --json flag is set", async () => {
239
- const program = makeProgram();
240
- registerConfigCommand(program);
241
-
242
- const capture = await runCommand(program, ["--json", "config", "show"]);
243
- const json = capture.json<{
244
- global?: Record<string, string>;
245
- workers?: Record<string, unknown>;
246
- }>();
247
- capture.restore();
248
-
249
- expect(json.global).toBeDefined();
250
- expect(json.global?.cloudflare_account_id).toBe("abc123");
251
- expect(json.workers).toBeDefined();
252
- });
253
-
254
- it("handles missing config file gracefully", async () => {
255
- // Create empty temp dir without wrangler.jsonc
256
- const emptyDir = mkdtempSync(join(tmpdir(), "empty-config-"));
257
- process.chdir(emptyDir);
258
-
259
- const program = makeProgram();
260
- registerConfigCommand(program);
261
-
262
- const capture = await runCommand(program, ["config", "show"]);
263
- const text = capture.text();
264
- capture.restore();
265
-
266
- process.chdir(tmpDir);
267
- rmSync(emptyDir, { recursive: true, force: true });
268
-
269
- expect(text).toContain("Failed to show config");
270
- });
271
- });
272
-
273
- // ---------------------------------------------------------------------------
274
- // Tests: config set
275
- // ---------------------------------------------------------------------------
276
-
277
- describe("config set", () => {
278
- let tmpDir: string;
279
- let originalCwd: string;
280
-
281
- beforeEach(() => {
282
- tmpDir = setupTempConfig();
283
- originalCwd = process.cwd();
284
- process.chdir(tmpDir);
285
- });
286
-
287
- afterEach(() => {
288
- process.chdir(originalCwd);
289
- rmSync(tmpDir, { recursive: true, force: true });
290
- });
291
-
292
- it("updates a global config value", async () => {
293
- const program = makeProgram();
294
- registerConfigCommand(program);
295
-
296
- const capture = await runCommand(program, [
297
- "config",
298
- "set",
299
- "global.cloudflare_account_id",
300
- "new-id-456",
301
- ]);
302
- const text = capture.text();
303
- capture.restore();
304
-
305
- // Read the file back to verify the update
306
- const updated = await Bun.file("wrangler.jsonc").text();
307
- const parsed = JSON.parse(updated) as {
308
- global: { cloudflare_account_id: string };
309
- };
310
-
311
- expect(text).toContain("Updated");
312
- expect(parsed.global.cloudflare_account_id).toBe("new-id-456");
313
- });
314
-
315
- it("updates a nested worker var value", async () => {
316
- const program = makeProgram();
317
- registerConfigCommand(program);
318
-
319
- const capture = await runCommand(program, [
320
- "config",
321
- "set",
322
- "workers.d1-worker.vars.database_name",
323
- "new-db",
324
- ]);
325
- capture.restore();
326
-
327
- const updated = await Bun.file("wrangler.jsonc").text();
328
- const parsed = JSON.parse(updated) as {
329
- workers: Record<string, { vars: Record<string, string> }>;
330
- };
331
-
332
- expect(parsed.workers["d1-worker"].vars.database_name).toBe("new-db");
333
- });
334
-
335
- it("coerces boolean strings to booleans", async () => {
336
- const program = makeProgram();
337
- registerConfigCommand(program);
338
-
339
- const capture = await runCommand(program, [
340
- "config",
341
- "set",
342
- "workers.d1-worker.enabled",
343
- "false",
344
- ]);
345
- capture.restore();
346
-
347
- const updated = await Bun.file("wrangler.jsonc").text();
348
- const parsed = JSON.parse(updated) as {
349
- workers: Record<string, { enabled: boolean }>;
350
- };
351
-
352
- expect(parsed.workers["d1-worker"].enabled).toBe(false);
353
- });
354
-
355
- it("coerces numeric strings to numbers", async () => {
356
- const program = makeProgram();
357
- registerConfigCommand(program);
358
-
359
- const capture = await runCommand(program, [
360
- "config",
361
- "set",
362
- "workers.trade-worker.vars.max_retries",
363
- "5",
364
- ]);
365
- capture.restore();
366
-
367
- const updated = await Bun.file("wrangler.jsonc").text();
368
- const parsed = JSON.parse(updated) as {
369
- workers: Record<string, { vars: Record<string, number> }>;
370
- };
371
-
372
- expect(parsed.workers["trade-worker"].vars.max_retries).toBe(5);
373
- });
374
-
375
- it("reports error for invalid key paths", async () => {
376
- const program = makeProgram();
377
- registerConfigCommand(program);
378
-
379
- const capture = await runCommand(program, [
380
- "config",
381
- "set",
382
- "nonexistent.deep.path",
383
- "value",
384
- ]);
385
- const text = capture.text();
386
- capture.restore();
387
-
388
- // The command should output an error message
389
- expect(text.length).toBeGreaterThan(0);
390
- });
391
- });
392
-
393
- // ---------------------------------------------------------------------------
394
- // Tests: config secrets list (no Cloudflare API needed)
395
- // ---------------------------------------------------------------------------
396
-
397
- describe("config secrets", () => {
398
- let tmpDir: string;
399
- let originalCwd: string;
400
-
401
- beforeEach(() => {
402
- tmpDir = setupTempConfig();
403
- originalCwd = process.cwd();
404
- process.chdir(tmpDir);
405
- });
406
-
407
- afterEach(() => {
408
- process.chdir(originalCwd);
409
- rmSync(tmpDir, { recursive: true, force: true });
410
- });
411
-
412
- it("lists secrets for all workers", async () => {
413
- const program = makeProgram();
414
- registerConfigCommand(program);
415
-
416
- const capture = await runCommand(program, ["config", "secrets", "list"]);
417
- const text = capture.text();
418
- capture.restore();
419
-
420
- expect(text).toContain("trade-worker");
421
- expect(text).toContain("API_KEY");
422
- expect(text).toContain("API_SECRET");
423
- expect(text).toContain("d1-worker");
424
- expect(text).toContain("SECRET_ONE");
425
- });
426
-
427
- it("lists secrets for a specific worker", async () => {
428
- const program = makeProgram();
429
- registerConfigCommand(program);
430
-
431
- const capture = await runCommand(program, [
432
- "config",
433
- "secrets",
434
- "list",
435
- "trade-worker",
436
- ]);
437
- const text = capture.text();
438
- capture.restore();
439
-
440
- expect(text).toContain("trade-worker");
441
- expect(text).toContain("API_KEY");
442
- expect(text).toContain("API_SECRET");
443
- });
444
-
445
- it("outputs JSON for secrets list when --json is set", async () => {
446
- const program = makeProgram();
447
- registerConfigCommand(program);
448
-
449
- const capture = await runCommand(program, [
450
- "--json",
451
- "config",
452
- "secrets",
453
- "list",
454
- ]);
455
- const json = capture.json<Record<string, string[]>>();
456
- capture.restore();
457
-
458
- expect(json["trade-worker"]).toBeDefined();
459
- expect(json["trade-worker"]).toContain("API_KEY");
460
- });
461
-
462
- it("shows message when no secrets declared on specific worker", async () => {
463
- const program = makeProgram();
464
- registerConfigCommand(program);
465
-
466
- const capture = await runCommand(program, [
467
- "config",
468
- "secrets",
469
- "list",
470
- "hoox",
471
- ]);
472
- const text = capture.text();
473
- capture.restore();
474
-
475
- // "hoox" is in the config but might not have secrets or may be missing
476
- expect(text.length).toBeGreaterThan(0);
477
- });
478
- });
479
-
480
- // ---------------------------------------------------------------------------
481
- // Tests: config keys
482
- // ---------------------------------------------------------------------------
483
-
484
- describe("config keys", () => {
485
- let tmpDir: string;
486
- let originalCwd: string;
487
-
488
- beforeEach(() => {
489
- tmpDir = setupTempConfig();
490
- originalCwd = process.cwd();
491
- process.chdir(tmpDir);
492
- });
493
-
494
- afterEach(() => {
495
- process.chdir(originalCwd);
496
- rmSync(tmpDir, { recursive: true, force: true });
497
- });
498
-
499
- it("generates key files in .keys/ directory", async () => {
500
- const program = makeProgram();
501
- registerConfigCommand(program);
502
-
503
- const capture = await runCommand(program, ["config", "keys", "generate"]);
504
- const text = capture.text();
505
- capture.restore();
506
-
507
- expect(text).toContain("Generated");
508
- expect(text).toContain(".keys/");
509
-
510
- // Verify files were created
511
- const internalKeyFile = Bun.file(".keys/internal_service_key.env");
512
- expect(await internalKeyFile.exists()).toBe(true);
513
- const content = await internalKeyFile.text();
514
- expect(content).toMatch(/^INTERNAL_SERVICE_KEY=[a-f0-9]{64}\n$/);
515
- });
516
-
517
- it("lists generated key files", async () => {
518
- // First generate keys
519
- const prog1 = makeProgram();
520
- registerConfigCommand(prog1);
521
- const c1 = await runCommand(prog1, ["config", "keys", "generate"]);
522
- c1.restore();
523
-
524
- // Now list them
525
- const prog2 = makeProgram();
526
- registerConfigCommand(prog2);
527
- const capture = await runCommand(prog2, ["config", "keys", "list"]);
528
- const text = capture.text();
529
- capture.restore();
530
-
531
- expect(text).toContain("Key files");
532
- expect(text).toContain("****"); // Values are masked
533
- });
534
-
535
- it("handles missing .keys/ directory gracefully for list", async () => {
536
- const program = makeProgram();
537
- registerConfigCommand(program);
538
-
539
- const capture = await runCommand(program, ["config", "keys", "list"]);
540
- const text = capture.text();
541
- capture.restore();
542
-
543
- expect(text).toContain("No .keys/");
544
- });
545
-
546
- it("outputs JSON for keys list when --json is set", async () => {
547
- // First generate keys
548
- const prog1 = makeProgram();
549
- registerConfigCommand(prog1);
550
- const c1 = await runCommand(prog1, ["config", "keys", "generate"]);
551
- c1.restore();
552
-
553
- const prog2 = makeProgram();
554
- registerConfigCommand(prog2);
555
- const capture = await runCommand(prog2, [
556
- "--json",
557
- "config",
558
- "keys",
559
- "list",
560
- ]);
561
- const json = capture.json<{ keys: number; files: string[] }>();
562
- capture.restore();
563
-
564
- expect(json.keys).toBeGreaterThan(0);
565
- expect(Array.isArray(json.files)).toBe(true);
566
- });
567
-
568
- it("generates hex-formatted keys of expected length", async () => {
569
- const program = makeProgram();
570
- registerConfigCommand(program);
571
-
572
- const capture = await runCommand(program, ["config", "keys", "generate"]);
573
- capture.restore();
574
-
575
- const file = Bun.file(".keys/webhook_api_key.env");
576
- expect(await file.exists()).toBe(true);
577
- const content = await file.text();
578
- const match = content.match(/^WEBHOOK_API_KEY=([a-f0-9]+)\n$/);
579
- expect(match).not.toBeNull();
580
- // Service keys are 32 bytes = 64 hex chars
581
- expect(match![1].length).toBe(64);
582
- });
583
- });