@jango-blockchained/hoox-cli 0.5.2 → 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 (114) 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 -1087
  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 -97
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -440
  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 -896
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -292
  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 -130
  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 -311
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -324
  27. package/src/commands/deploy/deploy-command.ts +0 -1054
  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 -405
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/disclaimer/disclaimer-command.ts +0 -21
  35. package/src/commands/disclaimer/index.ts +0 -1
  36. package/src/commands/infra/index.ts +0 -5
  37. package/src/commands/infra/infra-command.test.ts +0 -719
  38. package/src/commands/infra/infra-command.ts +0 -974
  39. package/src/commands/infra/types.ts +0 -23
  40. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  41. package/src/commands/init/cli-provisioner.ts +0 -71
  42. package/src/commands/init/index.ts +0 -1
  43. package/src/commands/init/init-command.test.ts +0 -854
  44. package/src/commands/init/init-command.ts +0 -597
  45. package/src/commands/init/types.ts +0 -23
  46. package/src/commands/logs/index.ts +0 -1
  47. package/src/commands/logs/logs-command.test.ts +0 -293
  48. package/src/commands/logs/logs-command.ts +0 -452
  49. package/src/commands/monitor/index.ts +0 -2
  50. package/src/commands/monitor/monitor-command.test.ts +0 -271
  51. package/src/commands/monitor/monitor-command.ts +0 -269
  52. package/src/commands/monitor/monitor-service.ts +0 -65
  53. package/src/commands/monitor/types.ts +0 -13
  54. package/src/commands/repair/index.ts +0 -2
  55. package/src/commands/repair/repair-command.test.ts +0 -243
  56. package/src/commands/repair/repair-command.ts +0 -220
  57. package/src/commands/repair/repair-service.ts +0 -164
  58. package/src/commands/repair/types.ts +0 -13
  59. package/src/commands/schema/index.ts +0 -1
  60. package/src/commands/schema/schema-command.ts +0 -137
  61. package/src/commands/test/index.ts +0 -2
  62. package/src/commands/test/test-command.test.ts +0 -318
  63. package/src/commands/test/test-command.ts +0 -406
  64. package/src/commands/tui/index.ts +0 -1
  65. package/src/commands/tui/tui-command.ts +0 -87
  66. package/src/commands/update/index.ts +0 -1
  67. package/src/commands/update/update-command.ts +0 -109
  68. package/src/commands/waf/index.ts +0 -2
  69. package/src/commands/waf/types.ts +0 -48
  70. package/src/commands/waf/waf-command.test.ts +0 -506
  71. package/src/commands/waf/waf-command.ts +0 -545
  72. package/src/index.ts +0 -261
  73. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  74. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  75. package/src/services/cloudflare/index.ts +0 -2
  76. package/src/services/cloudflare/types.ts +0 -30
  77. package/src/services/config/config-service.test.ts +0 -395
  78. package/src/services/config/config-service.ts +0 -202
  79. package/src/services/config/index.ts +0 -9
  80. package/src/services/config/types.ts +0 -66
  81. package/src/services/db/db-service.test.ts +0 -63
  82. package/src/services/db/db-service.ts +0 -131
  83. package/src/services/db/index.ts +0 -1
  84. package/src/services/docker/docker-service.ts +0 -155
  85. package/src/services/docker/index.ts +0 -1
  86. package/src/services/env/env-service.test.ts +0 -259
  87. package/src/services/env/env-service.ts +0 -372
  88. package/src/services/env/index.ts +0 -1
  89. package/src/services/kv/index.ts +0 -1
  90. package/src/services/kv/kv-sync-service.test.ts +0 -38
  91. package/src/services/kv/kv-sync-service.ts +0 -243
  92. package/src/services/prerequisites/index.ts +0 -1
  93. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  94. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  95. package/src/services/prerequisites/types.ts +0 -48
  96. package/src/services/schema/index.ts +0 -1
  97. package/src/services/schema/schema-service.ts +0 -99
  98. package/src/services/secrets/index.ts +0 -11
  99. package/src/services/secrets/secrets-service.test.ts +0 -502
  100. package/src/services/secrets/secrets-service.ts +0 -287
  101. package/src/services/secrets/types.ts +0 -50
  102. package/src/services/update/index.ts +0 -2
  103. package/src/services/update/update-service.test.ts +0 -76
  104. package/src/services/update/update-service.ts +0 -193
  105. package/src/ui/banner.ts +0 -141
  106. package/src/ui/index.ts +0 -8
  107. package/src/ui/menu.ts +0 -490
  108. package/src/utils/error-handler.ts +0 -62
  109. package/src/utils/errors.test.ts +0 -69
  110. package/src/utils/errors.ts +0 -24
  111. package/src/utils/formatters.test.ts +0 -180
  112. package/src/utils/formatters.ts +0 -269
  113. package/src/utils/git.ts +0 -134
  114. package/src/utils/theme.ts +0 -92
