@jango-blockchained/hoox-cli 0.5.1 → 0.5.2
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/package.json +1 -1
- package/src/commands/check/check-command.ts +8 -65
- package/src/commands/check/prerequisites-command.ts +7 -6
- package/src/commands/clone/clone-command.test.ts +8 -10
- package/src/commands/config/config-command.test.ts +4 -4
- package/src/commands/config/config-command.ts +10 -11
- package/src/commands/config/env-command.test.ts +2 -2
- package/src/commands/config/env-command.ts +40 -43
- package/src/commands/config/kv-command.ts +60 -60
- package/src/commands/dashboard/dashboard-command.ts +32 -30
- package/src/commands/db/db-command.ts +79 -80
- package/src/commands/deploy/deploy-command.test.ts +54 -34
- package/src/commands/deploy/deploy-command.ts +3 -84
- package/src/commands/dev/dev-command.test.ts +84 -62
- package/src/commands/disclaimer/disclaimer-command.ts +21 -0
- package/src/commands/disclaimer/index.ts +1 -0
- package/src/commands/init/init-command.test.ts +69 -91
- package/src/commands/init/init-command.ts +19 -15
- package/src/commands/logs/logs-command.test.ts +0 -1
- package/src/commands/monitor/monitor-command.test.ts +37 -29
- package/src/commands/repair/repair-command.test.ts +60 -41
- package/src/commands/repair/repair-service.ts +26 -0
- package/src/commands/schema/index.ts +1 -0
- package/src/commands/schema/schema-command.ts +137 -0
- package/src/commands/test/test-command.test.ts +2 -3
- package/src/commands/update/update-command.ts +6 -65
- package/src/commands/waf/waf-command.ts +56 -59
- package/src/index.ts +5 -13
- package/src/services/config/config-service.ts +1 -6
- package/src/services/db/db-service.test.ts +13 -1
- package/src/services/env/env-service.test.ts +41 -18
- package/src/services/env/env-service.ts +45 -59
- package/src/services/kv/kv-sync-service.ts +14 -5
- package/src/services/schema/index.ts +1 -0
- package/src/services/schema/schema-service.ts +99 -0
- package/src/services/secrets/secrets-service.test.ts +42 -35
- package/src/services/secrets/types.ts +1 -1
- package/src/ui/menu.ts +1 -1
- package/src/utils/error-handler.ts +62 -0
- package/src/utils/errors.ts +1 -0
- package/src/utils/git.ts +134 -0
- package/src/utils/theme.ts +0 -2
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
} from "@jango-blockchained/hoox-shared";
|
|
19
19
|
import type { WorkersJsonConfig } from "@jango-blockchained/hoox-shared";
|
|
20
20
|
import { CloudflareService } from "../../services/cloudflare/index.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getFormatOptions,
|
|
23
|
+
formatSuccess,
|
|
24
|
+
formatError,
|
|
25
|
+
} from "../../utils/formatters.js";
|
|
22
26
|
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
23
27
|
import { theme } from "../../utils/theme.js";
|
|
24
28
|
import { CLIProvisioner } from "./cli-provisioner.js";
|
|
@@ -204,7 +208,7 @@ EXAMPLES:
|
|
|
204
208
|
.option("--resume", "Resume from saved wizard state")
|
|
205
209
|
.option("--accept-risk", "Skip the risk acknowledgment confirmation")
|
|
206
210
|
.action(async (options: InitOptions) => {
|
|
207
|
-
const globalOpts = program
|
|
211
|
+
const globalOpts = getFormatOptions(program);
|
|
208
212
|
const isNonInteractive = Boolean(options.token && options.account);
|
|
209
213
|
|
|
210
214
|
try {
|
|
@@ -213,7 +217,7 @@ EXAMPLES:
|
|
|
213
217
|
const message = err instanceof Error ? err.message : String(err);
|
|
214
218
|
p.log.error(message);
|
|
215
219
|
formatError(new CLIError(message, ExitCode.ERROR), globalOpts);
|
|
216
|
-
process.
|
|
220
|
+
process.exitCode = ExitCode.ERROR;
|
|
217
221
|
}
|
|
218
222
|
});
|
|
219
223
|
}
|
|
@@ -244,7 +248,7 @@ export async function runInitCommand(
|
|
|
244
248
|
const error = await validateApiToken(cf, token);
|
|
245
249
|
if (error) {
|
|
246
250
|
formatError(new CLIError(error, ExitCode.ERROR), globalOpts);
|
|
247
|
-
process.
|
|
251
|
+
process.exitCode = ExitCode.ERROR;
|
|
248
252
|
}
|
|
249
253
|
if (!globalOpts.quiet) {
|
|
250
254
|
formatSuccess("Cloudflare API token validated", globalOpts);
|
|
@@ -296,7 +300,7 @@ export async function runInitCommand(
|
|
|
296
300
|
});
|
|
297
301
|
if (p.isCancel(resumed)) {
|
|
298
302
|
p.cancel("Setup cancelled.");
|
|
299
|
-
process.
|
|
303
|
+
process.exitCode = 0;
|
|
300
304
|
}
|
|
301
305
|
if (!resumed) {
|
|
302
306
|
engine = null;
|
|
@@ -320,12 +324,12 @@ export async function runInitCommand(
|
|
|
320
324
|
|
|
321
325
|
if (p.isCancel(accepted)) {
|
|
322
326
|
p.cancel("Setup cancelled.");
|
|
323
|
-
process.
|
|
327
|
+
process.exitCode = 0;
|
|
324
328
|
}
|
|
325
329
|
|
|
326
330
|
if (!accepted) {
|
|
327
331
|
p.outro("Setup cancelled. See DISCLAIMER.md for full terms.");
|
|
328
|
-
process.
|
|
332
|
+
process.exitCode = 0;
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
// Step 0: Prerequisites check (always passes — CLI is running)
|
|
@@ -350,7 +354,7 @@ export async function runInitCommand(
|
|
|
350
354
|
|
|
351
355
|
if (p.isCancel(apiToken)) {
|
|
352
356
|
p.cancel("Setup cancelled.");
|
|
353
|
-
process.
|
|
357
|
+
process.exitCode = 0;
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
p.log.step("Validating Cloudflare API token...");
|
|
@@ -379,7 +383,7 @@ export async function runInitCommand(
|
|
|
379
383
|
});
|
|
380
384
|
if (p.isCancel(accountResult)) {
|
|
381
385
|
p.cancel("Setup cancelled.");
|
|
382
|
-
process.
|
|
386
|
+
process.exitCode = 0;
|
|
383
387
|
}
|
|
384
388
|
|
|
385
389
|
const secretStoreResult = await p.text({
|
|
@@ -389,7 +393,7 @@ export async function runInitCommand(
|
|
|
389
393
|
});
|
|
390
394
|
if (p.isCancel(secretStoreResult)) {
|
|
391
395
|
p.cancel("Setup cancelled.");
|
|
392
|
-
process.
|
|
396
|
+
process.exitCode = 0;
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
const prefixResult = await p.text({
|
|
@@ -403,7 +407,7 @@ export async function runInitCommand(
|
|
|
403
407
|
});
|
|
404
408
|
if (p.isCancel(prefixResult)) {
|
|
405
409
|
p.cancel("Setup cancelled.");
|
|
406
|
-
process.
|
|
410
|
+
process.exitCode = 0;
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
engine.execute({
|
|
@@ -440,7 +444,7 @@ export async function runInitCommand(
|
|
|
440
444
|
|
|
441
445
|
if (p.isCancel(presetChoice)) {
|
|
442
446
|
p.cancel("Setup cancelled.");
|
|
443
|
-
process.
|
|
447
|
+
process.exitCode = 0;
|
|
444
448
|
}
|
|
445
449
|
|
|
446
450
|
engine.execute({ preset: presetChoice as string });
|
|
@@ -471,7 +475,7 @@ export async function runInitCommand(
|
|
|
471
475
|
|
|
472
476
|
if (p.isCancel(shouldProvision)) {
|
|
473
477
|
p.cancel("Setup cancelled.");
|
|
474
|
-
process.
|
|
478
|
+
process.exitCode = 0;
|
|
475
479
|
}
|
|
476
480
|
|
|
477
481
|
if (shouldProvision) {
|
|
@@ -529,7 +533,7 @@ export async function runInitCommand(
|
|
|
529
533
|
const collected = await p.group(groupFields, {
|
|
530
534
|
onCancel: () => {
|
|
531
535
|
p.cancel("Setup cancelled.");
|
|
532
|
-
process.
|
|
536
|
+
process.exitCode = 0;
|
|
533
537
|
},
|
|
534
538
|
});
|
|
535
539
|
|
|
@@ -569,7 +573,7 @@ export async function runInitCommand(
|
|
|
569
573
|
|
|
570
574
|
if (p.isCancel(shouldDeploy)) {
|
|
571
575
|
p.cancel("Setup cancelled.");
|
|
572
|
-
process.
|
|
576
|
+
process.exitCode = 0;
|
|
573
577
|
}
|
|
574
578
|
|
|
575
579
|
if (shouldDeploy) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
2
|
* Unit tests for the monitor command.
|
|
4
3
|
*
|
|
@@ -35,16 +34,19 @@ beforeEach(() => {
|
|
|
35
34
|
process.exitCode = 0;
|
|
36
35
|
|
|
37
36
|
// Restore originals
|
|
38
|
-
(
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
(
|
|
38
|
+
MonitorService.prototype as unknown as Record<string, unknown>
|
|
39
|
+
).checkAllWorkerHealth = origCheckAll;
|
|
40
|
+
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
41
41
|
origResolveDbName;
|
|
42
|
-
(DbService.prototype as Record<string, unknown>).query = origQuery;
|
|
43
|
-
(DbService.prototype as Record<string, unknown>).export =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
(DbService.prototype as unknown as Record<string, unknown>).query = origQuery;
|
|
43
|
+
(DbService.prototype as unknown as Record<string, unknown>).export =
|
|
44
|
+
origExport;
|
|
45
|
+
(
|
|
46
|
+
KvSyncService.prototype as unknown as Record<string, unknown>
|
|
47
|
+
).resolveNamespaceId = origResolveNs;
|
|
48
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).get = origGet;
|
|
49
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).set = origSet;
|
|
48
50
|
|
|
49
51
|
// Fresh mocks
|
|
50
52
|
checkAllWorkerHealthMock = mock(async () => ({
|
|
@@ -68,30 +70,36 @@ beforeEach(() => {
|
|
|
68
70
|
setMock = mock(async (_nsId: string, _key: string, _value: string) => {});
|
|
69
71
|
|
|
70
72
|
// Install mocks on prototypes
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
(
|
|
74
|
+
MonitorService.prototype as unknown as Record<string, unknown>
|
|
75
|
+
).checkAllWorkerHealth = checkAllWorkerHealthMock;
|
|
76
|
+
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
74
77
|
resolveDbNameMock;
|
|
75
|
-
(DbService.prototype as Record<string, unknown>).query = queryMock;
|
|
76
|
-
(DbService.prototype as Record<string, unknown>).export =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
(DbService.prototype as unknown as Record<string, unknown>).query = queryMock;
|
|
79
|
+
(DbService.prototype as unknown as Record<string, unknown>).export =
|
|
80
|
+
exportMock;
|
|
81
|
+
(
|
|
82
|
+
KvSyncService.prototype as unknown as Record<string, unknown>
|
|
83
|
+
).resolveNamespaceId = resolveNamespaceIdMock;
|
|
84
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).get = getMock;
|
|
85
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).set = setMock;
|
|
81
86
|
});
|
|
82
87
|
|
|
83
88
|
afterEach(() => {
|
|
84
89
|
mock.restore();
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
(
|
|
91
|
+
MonitorService.prototype as unknown as Record<string, unknown>
|
|
92
|
+
).checkAllWorkerHealth = origCheckAll;
|
|
93
|
+
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
88
94
|
origResolveDbName;
|
|
89
|
-
(DbService.prototype as Record<string, unknown>).query = origQuery;
|
|
90
|
-
(DbService.prototype as Record<string, unknown>).export =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
(DbService.prototype as unknown as Record<string, unknown>).query = origQuery;
|
|
96
|
+
(DbService.prototype as unknown as Record<string, unknown>).export =
|
|
97
|
+
origExport;
|
|
98
|
+
(
|
|
99
|
+
KvSyncService.prototype as unknown as Record<string, unknown>
|
|
100
|
+
).resolveNamespaceId = origResolveNs;
|
|
101
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).get = origGet;
|
|
102
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).set = origSet;
|
|
95
103
|
});
|
|
96
104
|
|
|
97
105
|
async function importMonitorCommand(): Promise<{
|
|
@@ -185,7 +193,7 @@ describe("registerMonitorCommand", () => {
|
|
|
185
193
|
throw new Error("Connection failed");
|
|
186
194
|
});
|
|
187
195
|
(
|
|
188
|
-
MonitorService.prototype as Record<string, unknown>
|
|
196
|
+
MonitorService.prototype as unknown as Record<string, unknown>
|
|
189
197
|
).checkAllWorkerHealth = checkAllWorkerHealthMock;
|
|
190
198
|
|
|
191
199
|
const program = await createProgram();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
2
|
* Unit tests for the repair command.
|
|
4
3
|
*
|
|
@@ -12,7 +11,6 @@ import { RepairService } from "./repair-service.js";
|
|
|
12
11
|
import { CloudflareService } from "../../services/cloudflare/cloudflare-service.js";
|
|
13
12
|
import { DbService } from "../../services/db/db-service.js";
|
|
14
13
|
import { KvSyncService } from "../../services/kv/kv-sync-service.js";
|
|
15
|
-
import { SecretsService } from "../../services/secrets/secrets-service.js";
|
|
16
14
|
|
|
17
15
|
// Stubs
|
|
18
16
|
let runSystemCheckMock: ReturnType<typeof mock>;
|
|
@@ -32,31 +30,39 @@ const origExport = DbService.prototype.export;
|
|
|
32
30
|
const origReset = DbService.prototype.reset;
|
|
33
31
|
const origResolveNs = KvSyncService.prototype.resolveNamespaceId;
|
|
34
32
|
const origKvSet = KvSyncService.prototype.set;
|
|
35
|
-
const origGetWorker = CloudflareService.prototype.deploy; // placeholder
|
|
36
|
-
const origListWorkers = CloudflareService.prototype.deploy; // placeholder
|
|
37
33
|
|
|
38
34
|
beforeEach(() => {
|
|
39
35
|
mock.restore();
|
|
40
36
|
process.exitCode = 0;
|
|
41
37
|
|
|
42
38
|
// Restore originals
|
|
43
|
-
(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
(CloudflareService.prototype as Record<string, unknown>).
|
|
47
|
-
|
|
48
|
-
(CloudflareService.prototype as Record<string, unknown>).
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
(
|
|
40
|
+
RepairService.prototype as unknown as Record<string, unknown>
|
|
41
|
+
).runSystemCheck = origRunSystemCheck;
|
|
42
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
|
|
43
|
+
origDeploy;
|
|
44
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).d1List =
|
|
45
|
+
origD1List;
|
|
46
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).kvList =
|
|
47
|
+
origKvList;
|
|
48
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).r2List =
|
|
49
|
+
origR2List;
|
|
50
|
+
(
|
|
51
|
+
CloudflareService.prototype as unknown as Record<string, unknown>
|
|
52
|
+
).queueList = origQueueList;
|
|
53
|
+
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
52
54
|
origResolveDbName;
|
|
53
|
-
(DbService.prototype as Record<string, unknown>).apply = origApply;
|
|
54
|
-
(DbService.prototype as Record<string, unknown>).migrate =
|
|
55
|
-
|
|
56
|
-
(DbService.prototype as Record<string, unknown>).
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(
|
|
55
|
+
(DbService.prototype as unknown as Record<string, unknown>).apply = origApply;
|
|
56
|
+
(DbService.prototype as unknown as Record<string, unknown>).migrate =
|
|
57
|
+
origMigrate;
|
|
58
|
+
(DbService.prototype as unknown as Record<string, unknown>).export =
|
|
59
|
+
origExport;
|
|
60
|
+
(DbService.prototype as unknown as Record<string, unknown>).reset = origReset;
|
|
61
|
+
(
|
|
62
|
+
KvSyncService.prototype as unknown as Record<string, unknown>
|
|
63
|
+
).resolveNamespaceId = origResolveNs;
|
|
64
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).set =
|
|
65
|
+
origKvSet;
|
|
60
66
|
|
|
61
67
|
// Fresh mocks
|
|
62
68
|
runSystemCheckMock = mock(async () => ({
|
|
@@ -77,30 +83,42 @@ beforeEach(() => {
|
|
|
77
83
|
data: { url: "https://test-worker.cryptolinx.workers.dev" },
|
|
78
84
|
}));
|
|
79
85
|
|
|
80
|
-
(
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
(
|
|
87
|
+
RepairService.prototype as unknown as Record<string, unknown>
|
|
88
|
+
).runSystemCheck = runSystemCheckMock;
|
|
89
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
|
|
90
|
+
deployMock;
|
|
83
91
|
});
|
|
84
92
|
|
|
85
93
|
afterEach(() => {
|
|
86
94
|
mock.restore();
|
|
87
|
-
(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(CloudflareService.prototype as Record<string, unknown>).
|
|
91
|
-
|
|
92
|
-
(CloudflareService.prototype as Record<string, unknown>).
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
(
|
|
96
|
+
RepairService.prototype as unknown as Record<string, unknown>
|
|
97
|
+
).runSystemCheck = origRunSystemCheck;
|
|
98
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).deploy =
|
|
99
|
+
origDeploy;
|
|
100
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).d1List =
|
|
101
|
+
origD1List;
|
|
102
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).kvList =
|
|
103
|
+
origKvList;
|
|
104
|
+
(CloudflareService.prototype as unknown as Record<string, unknown>).r2List =
|
|
105
|
+
origR2List;
|
|
106
|
+
(
|
|
107
|
+
CloudflareService.prototype as unknown as Record<string, unknown>
|
|
108
|
+
).queueList = origQueueList;
|
|
109
|
+
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
96
110
|
origResolveDbName;
|
|
97
|
-
(DbService.prototype as Record<string, unknown>).apply = origApply;
|
|
98
|
-
(DbService.prototype as Record<string, unknown>).migrate =
|
|
99
|
-
|
|
100
|
-
(DbService.prototype as Record<string, unknown>).
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
(
|
|
111
|
+
(DbService.prototype as unknown as Record<string, unknown>).apply = origApply;
|
|
112
|
+
(DbService.prototype as unknown as Record<string, unknown>).migrate =
|
|
113
|
+
origMigrate;
|
|
114
|
+
(DbService.prototype as unknown as Record<string, unknown>).export =
|
|
115
|
+
origExport;
|
|
116
|
+
(DbService.prototype as unknown as Record<string, unknown>).reset = origReset;
|
|
117
|
+
(
|
|
118
|
+
KvSyncService.prototype as unknown as Record<string, unknown>
|
|
119
|
+
).resolveNamespaceId = origResolveNs;
|
|
120
|
+
(KvSyncService.prototype as unknown as Record<string, unknown>).set =
|
|
121
|
+
origKvSet;
|
|
104
122
|
});
|
|
105
123
|
|
|
106
124
|
async function importRepairCommand(): Promise<{
|
|
@@ -194,8 +212,9 @@ describe("registerRepairCommand", () => {
|
|
|
194
212
|
runSystemCheckMock = mock(async () => {
|
|
195
213
|
throw new Error("Check failed");
|
|
196
214
|
});
|
|
197
|
-
(
|
|
198
|
-
|
|
215
|
+
(
|
|
216
|
+
RepairService.prototype as unknown as Record<string, unknown>
|
|
217
|
+
).runSystemCheck = runSystemCheckMock;
|
|
199
218
|
|
|
200
219
|
const program = await createProgram();
|
|
201
220
|
await program.parseAsync(["repair", "check"], { from: "user" });
|
|
@@ -126,6 +126,32 @@ export class RepairService {
|
|
|
126
126
|
steps.push({ step: "Secrets", success: false, error: String(err) });
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Step 6: Worker config schema validation
|
|
130
|
+
try {
|
|
131
|
+
const { SchemaService } =
|
|
132
|
+
await import("../../services/schema/schema-service.js");
|
|
133
|
+
const svc = new SchemaService();
|
|
134
|
+
const results = svc.validateAll();
|
|
135
|
+
const totalErrors = results.reduce(
|
|
136
|
+
(sum, r) => sum + r.errors.filter((e) => e.severity === "error").length,
|
|
137
|
+
0
|
|
138
|
+
);
|
|
139
|
+
steps.push({
|
|
140
|
+
step: "Worker config schema",
|
|
141
|
+
success: totalErrors === 0,
|
|
142
|
+
message:
|
|
143
|
+
totalErrors === 0
|
|
144
|
+
? "All workers match manifest"
|
|
145
|
+
: `${totalErrors} schema issue(s) found`,
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
steps.push({
|
|
149
|
+
step: "Worker config schema",
|
|
150
|
+
success: false,
|
|
151
|
+
error: String(err),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
const passed = steps.filter((s) => s.success).length;
|
|
130
156
|
const failed = steps.filter((s) => !s.success).length;
|
|
131
157
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerSchemaCommand } from "./schema-command.js";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { SchemaService } from "../../services/schema/schema-service.js";
|
|
4
|
+
import {
|
|
5
|
+
formatSuccess,
|
|
6
|
+
formatError,
|
|
7
|
+
formatKeyValue,
|
|
8
|
+
formatTable,
|
|
9
|
+
getFormatOptions,
|
|
10
|
+
} from "../../utils/formatters.js";
|
|
11
|
+
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
12
|
+
import { withErrorHandling } from "../../utils/error-handler.js";
|
|
13
|
+
import {
|
|
14
|
+
generateWranglerJsonc,
|
|
15
|
+
generateDevVars,
|
|
16
|
+
} from "@jango-blockchained/hoox-shared";
|
|
17
|
+
|
|
18
|
+
export function registerSchemaCommand(program: Command): void {
|
|
19
|
+
const schemaCmd = program
|
|
20
|
+
.command("schema")
|
|
21
|
+
.summary("Validate and manage worker configuration")
|
|
22
|
+
.description(
|
|
23
|
+
"Validate, generate, and repair worker wrangler.jsonc against canonical manifests."
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
schemaCmd
|
|
27
|
+
.command("validate [worker]")
|
|
28
|
+
.description(
|
|
29
|
+
"Validate worker(s) against manifest (omit worker to validate all)"
|
|
30
|
+
)
|
|
31
|
+
.action(
|
|
32
|
+
withErrorHandling(async (worker?: string) => {
|
|
33
|
+
const fmt = getFormatOptions(schemaCmd);
|
|
34
|
+
const svc = new SchemaService();
|
|
35
|
+
const results = worker
|
|
36
|
+
? [svc.validateWorker(worker)]
|
|
37
|
+
: svc.validateAll();
|
|
38
|
+
let totalErrors = 0;
|
|
39
|
+
for (const r of results) {
|
|
40
|
+
if (r.passed) {
|
|
41
|
+
formatSuccess(`${r.worker}: ✅ passed`, fmt);
|
|
42
|
+
} else {
|
|
43
|
+
formatError(
|
|
44
|
+
new CLIError(
|
|
45
|
+
`${r.worker}: ❌ failed (${r.errors.length} issues)`,
|
|
46
|
+
ExitCode.ERROR
|
|
47
|
+
),
|
|
48
|
+
fmt
|
|
49
|
+
);
|
|
50
|
+
for (const e of r.errors.filter((e) => e.severity === "error")) {
|
|
51
|
+
process.stderr.write(` ✗ ${e.message}\n`);
|
|
52
|
+
}
|
|
53
|
+
totalErrors += r.errors.filter(
|
|
54
|
+
(e) => e.severity === "error"
|
|
55
|
+
).length;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (totalErrors > 0) process.exitCode = ExitCode.ERROR;
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
schemaCmd
|
|
63
|
+
.command("list")
|
|
64
|
+
.description("List all workers with their binding counts")
|
|
65
|
+
.action(
|
|
66
|
+
withErrorHandling(async () => {
|
|
67
|
+
const fmt = getFormatOptions(schemaCmd);
|
|
68
|
+
const svc = new SchemaService();
|
|
69
|
+
const rows: Record<string, string>[] = [];
|
|
70
|
+
for (const name of svc.getWorkerNames()) {
|
|
71
|
+
const m = svc.getManifest(name)!;
|
|
72
|
+
const secretCount = Object.values(m.vars).filter(
|
|
73
|
+
(v) => v.type === "secret"
|
|
74
|
+
).length;
|
|
75
|
+
const svcCount = m.services.length;
|
|
76
|
+
const infraCount = Object.keys(m.infrastructure).length;
|
|
77
|
+
const middleware = m.middleware.length
|
|
78
|
+
? m.middleware.join(", ")
|
|
79
|
+
: "\u2014";
|
|
80
|
+
rows.push({
|
|
81
|
+
worker: name,
|
|
82
|
+
secrets: String(secretCount),
|
|
83
|
+
services: String(svcCount),
|
|
84
|
+
infra: String(infraCount),
|
|
85
|
+
middleware,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
formatTable(rows, fmt);
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
schemaCmd
|
|
93
|
+
.command("generate <worker>")
|
|
94
|
+
.description("Generate wrangler.jsonc and .dev.vars from manifest")
|
|
95
|
+
.option("--dry-run", "Print generated content without writing")
|
|
96
|
+
.action(
|
|
97
|
+
withErrorHandling(async (worker: string, opts: { dryRun?: boolean }) => {
|
|
98
|
+
const fmt = getFormatOptions(schemaCmd);
|
|
99
|
+
const svc = new SchemaService();
|
|
100
|
+
const manifest = svc.getManifest(worker);
|
|
101
|
+
if (!manifest) {
|
|
102
|
+
formatError(
|
|
103
|
+
new CLIError(`Unknown worker "${worker}"`, ExitCode.INVALID_USAGE),
|
|
104
|
+
fmt
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const wranglerContent = generateWranglerJsonc(manifest);
|
|
110
|
+
const devVarsContent = generateDevVars(manifest);
|
|
111
|
+
|
|
112
|
+
if (opts.dryRun) {
|
|
113
|
+
process.stdout.write(`--- wrangler.jsonc (${worker}) ---\n`);
|
|
114
|
+
process.stdout.write(wranglerContent + "\n");
|
|
115
|
+
process.stdout.write(`--- .dev.vars (${worker}) ---\n`);
|
|
116
|
+
process.stdout.write(devVarsContent + "\n");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const workersDir = resolve(process.cwd(), "workers", worker);
|
|
121
|
+
const wranglerPath = resolve(workersDir, "wrangler.jsonc");
|
|
122
|
+
const devVarsPath = resolve(workersDir, ".dev.vars");
|
|
123
|
+
|
|
124
|
+
await Bun.write(wranglerPath, wranglerContent);
|
|
125
|
+
await Bun.write(devVarsPath, devVarsContent);
|
|
126
|
+
|
|
127
|
+
formatKeyValue(
|
|
128
|
+
{
|
|
129
|
+
"wrangler.jsonc": wranglerPath,
|
|
130
|
+
".dev.vars": devVarsPath,
|
|
131
|
+
},
|
|
132
|
+
{ ...fmt, json: false }
|
|
133
|
+
);
|
|
134
|
+
formatSuccess(`Generated files for ${worker}`, fmt);
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
|
|
3
2
|
import { Command } from "commander";
|
|
4
3
|
import { registerTestCommand, runStep, printSummary } from "./test-command.js";
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
4
|
+
import type { TestSummary } from "./test-command.js";
|
|
5
|
+
import { ExitCode } from "../../utils/errors.js";
|
|
7
6
|
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
9
8
|
// Helpers
|
|
@@ -5,71 +5,12 @@ import { UpdateService } from "../../services/update/index.js";
|
|
|
5
5
|
import { formatError, getFormatOptions } from "../../utils/formatters.js";
|
|
6
6
|
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
7
7
|
import { theme } from "../../utils/theme.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
15
|
-
const exitCode = await proc.exited;
|
|
16
|
-
const stdout = await new Response(proc.stdout).text();
|
|
17
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18
|
-
|
|
19
|
-
if (exitCode !== 0) {
|
|
20
|
-
throw new Error(stderr.trim() || `git pull failed (exit ${exitCode})`);
|
|
21
|
-
}
|
|
22
|
-
return stdout.trim();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function gitSubmoduleUpdate(
|
|
26
|
-
cwd: string,
|
|
27
|
-
submodulePath: string
|
|
28
|
-
): Promise<string> {
|
|
29
|
-
const proc = Bun.spawn(
|
|
30
|
-
["git", "submodule", "update", "--remote", "--init", "--", submodulePath],
|
|
31
|
-
{
|
|
32
|
-
cwd,
|
|
33
|
-
stdout: "pipe",
|
|
34
|
-
stderr: "pipe",
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
const exitCode = await proc.exited;
|
|
38
|
-
const stdout = await new Response(proc.stdout).text();
|
|
39
|
-
const stderr = await new Response(proc.stderr).text();
|
|
40
|
-
|
|
41
|
-
if (exitCode !== 0) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
stderr.trim() || `git submodule update failed (exit ${exitCode})`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return stdout.trim();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function isGitRepo(cwd: string): Promise<boolean> {
|
|
50
|
-
const proc = Bun.spawn(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
51
|
-
cwd,
|
|
52
|
-
stdout: "pipe",
|
|
53
|
-
stderr: "pipe",
|
|
54
|
-
});
|
|
55
|
-
const exitCode = await proc.exited;
|
|
56
|
-
return exitCode === 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function isSubmodule(
|
|
60
|
-
cwd: string,
|
|
61
|
-
submodulePath: string
|
|
62
|
-
): Promise<boolean> {
|
|
63
|
-
const proc = Bun.spawn(["git", "submodule", "status", "--", submodulePath], {
|
|
64
|
-
cwd,
|
|
65
|
-
stdout: "pipe",
|
|
66
|
-
stderr: "pipe",
|
|
67
|
-
});
|
|
68
|
-
const exitCode = await proc.exited;
|
|
69
|
-
if (exitCode !== 0) return false;
|
|
70
|
-
const out = (await new Response(proc.stdout).text()).trim();
|
|
71
|
-
return out.length > 0;
|
|
72
|
-
}
|
|
8
|
+
import {
|
|
9
|
+
gitPull,
|
|
10
|
+
gitSubmoduleUpdate,
|
|
11
|
+
isGitRepo,
|
|
12
|
+
isSubmodule,
|
|
13
|
+
} from "../../utils/git.js";
|
|
73
14
|
|
|
74
15
|
export function registerUpdateCommand(program: Command): void {
|
|
75
16
|
program
|