@jango-blockchained/hoox-cli 0.5.2 → 0.5.7

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 +32989 -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,395 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { ConfigService } from "./config-service.js";
6
-
7
- /**
8
- * Helper: write a wrangler.jsonc file to a test directory using Bun.
9
- */
10
- async function writeConfig(dir: string, content: string): Promise<void> {
11
- await Bun.write(join(dir, "wrangler.jsonc"), content);
12
- }
13
-
14
- /**
15
- * Helper: create a minimal valid config JSON string.
16
- */
17
- function validConfigJson(): string {
18
- return JSON.stringify(
19
- {
20
- global: {
21
- cloudflare_account_id: "abc123",
22
- },
23
- workers: {
24
- "test-worker": {
25
- enabled: true,
26
- path: "workers/test-worker",
27
- },
28
- },
29
- },
30
- null,
31
- 2
32
- );
33
- }
34
-
35
- /**
36
- * Helper: create a valid config with JSONC comments.
37
- */
38
- function validConfigWithComments(): string {
39
- return `{
40
- // This is a line comment
41
- "global": {
42
- /* Block comment explaining account_id */
43
- "cloudflare_account_id": "abc123"
44
- },
45
- "workers": {
46
- "test-worker": {
47
- "enabled": true,
48
- "path": "workers/test-worker" // inline comment
49
- }
50
- }
51
- }`;
52
- }
53
-
54
- describe("ConfigService", () => {
55
- let tmpDir: string;
56
-
57
- beforeEach(() => {
58
- tmpDir = mkdtempSync(join(tmpdir(), "hoox-config-test-"));
59
- });
60
-
61
- afterEach(() => {
62
- rmSync(tmpDir, { recursive: true, force: true });
63
- });
64
-
65
- // ── Constructor defaults ──────────────────────────────────────────
66
-
67
- it("defaults config path to wrangler.jsonc in cwd", () => {
68
- const service = new ConfigService();
69
- // Constructor stores the default path — verified via load() behavior
70
- expect(service).toBeInstanceOf(ConfigService);
71
- });
72
-
73
- it("accepts a custom config path in constructor", () => {
74
- const customPath = "/tmp/custom-wrangler.jsonc";
75
- const service = new ConfigService(customPath);
76
- expect(service).toBeInstanceOf(ConfigService);
77
- });
78
-
79
- // ── load() — happy path ───────────────────────────────────────────
80
-
81
- it("loads and parses a valid wrangler.jsonc", async () => {
82
- await writeConfig(tmpDir, validConfigJson());
83
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
84
- const config = await service.load();
85
- expect(config.global.cloudflare_account_id).toBe("abc123");
86
- expect(config.workers["test-worker"]).toBeDefined();
87
- });
88
-
89
- it("loads a config with enabled/disabled workers", async () => {
90
- const content = JSON.stringify({
91
- global: { cloudflare_account_id: "abc123" },
92
- workers: {
93
- "worker-a": { enabled: true, path: "workers/a" },
94
- "worker-b": { enabled: false, path: "workers/b" },
95
- "worker-c": { enabled: true, path: "workers/c" },
96
- },
97
- });
98
- await writeConfig(tmpDir, content);
99
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
100
- await service.load();
101
- expect(service.listWorkers()).toEqual(["worker-a", "worker-b", "worker-c"]);
102
- expect(service.listEnabledWorkers()).toEqual(["worker-a", "worker-c"]);
103
- });
104
-
105
- // ── load() — error cases ──────────────────────────────────────────
106
-
107
- it("throws when config file does not exist", async () => {
108
- const service = new ConfigService(join(tmpDir, "nonexistent.jsonc"));
109
- await expect(service.load()).rejects.toThrow("not found");
110
- });
111
-
112
- it("throws for invalid JSONC syntax", async () => {
113
- await writeConfig(tmpDir, "{ not valid json @@@ }");
114
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
115
- await expect(service.load()).rejects.toThrow("Invalid JSONC");
116
- });
117
-
118
- it("throws when root is not an object (e.g. an array)", async () => {
119
- await writeConfig(tmpDir, "[1, 2, 3]");
120
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
121
- await expect(service.load()).rejects.toThrow("must contain a JSON object");
122
- });
123
-
124
- it("throws when root is a primitive (e.g. string)", async () => {
125
- await writeConfig(tmpDir, '"just a string"');
126
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
127
- await expect(service.load()).rejects.toThrow("must contain a JSON object");
128
- });
129
-
130
- // ── Comment stripping (JSONC support) ─────────────────────────────
131
-
132
- it("strips // line comments via jsonc-parser", async () => {
133
- await writeConfig(tmpDir, validConfigWithComments());
134
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
135
- const config = await service.load();
136
- expect(config.global.cloudflare_account_id).toBe("abc123");
137
- });
138
-
139
- it("strips /* */ block comments via jsonc-parser", async () => {
140
- const content = `{
141
- /* multi
142
- line
143
- block */
144
- "global": { "cloudflare_account_id": "xyz" },
145
- "workers": { "w": { "enabled": true, "path": "p" } }
146
- }`;
147
- await writeConfig(tmpDir, content);
148
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
149
- const config = await service.load();
150
- expect(config.global.cloudflare_account_id).toBe("xyz");
151
- });
152
-
153
- it("handles pure JSON (no comments) correctly", async () => {
154
- await writeConfig(tmpDir, validConfigJson());
155
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
156
- const config = await service.load();
157
- expect(config.global.cloudflare_account_id).toBe("abc123");
158
- });
159
-
160
- // ── Accessor methods (after load) ─────────────────────────────────
161
-
162
- it("getWorker() returns a worker config by name", async () => {
163
- await writeConfig(tmpDir, validConfigJson());
164
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
165
- await service.load();
166
- const worker = service.getWorker("test-worker");
167
- expect(worker).toBeDefined();
168
- expect(worker?.enabled).toBe(true);
169
- expect(worker?.path).toBe("workers/test-worker");
170
- });
171
-
172
- it("getWorker() returns undefined for unknown worker", async () => {
173
- await writeConfig(tmpDir, validConfigJson());
174
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
175
- await service.load();
176
- expect(service.getWorker("nonexistent")).toBeUndefined();
177
- });
178
-
179
- it("getGlobal() returns the global config section", async () => {
180
- await writeConfig(tmpDir, validConfigJson());
181
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
182
- await service.load();
183
- const global = service.getGlobal();
184
- expect(global.cloudflare_account_id).toBe("abc123");
185
- });
186
-
187
- it("listWorkers() returns all worker names", async () => {
188
- const content = JSON.stringify({
189
- global: { cloudflare_account_id: "abc123" },
190
- workers: {
191
- w1: { enabled: true, path: "workers/w1" },
192
- w2: { enabled: false, path: "workers/w2" },
193
- },
194
- });
195
- await writeConfig(tmpDir, content);
196
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
197
- await service.load();
198
- expect(service.listWorkers().sort()).toEqual(["w1", "w2"]);
199
- });
200
-
201
- it("listEnabledWorkers() filters to only enabled workers", async () => {
202
- const content = JSON.stringify({
203
- global: { cloudflare_account_id: "abc123" },
204
- workers: {
205
- w1: { enabled: true, path: "workers/w1" },
206
- w2: { enabled: false, path: "workers/w2" },
207
- w3: { enabled: true, path: "workers/w3" },
208
- },
209
- });
210
- await writeConfig(tmpDir, content);
211
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
212
- await service.load();
213
- const enabled = service.listEnabledWorkers().sort();
214
- expect(enabled).toEqual(["w1", "w3"]);
215
- expect(enabled).not.toContain("w2");
216
- });
217
-
218
- it("listEnabledWorkers() returns empty array when all disabled", async () => {
219
- const content = JSON.stringify({
220
- global: { cloudflare_account_id: "abc123" },
221
- workers: {
222
- w1: { enabled: false, path: "workers/w1" },
223
- w2: { enabled: false, path: "workers/w2" },
224
- },
225
- });
226
- await writeConfig(tmpDir, content);
227
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
228
- await service.load();
229
- expect(service.listEnabledWorkers()).toEqual([]);
230
- });
231
-
232
- // ── Accessor guards (before load) ─────────────────────────────────
233
-
234
- it("getWorker() throws if called before load()", () => {
235
- const service = new ConfigService();
236
- expect(() => service.getWorker("any")).toThrow("not loaded");
237
- });
238
-
239
- it("getGlobal() throws if called before load()", () => {
240
- const service = new ConfigService();
241
- expect(() => service.getGlobal()).toThrow("not loaded");
242
- });
243
-
244
- it("listWorkers() throws if called before load()", () => {
245
- const service = new ConfigService();
246
- expect(() => service.listWorkers()).toThrow("not loaded");
247
- });
248
-
249
- it("listEnabledWorkers() throws if called before load()", () => {
250
- const service = new ConfigService();
251
- expect(() => service.listEnabledWorkers()).toThrow("not loaded");
252
- });
253
-
254
- // ── validate() ────────────────────────────────────────────────────
255
-
256
- it("validate() returns valid: true for a valid config", async () => {
257
- await writeConfig(tmpDir, validConfigJson());
258
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
259
- await service.load();
260
- const result = service.validate();
261
- expect(result.valid).toBe(true);
262
- expect(result.errors).toEqual([]);
263
- });
264
-
265
- it("validate() returns errors when config not loaded", () => {
266
- const service = new ConfigService();
267
- const result = service.validate();
268
- expect(result.valid).toBe(false);
269
- expect(result.errors[0]).toContain("not loaded");
270
- });
271
-
272
- it("validate() catches missing global.cloudflare_account_id", async () => {
273
- const content = JSON.stringify({
274
- global: {},
275
- workers: { w: { enabled: true, path: "workers/w" } },
276
- });
277
- await writeConfig(tmpDir, content);
278
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
279
- await service.load();
280
- const result = service.validate();
281
- expect(result.valid).toBe(false);
282
- expect(result.errors.some((e) => e.includes("cloudflare_account_id"))).toBe(
283
- true
284
- );
285
- });
286
-
287
- it("validate() catches missing global section entirely", async () => {
288
- const content = JSON.stringify({
289
- workers: { w: { enabled: true, path: "workers/w" } },
290
- });
291
- await writeConfig(tmpDir, content);
292
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
293
- await service.load();
294
- const result = service.validate();
295
- expect(result.valid).toBe(false);
296
- expect(result.errors.some((e) => e.includes("cloudflare_account_id"))).toBe(
297
- true
298
- );
299
- });
300
-
301
- it("validate() catches missing worker path", async () => {
302
- const content = JSON.stringify({
303
- global: { cloudflare_account_id: "abc123" },
304
- workers: {
305
- "bad-worker": { enabled: true },
306
- },
307
- });
308
- await writeConfig(tmpDir, content);
309
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
310
- await service.load();
311
- const result = service.validate();
312
- expect(result.valid).toBe(false);
313
- expect(result.errors.some((e) => e.includes("path"))).toBe(true);
314
- });
315
-
316
- it("validate() catches empty workers section", async () => {
317
- const content = JSON.stringify({
318
- global: { cloudflare_account_id: "abc123" },
319
- workers: {},
320
- });
321
- await writeConfig(tmpDir, content);
322
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
323
- await service.load();
324
- const result = service.validate();
325
- expect(result.valid).toBe(false);
326
- expect(result.errors.some((e) => e.includes("No workers"))).toBe(true);
327
- });
328
-
329
- it("validate() reports multiple errors at once", async () => {
330
- const content = JSON.stringify({
331
- global: {},
332
- workers: {
333
- w1: { enabled: true },
334
- w2: { enabled: false },
335
- },
336
- });
337
- await writeConfig(tmpDir, content);
338
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
339
- await service.load();
340
- const result = service.validate();
341
- expect(result.valid).toBe(false);
342
- // Should have at least 3 errors: account_id missing, w1 missing path, w2 missing path
343
- expect(result.errors.length).toBeGreaterThanOrEqual(3);
344
- });
345
-
346
- // ── Worker config with secrets and vars ───────────────────────────
347
-
348
- it("parses worker secrets array", async () => {
349
- const content = JSON.stringify({
350
- global: { cloudflare_account_id: "abc123" },
351
- workers: {
352
- "secrets-worker": {
353
- enabled: true,
354
- path: "workers/secrets-worker",
355
- secrets: ["API_KEY", "DB_PASSWORD"],
356
- },
357
- },
358
- });
359
- await writeConfig(tmpDir, content);
360
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
361
- await service.load();
362
- const worker = service.getWorker("secrets-worker");
363
- expect(worker?.secrets).toEqual(["API_KEY", "DB_PASSWORD"]);
364
- });
365
-
366
- it("parses worker vars", async () => {
367
- const content = JSON.stringify({
368
- global: { cloudflare_account_id: "abc123" },
369
- workers: {
370
- "vars-worker": {
371
- enabled: true,
372
- path: "workers/vars-worker",
373
- vars: { DATABASE_NAME: "my-db", LOG_LEVEL: "debug" },
374
- },
375
- },
376
- });
377
- await writeConfig(tmpDir, content);
378
- const service = new ConfigService(join(tmpDir, "wrangler.jsonc"));
379
- await service.load();
380
- const worker = service.getWorker("vars-worker");
381
- expect(worker?.vars).toEqual({
382
- DATABASE_NAME: "my-db",
383
- LOG_LEVEL: "debug",
384
- });
385
- });
386
-
387
- // ── load() called with explicit path overrides constructor ─────────
388
-
389
- it("load() with explicit path overrides the constructor path", async () => {
390
- await writeConfig(tmpDir, validConfigJson());
391
- const service = new ConfigService("/some/wrong/path.jsonc");
392
- const config = await service.load(join(tmpDir, "wrangler.jsonc"));
393
- expect(config.global.cloudflare_account_id).toBe("abc123");
394
- });
395
- });
@@ -1,202 +0,0 @@
1
- import { parse, printParseErrorCode } from "jsonc-parser";
2
- import type { ParseError } from "jsonc-parser";
3
- import type { HooxConfig, WorkerConfig, GlobalConfig } from "./types";
4
-
5
- /**
6
- * Loads, parses, and validates the central wrangler.jsonc configuration.
7
- *
8
- * Uses jsonc-parser for fault-tolerant JSONC parsing with native comment
9
- * support (both line and block comments). Provides typed accessors for the
10
- * global config and per-worker entries.
11
- *
12
- * @example
13
- * ```ts
14
- * const config = new ConfigService();
15
- * await config.load();
16
- * console.log(config.listEnabledWorkers()); // ["d1-worker", "hoox", ...]
17
- * ```
18
- */
19
- export class ConfigService {
20
- private config: HooxConfig | null = null;
21
- private configPath: string;
22
-
23
- /**
24
- * @param configPath - Absolute or relative path to wrangler.jsonc.
25
- * Defaults to `wrangler.jsonc` in the current working directory.
26
- */
27
- constructor(configPath?: string) {
28
- this.configPath = configPath ?? "wrangler.jsonc";
29
- }
30
-
31
- /**
32
- * Read and parse wrangler.jsonc from disk.
33
- *
34
- * Strips JSONC comments via jsonc-parser's native `parse()`.
35
- * Throws on file-not-found, invalid JSONC, or missing root object.
36
- *
37
- * @param configPath - Override the path set in the constructor.
38
- * @returns The parsed HooxConfig object.
39
- */
40
- async load(configPath?: string): Promise<HooxConfig> {
41
- const filePath = configPath ?? this.configPath;
42
- const file = Bun.file(filePath);
43
-
44
- if (!(await file.exists())) {
45
- throw new Error(
46
- `Config file not found: ${filePath}. Run 'hoox config init' to create one.`
47
- );
48
- }
49
-
50
- const content = await file.text();
51
- const errors: ParseError[] = [];
52
- const raw: unknown = parse(content, errors);
53
-
54
- if (errors.length > 0) {
55
- const messages = errors.map(
56
- (e) =>
57
- ` - ${printParseErrorCode(e.error)} at offset ${e.offset} (length ${e.length})`
58
- );
59
- throw new Error(`Invalid JSONC in ${filePath}:\n${messages.join("\n")}`);
60
- }
61
-
62
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
63
- throw new Error(`${filePath} must contain a JSON object at the root`);
64
- }
65
-
66
- this.config = raw as HooxConfig;
67
- return this.config;
68
- }
69
-
70
- /**
71
- * Get the configuration for a specific worker by name.
72
- *
73
- * @param name - The worker key as defined in wrangler.jsonc (e.g. "d1-worker").
74
- * @returns The worker config, or undefined if not found.
75
- */
76
- getWorker(name: string): WorkerConfig | undefined {
77
- const config = this.ensureLoaded();
78
- return config.workers[name];
79
- }
80
-
81
- /**
82
- * Get the global configuration section.
83
- */
84
- getGlobal(): GlobalConfig {
85
- const config = this.ensureLoaded();
86
- return config.global;
87
- }
88
-
89
- /**
90
- * List all worker names defined in wrangler.jsonc (enabled + disabled).
91
- */
92
- listWorkers(): string[] {
93
- const config = this.ensureLoaded();
94
- return Object.keys(config.workers);
95
- }
96
-
97
- /**
98
- * List only enabled worker names.
99
- */
100
- listEnabledWorkers(): string[] {
101
- const config = this.ensureLoaded();
102
- return Object.entries(config.workers)
103
- .filter(([, w]) => w.enabled)
104
- .map(([name]) => name);
105
- }
106
-
107
- /**
108
- * Validate the loaded configuration against required fields.
109
- *
110
- * Required fields:
111
- * - global.cloudflare_account_id
112
- * - Each worker must have a `path` property
113
- *
114
- * @returns An object with `valid` and an `errors` array.
115
- */
116
- validate(): { valid: boolean; errors: string[] } {
117
- const errors: string[] = [];
118
-
119
- if (!this.config) {
120
- return {
121
- valid: false,
122
- errors: ["Config not loaded. Call load() first."],
123
- };
124
- }
125
-
126
- // Global required fields
127
- if (!this.config.global?.cloudflare_account_id) {
128
- errors.push(
129
- "Required field missing: global.cloudflare_account_id — must be set in wrangler.jsonc"
130
- );
131
- }
132
-
133
- // Worker required fields
134
- if (!this.config.workers || Object.keys(this.config.workers).length === 0) {
135
- errors.push("No workers defined in wrangler.jsonc");
136
- } else {
137
- for (const [name, worker] of Object.entries(this.config.workers)) {
138
- if (!worker.path) {
139
- errors.push(`Worker "${name}" is missing required field: path`);
140
- }
141
- }
142
- }
143
-
144
- return { valid: errors.length === 0, errors };
145
- }
146
-
147
- /**
148
- * Get the preferred dev runtime from config ("native" or "docker").
149
- */
150
- getDevRuntime(): "native" | "docker" | null {
151
- return this.ensureLoaded().dev?.runtime ?? null;
152
- }
153
-
154
- /**
155
- * Persist the dev runtime preference to wrangler.jsonc.
156
- *
157
- * Reads the current file, updates the dev.runtime field, and writes it
158
- * back. Preserves JSONC comments where possible.
159
- */
160
- async setDevRuntime(runtime: "native" | "docker"): Promise<void> {
161
- const filePath = this.configPath;
162
- const file = Bun.file(filePath);
163
-
164
- if (!(await file.exists())) {
165
- throw new Error(`Config file not found: ${filePath}`);
166
- }
167
-
168
- const raw = await file.text();
169
- const errors: ParseError[] = [];
170
- const parsed = parse(raw, errors);
171
-
172
- if (
173
- errors.length > 0 ||
174
- parsed === null ||
175
- typeof parsed !== "object" ||
176
- Array.isArray(parsed)
177
- ) {
178
- throw new Error(`Invalid JSONC in ${filePath}`);
179
- }
180
-
181
- const config = parsed as Record<string, unknown>;
182
- if (!config.dev) {
183
- config.dev = {};
184
- }
185
- (config.dev as Record<string, unknown>).runtime = runtime;
186
-
187
- // Write back as regular JSON (jsonc-parser doesn't support round-trip comment preservation)
188
- await Bun.write(filePath, JSON.stringify(config, null, 2) + "\n");
189
- }
190
-
191
- /**
192
- * Guard: throws if config hasn't been loaded yet; returns the loaded config.
193
- */
194
- private ensureLoaded(): HooxConfig {
195
- if (!this.config) {
196
- throw new Error(
197
- "Config not loaded. Call load() before accessing configuration."
198
- );
199
- }
200
- return this.config;
201
- }
202
- }
@@ -1,9 +0,0 @@
1
- export { ConfigService } from "./config-service";
2
- export type {
3
- HooxConfig,
4
- GlobalConfig,
5
- WorkerConfig,
6
- WorkerSecret,
7
- WorkerVars,
8
- DevConfig,
9
- } from "./types";
@@ -1,66 +0,0 @@
1
- /**
2
- * ConfigService types — strongly-typed interfaces for wrangler.jsonc.
3
- *
4
- * The shape mirrors the actual wrangler.jsonc at the project root:
5
- * global: { cloudflare_api_token, cloudflare_account_id, cloudflare_secret_store_id, subdomain_prefix }
6
- * workers: { name: { enabled, path, vars?, secrets? } }
7
- */
8
-
9
- /**
10
- * A secret referenced by a worker. Each entry is the Cloudflare secret
11
- * name that the worker expects at runtime.
12
- */
13
- export type WorkerSecret = string;
14
-
15
- /**
16
- * Environment variables bound to a worker at deploy time.
17
- */
18
- export interface WorkerVars {
19
- [key: string]: string;
20
- }
21
-
22
- /**
23
- * Per-worker configuration as defined in wrangler.jsonc.
24
- */
25
- export interface WorkerConfig {
26
- /** Whether the worker is deployed and managed by the CLI. */
27
- enabled: boolean;
28
- /** Relative path from project root to the worker directory. */
29
- path: string;
30
- /** Plain-text environment variables (non-secret build-time vars). */
31
- vars?: WorkerVars;
32
- /** Cloudflare secret names this worker needs at runtime. */
33
- secrets?: WorkerSecret[];
34
- }
35
-
36
- /**
37
- * Development configuration for local runtime selection.
38
- */
39
- export interface DevConfig {
40
- /** Preferred runtime: "native" (wrangler dev) or "docker" (compose). */
41
- runtime?: "native" | "docker";
42
- }
43
-
44
- /**
45
- * Global configuration shared across all workers in the project.
46
- */
47
- export interface GlobalConfig {
48
- /** Cloudflare API token for authenticated operations. */
49
- cloudflare_api_token?: string;
50
- /** Cloudflare account ID (required — must be set before deploy). */
51
- cloudflare_account_id?: string;
52
- /** Secret Store ID for centralized secret management. */
53
- cloudflare_secret_store_id?: string;
54
- /** Subdomain prefix used for worker URL routing. */
55
- subdomain_prefix?: string;
56
- }
57
-
58
- /**
59
- * Top-level Hoox configuration — the root shape of wrangler.jsonc.
60
- */
61
- export interface HooxConfig {
62
- global: GlobalConfig;
63
- workers: Record<string, WorkerConfig>;
64
- /** Development settings (runtime preference, etc.). */
65
- dev?: DevConfig;
66
- }