@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.
- package/bin/hoox.js +3 -4
- package/dist/index.js +32545 -0
- package/package.json +10 -15
- package/src/commands/check/check-command.test.ts +0 -476
- package/src/commands/check/check-command.ts +0 -1087
- package/src/commands/check/index.ts +0 -10
- package/src/commands/check/prerequisites-command.test.ts +0 -19
- package/src/commands/check/prerequisites-command.ts +0 -97
- package/src/commands/check/types.ts +0 -103
- package/src/commands/clone/clone-command.test.ts +0 -440
- package/src/commands/clone/clone-command.ts +0 -431
- package/src/commands/clone/index.ts +0 -1
- package/src/commands/config/config-command.test.ts +0 -583
- package/src/commands/config/config-command.ts +0 -896
- package/src/commands/config/env-command.test.ts +0 -43
- package/src/commands/config/env-command.ts +0 -292
- package/src/commands/config/index.ts +0 -3
- package/src/commands/config/kv-command.test.ts +0 -14
- package/src/commands/config/kv-command.ts +0 -290
- package/src/commands/dashboard/dashboard-command.test.ts +0 -47
- package/src/commands/dashboard/dashboard-command.ts +0 -130
- package/src/commands/dashboard/index.ts +0 -1
- package/src/commands/db/db-command.test.ts +0 -21
- package/src/commands/db/db-command.ts +0 -311
- package/src/commands/db/index.ts +0 -1
- package/src/commands/deploy/deploy-command.test.ts +0 -324
- package/src/commands/deploy/deploy-command.ts +0 -1054
- package/src/commands/deploy/index.ts +0 -2
- package/src/commands/deploy/telegram-service.ts +0 -76
- package/src/commands/deploy/types.ts +0 -34
- package/src/commands/dev/dev-command.test.ts +0 -405
- package/src/commands/dev/dev-command.ts +0 -398
- package/src/commands/dev/index.ts +0 -1
- package/src/commands/disclaimer/disclaimer-command.ts +0 -21
- package/src/commands/disclaimer/index.ts +0 -1
- package/src/commands/infra/index.ts +0 -5
- package/src/commands/infra/infra-command.test.ts +0 -719
- package/src/commands/infra/infra-command.ts +0 -974
- package/src/commands/infra/types.ts +0 -23
- package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
- package/src/commands/init/cli-provisioner.ts +0 -71
- package/src/commands/init/index.ts +0 -1
- package/src/commands/init/init-command.test.ts +0 -854
- package/src/commands/init/init-command.ts +0 -597
- package/src/commands/init/types.ts +0 -23
- package/src/commands/logs/index.ts +0 -1
- package/src/commands/logs/logs-command.test.ts +0 -293
- package/src/commands/logs/logs-command.ts +0 -452
- package/src/commands/monitor/index.ts +0 -2
- package/src/commands/monitor/monitor-command.test.ts +0 -271
- package/src/commands/monitor/monitor-command.ts +0 -269
- package/src/commands/monitor/monitor-service.ts +0 -65
- package/src/commands/monitor/types.ts +0 -13
- package/src/commands/repair/index.ts +0 -2
- package/src/commands/repair/repair-command.test.ts +0 -243
- package/src/commands/repair/repair-command.ts +0 -220
- package/src/commands/repair/repair-service.ts +0 -164
- package/src/commands/repair/types.ts +0 -13
- package/src/commands/schema/index.ts +0 -1
- package/src/commands/schema/schema-command.ts +0 -137
- package/src/commands/test/index.ts +0 -2
- package/src/commands/test/test-command.test.ts +0 -318
- package/src/commands/test/test-command.ts +0 -406
- package/src/commands/tui/index.ts +0 -1
- package/src/commands/tui/tui-command.ts +0 -87
- package/src/commands/update/index.ts +0 -1
- package/src/commands/update/update-command.ts +0 -109
- package/src/commands/waf/index.ts +0 -2
- package/src/commands/waf/types.ts +0 -48
- package/src/commands/waf/waf-command.test.ts +0 -506
- package/src/commands/waf/waf-command.ts +0 -545
- package/src/index.ts +0 -261
- package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
- package/src/services/cloudflare/cloudflare-service.ts +0 -433
- package/src/services/cloudflare/index.ts +0 -2
- package/src/services/cloudflare/types.ts +0 -30
- package/src/services/config/config-service.test.ts +0 -395
- package/src/services/config/config-service.ts +0 -202
- package/src/services/config/index.ts +0 -9
- package/src/services/config/types.ts +0 -66
- package/src/services/db/db-service.test.ts +0 -63
- package/src/services/db/db-service.ts +0 -131
- package/src/services/db/index.ts +0 -1
- package/src/services/docker/docker-service.ts +0 -155
- package/src/services/docker/index.ts +0 -1
- package/src/services/env/env-service.test.ts +0 -259
- package/src/services/env/env-service.ts +0 -372
- package/src/services/env/index.ts +0 -1
- package/src/services/kv/index.ts +0 -1
- package/src/services/kv/kv-sync-service.test.ts +0 -38
- package/src/services/kv/kv-sync-service.ts +0 -243
- package/src/services/prerequisites/index.ts +0 -1
- package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
- package/src/services/prerequisites/prerequisites-service.ts +0 -377
- package/src/services/prerequisites/types.ts +0 -48
- package/src/services/schema/index.ts +0 -1
- package/src/services/schema/schema-service.ts +0 -99
- package/src/services/secrets/index.ts +0 -11
- package/src/services/secrets/secrets-service.test.ts +0 -502
- package/src/services/secrets/secrets-service.ts +0 -287
- package/src/services/secrets/types.ts +0 -50
- package/src/services/update/index.ts +0 -2
- package/src/services/update/update-service.test.ts +0 -76
- package/src/services/update/update-service.ts +0 -193
- package/src/ui/banner.ts +0 -141
- package/src/ui/index.ts +0 -8
- package/src/ui/menu.ts +0 -490
- package/src/utils/error-handler.ts +0 -62
- package/src/utils/errors.test.ts +0 -69
- package/src/utils/errors.ts +0 -24
- package/src/utils/formatters.test.ts +0 -180
- package/src/utils/formatters.ts +0 -269
- package/src/utils/git.ts +0 -134
- 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,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
|
-
}
|