@@ -1,506 +0,0 @@
1
- /**
2
- * Unit tests for `hoox waf` command.
3
- *
4
- * Mocks CloudflareService.zonesList() and global fetch() for Cloudflare API
5
- * calls. Tests verify command action functions produce correct output and
6
- * handle errors gracefully.
7
- */
8
-
9
- import {
10
- afterEach,
11
- beforeEach,
12
- describe,
13
- expect,
14
- it,
15
- mock,
16
- spyOn,
17
- } from "bun:test";
18
- import { Command } from "commander";
19
- import { registerWafCommand } from "./waf-command.js";
20
- import { CloudflareService } from "../../services/cloudflare/cloudflare-service.js";
21
- import type { WranglerResult } from "../../services/cloudflare/types.js";
22
-
23
- // ---------------------------------------------------------------------------
24
- // Helpers
25
- // ---------------------------------------------------------------------------
26
-
27
- const realFetch = globalThis.fetch;
28
- const realEnv = { ...process.env };
29
-
30
- /** Mock CloudflareService.zonesList to return a known zone. */
31
- function mockZonesList(output: string, ok = true, error?: string): void {
32
- const result: WranglerResult<string> = ok
33
- ? { ok: true, value: output }
34
- : { ok: false, error: error ?? "zone list failed" };
35
-
36
- spyOn(CloudflareService.prototype, "zonesList").mockImplementation(() =>
37
- Promise.resolve(result)
38
- );
39
- }
40
-
41
- /**
42
- * Sets up a mock fetch that returns a CF-style JSON response.
43
- * Returns the list of calls made to fetch for assertions.
44
- */
45
- function mockCfFetch(
46
- responseMap: Record<string, unknown>
47
- ): Array<{ url: string; method: string; body?: string }> {
48
- const calls: Array<{ url: string; method: string; body?: string }> = [];
49
-
50
- const fetchMock = mock(
51
- async (url: string, init?: Record<string, unknown>) => {
52
- void init; // Mark as used
53
- // Find matching response by path partial match
54
- let matched: unknown = null;
55
- for (const [pathPattern, response] of Object.entries(responseMap)) {
56
- if ((url as string).includes(pathPattern)) {
57
- matched = response;
58
- break;
59
- }
60
- }
61
-
62
- calls.push({
63
- url: url as string,
64
- method: (init?.method ?? "GET") as string,
65
- body: init?.body as string | undefined,
66
- });
67
-
68
- if (
69
- matched &&
70
- typeof matched === "object" &&
71
- matched !== null &&
72
- "error" in (matched as Record<string, unknown>)
73
- ) {
74
- // Error response
75
- return {
76
- ok: false,
77
- status: 400,
78
- json: async () => ({
79
- success: false,
80
- errors: [{ message: (matched as Record<string, string>).error }],
81
- }),
82
- };
83
- }
84
-
85
- return {
86
- ok: true,
87
- status: 200,
88
- json: async () => ({
89
- success: true,
90
- result: matched,
91
- }),
92
- };
93
- }
94
- );
95
-
96
- (globalThis as unknown as Record<string, unknown>).fetch = fetchMock;
97
- return calls;
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // Setup / Teardown
102
- // ---------------------------------------------------------------------------
103
-
104
- beforeEach(() => {
105
- // Set required env vars for CF API calls
106
- process.env.CLOUDFLARE_API_TOKEN = "test-token";
107
- process.env.CLOUDFLARE_ACCOUNT_ID = "test-account-id";
108
- process.env.CLOUDFLARE_ZONE_ID = ""; // force zonesList path
109
- });
110
-
111
- afterEach(() => {
112
- (globalThis as unknown as Record<string, unknown>).fetch = realFetch;
113
- process.env = { ...realEnv };
114
- mock.restore();
115
- });
116
-
117
- // ---------------------------------------------------------------------------
118
- // Helper: build a Commander program with waf registered
119
- // ---------------------------------------------------------------------------
120
-
121
- function makeProgram(): Command {
122
- const program = new Command()
123
- .name("hoox")
124
- .option("--json", "JSON output")
125
- .option("--quiet", "Minimal output");
126
-
127
- registerWafCommand(program);
128
- return program;
129
- }
130
-
131
- /** Run a command string (e.g. "waf status --json") and capture stdout. */
132
- async function runCommand(
133
- args: string
134
- ): Promise<{ stdout: string; exitCode: number }> {
135
- const program = makeProgram();
136
-
137
- let stdout = "";
138
- const writeSpy = spyOn(process.stdout, "write").mockImplementation(
139
- (chunk: unknown) => {
140
- stdout += typeof chunk === "string" ? chunk : String(chunk ?? "");
141
- return true;
142
- }
143
- );
144
-
145
- // We don't want process.exit() to kill the test runner
146
- const exitSpy = spyOn(process, "exit").mockImplementation(
147
- (() => {}) as never
148
- );
149
-
150
- try {
151
- await program.parseAsync([...args.split(" ")], { from: "user" });
152
- } catch {
153
- // Some commands may fail — that's fine, we test output
154
- }
155
-
156
- writeSpy.mockRestore();
157
- exitSpy.mockRestore();
158
-
159
- return { stdout, exitCode: 0 };
160
- }
161
-
162
- // ---------------------------------------------------------------------------
163
- // Tests
164
- // ---------------------------------------------------------------------------
165
-
166
- describe("waf command", () => {
167
- // -- waf status ------------------------------------------------------------
168
-
169
- describe("status", () => {
170
- it("shows WAF status when enabled", async () => {
171
- mockZonesList("example.com (abc123def456)");
172
- mockCfFetch({
173
- "/settings/waf": { id: "waf", value: "on" },
174
- "/firewall/rules": [
175
- {
176
- id: "rule-1",
177
- description: "Block bad IPs",
178
- action: "block",
179
- filter: { id: "f1", expression: "(ip.src eq 1.2.3.4)" },
180
- created_on: "2026-01-01T00:00:00Z",
181
- modified_on: "2026-01-02T00:00:00Z",
182
- },
183
- ],
184
- "/analytics/dashboard": { totals: { threats: 42 } },
185
- });
186
-
187
- const { stdout } = await runCommand("waf status");
188
-
189
- expect(stdout).toContain("example.com");
190
- expect(stdout).toContain("ENABLED");
191
- expect(stdout).toContain("1 active");
192
- expect(stdout).toContain("42");
193
- });
194
-
195
- it("shows WAF status when disabled", async () => {
196
- mockZonesList("example.com (abc123def456)");
197
- mockCfFetch({
198
- "/settings/waf": { id: "waf", value: "off" },
199
- "/firewall/rules": [],
200
- "/analytics/dashboard": { totals: { threats: 0 } },
201
- });
202
-
203
- const { stdout } = await runCommand("waf status");
204
-
205
- expect(stdout).toContain("DISABLED");
206
- expect(stdout).toContain("0 active");
207
- expect(stdout).toContain("0");
208
- });
209
-
210
- it("outputs JSON when --json flag is used", async () => {
211
- mockZonesList("example.com (abc123def456)");
212
- mockCfFetch({
213
- "/settings/waf": { id: "waf", value: "on" },
214
- "/firewall/rules": [],
215
- "/analytics/dashboard": { totals: { threats: 0 } },
216
- });
217
-
218
- const { stdout } = await runCommand("waf status --json");
219
-
220
- const parsed = JSON.parse(stdout.trim());
221
- expect(parsed).toHaveProperty("enabled", true);
222
- expect(parsed).toHaveProperty("zoneId", "abc123def456");
223
- expect(parsed).toHaveProperty("zoneName", "example.com");
224
- });
225
-
226
- it("throws when zonesList fails", async () => {
227
- mockZonesList("", false, "auth error");
228
-
229
- const { stdout } = await runCommand("waf status");
230
-
231
- expect(stdout).toContain("Failed to list zones");
232
- });
233
-
234
- it("uses CLOUDFLARE_ZONE_ID env var when set", async () => {
235
- process.env.CLOUDFLARE_ZONE_ID = "direct-zone-id";
236
- mockCfFetch({
237
- "/settings/waf": { id: "waf", value: "on" },
238
- "/firewall/rules": [],
239
- "/analytics/dashboard": { totals: { threats: 0 } },
240
- });
241
-
242
- const { stdout } = await runCommand("waf status");
243
-
244
- expect(stdout).toContain("direct-zone-id");
245
- });
246
- });
247
-
248
- // -- waf rules list --------------------------------------------------------
249
-
250
- describe("rules list", () => {
251
- it("lists firewall rules in human-readable format", async () => {
252
- mockZonesList("example.com (zone-1)");
253
- mockCfFetch({
254
- "/firewall/rules": [
255
- {
256
- id: "rule-1-is-a-very-long-id",
257
- description: "IP blocklist rule",
258
- action: "block",
259
- filter: {
260
- id: "f1",
261
- expression: "(ip.src eq 10.0.0.1)",
262
- },
263
- created_on: "2026-01-01T00:00:00Z",
264
- modified_on: "2026-01-02T00:00:00Z",
265
- },
266
- {
267
- id: "rule-2-short",
268
- description: "",
269
- action: "allow",
270
- filter: {
271
- id: "f2",
272
- expression: '(http.request.uri.path contains "/api")',
273
- },
274
- created_on: "2026-01-03T00:00:00Z",
275
- modified_on: "2026-01-04T00:00:00Z",
276
- },
277
- ],
278
- });
279
-
280
- const { stdout } = await runCommand("waf rules list");
281
-
282
- expect(stdout).toContain("IP blocklist rule");
283
- expect(stdout).toContain("block");
284
- expect(stdout).toContain("allow");
285
- expect(stdout).toContain("(ip.src eq 10.0.0.1)");
286
- });
287
-
288
- it("outputs JSON when --json flag is used", async () => {
289
- mockZonesList("example.com (zone-1)");
290
- mockCfFetch({
291
- "/firewall/rules": [
292
- {
293
- id: "rule-1",
294
- description: "Test rule",
295
- action: "block",
296
- filter: { id: "f1", expression: "(ip.src eq 1.1.1.1)" },
297
- created_on: "2026-01-01T00:00:00Z",
298
- modified_on: "2026-01-01T00:00:00Z",
299
- },
300
- ],
301
- });
302
-
303
- const { stdout } = await runCommand("waf rules list --json");
304
-
305
- const parsed = JSON.parse(stdout.trim());
306
- expect(Array.isArray(parsed)).toBe(true);
307
- expect(parsed[0]).toHaveProperty("id", "rule-1");
308
- });
309
-
310
- it("shows empty message when no rules exist", async () => {
311
- mockZonesList("example.com (zone-1)");
312
- mockCfFetch({ "/firewall/rules": [] });
313
-
314
- const { stdout } = await runCommand("waf rules list");
315
-
316
- expect(stdout).toContain("No firewall rules");
317
- });
318
-
319
- it("throws when rules fetch fails", async () => {
320
- mockZonesList("example.com (zone-1)");
321
- mockCfFetch({
322
- "/firewall/rules": { error: "API error" },
323
- });
324
-
325
- const { stdout } = await runCommand("waf rules list");
326
-
327
- expect(stdout).toContain("API error");
328
- });
329
- });
330
-
331
- // -- waf rules add ---------------------------------------------------------
332
-
333
- describe("rules add", () => {
334
- it("adds an IP allowlist rule", async () => {
335
- mockZonesList("example.com (zone-1)");
336
- mockCfFetch({
337
- "/filters": { id: "filter-1" },
338
- // NOTE: the firewall/rules endpoint is matched but returns array
339
- "/firewall/rules": [
340
- {
341
- id: "new-rule-1",
342
- description: "ip-allowlist rule for 192.168.1.1",
343
- action: "allow",
344
- filter: { id: "filter-1", expression: "(ip.src eq 192.168.1.1)" },
345
- created_on: "2026-01-01T00:00:00Z",
346
- modified_on: "2026-01-01T00:00:00Z",
347
- },
348
- ],
349
- });
350
-
351
- const { stdout } = await runCommand(
352
- "waf rules add ip-allowlist 192.168.1.1"
353
- );
354
-
355
- expect(stdout).toContain("WAF rule added");
356
- expect(stdout).toContain("192.168.1.1");
357
- });
358
-
359
- it("rejects invalid rule types", async () => {
360
- mockZonesList("example.com (zone-1)");
361
-
362
- const { stdout } = await runCommand("waf rules add invalid-type 1.2.3.4");
363
-
364
- expect(stdout).toContain("Invalid rule type");
365
- });
366
-
367
- it("throws when filter creation fails", async () => {
368
- mockZonesList("example.com (zone-1)");
369
- mockCfFetch({
370
- "/filters": { error: "Filter creation failed" },
371
- });
372
-
373
- const { stdout } = await runCommand(
374
- "waf rules add ip-blocklist 10.0.0.1"
375
- );
376
-
377
- expect(stdout).toContain("Filter creation failed");
378
- });
379
- });
380
-
381
- // -- waf rules remove ------------------------------------------------------
382
-
383
- describe("rules remove", () => {
384
- it("removes a WAF rule by ID", async () => {
385
- mockZonesList("example.com (zone-1)");
386
- mockCfFetch({
387
- "/firewall/rules/rule-to-delete": { id: "rule-to-delete" },
388
- });
389
-
390
- const { stdout } = await runCommand("waf rules remove rule-to-delete");
391
-
392
- expect(stdout).toContain("removed");
393
- expect(stdout).toContain("rule-to-delete");
394
- });
395
-
396
- it("throws when rule deletion fails", async () => {
397
- mockZonesList("example.com (zone-1)");
398
- mockCfFetch({
399
- "/firewall/rules/bad-id": { error: "Rule not found" },
400
- });
401
-
402
- const { stdout } = await runCommand("waf rules remove bad-id");
403
-
404
- expect(stdout).toContain("Rule not found");
405
- });
406
- });
407
-
408
- // -- waf mode --------------------------------------------------------------
409
-
410
- describe("mode", () => {
411
- it("enables WAF on the zone", async () => {
412
- mockZonesList("example.com (zone-1)");
413
- mockCfFetch({
414
- "/settings/waf": { id: "waf", value: "on" },
415
- });
416
-
417
- const { stdout } = await runCommand("waf mode enable");
418
-
419
- expect(stdout).toContain("enabled");
420
- expect(stdout).toContain("example.com");
421
- });
422
-
423
- it("disables WAF on the zone", async () => {
424
- mockZonesList("example.com (zone-1)");
425
- mockCfFetch({
426
- "/settings/waf": { id: "waf", value: "off" },
427
- });
428
-
429
- const { stdout } = await runCommand("waf mode disable");
430
-
431
- expect(stdout).toContain("disabled");
432
- expect(stdout).toContain("example.com");
433
- });
434
-
435
- it("throws when mode update fails", async () => {
436
- mockZonesList("example.com (zone-1)");
437
- mockCfFetch({
438
- "/settings/waf": { error: "Permission denied" },
439
- });
440
-
441
- const { stdout } = await runCommand("waf mode enable");
442
-
443
- expect(stdout).toContain("Permission denied");
444
- });
445
- });
446
-
447
- // -- Error handling --------------------------------------------------------
448
-
449
- describe("error handling", () => {
450
- it("throws when CLOUDFLARE_API_TOKEN is missing", async () => {
451
- delete process.env.CLOUDFLARE_API_TOKEN;
452
- mockZonesList("example.com (zone-1)");
453
-
454
- const { stdout } = await runCommand("waf status");
455
-
456
- expect(stdout).toContain("CLOUDFLARE_API_TOKEN");
457
- });
458
-
459
- it("throws when CLOUDFLARE_ACCOUNT_ID is missing", async () => {
460
- delete process.env.CLOUDFLARE_ACCOUNT_ID;
461
- mockZonesList("example.com (zone-1)");
462
-
463
- const { stdout } = await runCommand("waf status");
464
-
465
- expect(stdout).toContain("CLOUDFLARE_ACCOUNT_ID");
466
- });
467
- });
468
-
469
- // -- Subcommand structure --------------------------------------------------
470
-
471
- describe("subcommand structure", () => {
472
- it("registers waf as a command", () => {
473
- const program = makeProgram();
474
- const wafCmd = program.commands.find((c) => c.name() === "waf");
475
- expect(wafCmd).toBeDefined();
476
-
477
- const subNames = (wafCmd as Command).commands.map((c) => c.name());
478
- expect(subNames).toContain("status");
479
- expect(subNames).toContain("rules");
480
- expect(subNames).toContain("mode");
481
- });
482
-
483
- it("registers rules subcommands", () => {
484
- const program = makeProgram();
485
- const wafCmd = program.commands.find((c) => c.name() === "waf")!;
486
- const rulesCmd = (wafCmd as Command).commands.find(
487
- (c) => c.name() === "rules"
488
- )!;
489
- const ruleSubNames = (rulesCmd as Command).commands.map((c) => c.name());
490
- expect(ruleSubNames).toContain("list");
491
- expect(ruleSubNames).toContain("add");
492
- expect(ruleSubNames).toContain("remove");
493
- });
494
-
495
- it("registers mode subcommands", () => {
496
- const program = makeProgram();
497
- const wafCmd = program.commands.find((c) => c.name() === "waf")!;
498
- const modeCmd = (wafCmd as Command).commands.find(
499
- (c) => c.name() === "mode"
500
- )!;
501
- const modeSubNames = (modeCmd as Command).commands.map((c) => c.name());
502
- expect(modeSubNames).toContain("enable");
503
- expect(modeSubNames).toContain("disable");
504
- });
505
- });
506
- });