@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,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,207 +0,0 @@
1
- import { parse, printParseErrorCode } from "jsonc-parser";
2
- import type { ParseError } from "jsonc-parser";
3
- import type {
4
- HooxConfig,
5
- WorkerConfig,
6
- GlobalConfig,
7
- DevConfig,
8
- } from "./types";
9
-
10
- /**
11
- * Loads, parses, and validates the central wrangler.jsonc configuration.
12
- *
13
- * Uses jsonc-parser for fault-tolerant JSONC parsing with native comment
14
- * support (both line and block comments). Provides typed accessors for the
15
- * global config and per-worker entries.
16
- *
17
- * @example
18
- * ```ts
19
- * const config = new ConfigService();
20
- * await config.load();
21
- * console.log(config.listEnabledWorkers()); // ["d1-worker", "hoox", ...]
22
- * ```
23
- */
24
- export class ConfigService {
25
- private config: HooxConfig | null = null;
26
- private configPath: string;
27
-
28
- /**
29
- * @param configPath - Absolute or relative path to wrangler.jsonc.
30
- * Defaults to `wrangler.jsonc` in the current working directory.
31
- */
32
- constructor(configPath?: string) {
33
- this.configPath = configPath ?? "wrangler.jsonc";
34
- }
35
-
36
- /**
37
- * Read and parse wrangler.jsonc from disk.
38
- *
39
- * Strips JSONC comments via jsonc-parser's native `parse()`.
40
- * Throws on file-not-found, invalid JSONC, or missing root object.
41
- *
42
- * @param configPath - Override the path set in the constructor.
43
- * @returns The parsed HooxConfig object.
44
- */
45
- async load(configPath?: string): Promise<HooxConfig> {
46
- const filePath = configPath ?? this.configPath;
47
- const file = Bun.file(filePath);
48
-
49
- if (!(await file.exists())) {
50
- throw new Error(
51
- `Config file not found: ${filePath}. Run 'hoox config init' to create one.`
52
- );
53
- }
54
-
55
- const content = await file.text();
56
- const errors: ParseError[] = [];
57
- const raw: unknown = parse(content, errors);
58
-
59
- if (errors.length > 0) {
60
- const messages = errors.map(
61
- (e) =>
62
- ` - ${printParseErrorCode(e.error)} at offset ${e.offset} (length ${e.length})`
63
- );
64
- throw new Error(`Invalid JSONC in ${filePath}:\n${messages.join("\n")}`);
65
- }
66
-
67
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
68
- throw new Error(`${filePath} must contain a JSON object at the root`);
69
- }
70
-
71
- this.config = raw as HooxConfig;
72
- return this.config;
73
- }
74
-
75
- /**
76
- * Get the configuration for a specific worker by name.
77
- *
78
- * @param name - The worker key as defined in wrangler.jsonc (e.g. "d1-worker").
79
- * @returns The worker config, or undefined if not found.
80
- */
81
- getWorker(name: string): WorkerConfig | undefined {
82
- const config = this.ensureLoaded();
83
- return config.workers[name];
84
- }
85
-
86
- /**
87
- * Get the global configuration section.
88
- */
89
- getGlobal(): GlobalConfig {
90
- const config = this.ensureLoaded();
91
- return config.global;
92
- }
93
-
94
- /**
95
- * List all worker names defined in wrangler.jsonc (enabled + disabled).
96
- */
97
- listWorkers(): string[] {
98
- const config = this.ensureLoaded();
99
- return Object.keys(config.workers);
100
- }
101
-
102
- /**
103
- * List only enabled worker names.
104
- */
105
- listEnabledWorkers(): string[] {
106
- const config = this.ensureLoaded();
107
- return Object.entries(config.workers)
108
- .filter(([, w]) => w.enabled)
109
- .map(([name]) => name);
110
- }
111
-
112
- /**
113
- * Validate the loaded configuration against required fields.
114
- *
115
- * Required fields:
116
- * - global.cloudflare_account_id
117
- * - Each worker must have a `path` property
118
- *
119
- * @returns An object with `valid` and an `errors` array.
120
- */
121
- validate(): { valid: boolean; errors: string[] } {
122
- const errors: string[] = [];
123
-
124
- if (!this.config) {
125
- return {
126
- valid: false,
127
- errors: ["Config not loaded. Call load() first."],
128
- };
129
- }
130
-
131
- // Global required fields
132
- if (!this.config.global?.cloudflare_account_id) {
133
- errors.push(
134
- "Required field missing: global.cloudflare_account_id — must be set in wrangler.jsonc"
135
- );
136
- }
137
-
138
- // Worker required fields
139
- if (!this.config.workers || Object.keys(this.config.workers).length === 0) {
140
- errors.push("No workers defined in wrangler.jsonc");
141
- } else {
142
- for (const [name, worker] of Object.entries(this.config.workers)) {
143
- if (!worker.path) {
144
- errors.push(`Worker "${name}" is missing required field: path`);
145
- }
146
- }
147
- }
148
-
149
- return { valid: errors.length === 0, errors };
150
- }
151
-
152
- /**
153
- * Get the preferred dev runtime from config ("native" or "docker").
154
- */
155
- getDevRuntime(): "native" | "docker" | null {
156
- return this.ensureLoaded().dev?.runtime ?? null;
157
- }
158
-
159
- /**
160
- * Persist the dev runtime preference to wrangler.jsonc.
161
- *
162
- * Reads the current file, updates the dev.runtime field, and writes it
163
- * back. Preserves JSONC comments where possible.
164
- */
165
- async setDevRuntime(runtime: "native" | "docker"): Promise<void> {
166
- const filePath = this.configPath;
167
- const file = Bun.file(filePath);
168
-
169
- if (!(await file.exists())) {
170
- throw new Error(`Config file not found: ${filePath}`);
171
- }
172
-
173
- const raw = await file.text();
174
- const errors: ParseError[] = [];
175
- const parsed = parse(raw, errors);
176
-
177
- if (
178
- errors.length > 0 ||
179
- parsed === null ||
180
- typeof parsed !== "object" ||
181
- Array.isArray(parsed)
182
- ) {
183
- throw new Error(`Invalid JSONC in ${filePath}`);
184
- }
185
-
186
- const config = parsed as Record<string, unknown>;
187
- if (!config.dev) {
188
- config.dev = {};
189
- }
190
- (config.dev as Record<string, unknown>).runtime = runtime;
191
-
192
- // Write back as regular JSON (jsonc-parser doesn't support round-trip comment preservation)
193
- await Bun.write(filePath, JSON.stringify(config, null, 2) + "\n");
194
- }
195
-
196
- /**
197
- * Guard: throws if config hasn't been loaded yet; returns the loaded config.
198
- */
199
- private ensureLoaded(): HooxConfig {
200
- if (!this.config) {
201
- throw new Error(
202
- "Config not loaded. Call load() before accessing configuration."
203
- );
204
- }
205
- return this.config;
206
- }
207
- }
@@ -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
- }