@jango-blockchained/hoox-cli 0.3.4 → 0.3.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/README.md +2 -0
- package/package.json +5 -3
- package/src/commands/check/check-command.test.ts +28 -20
- package/src/commands/check/prerequisites-command.ts +9 -5
- package/src/commands/clone/clone-command.ts +1 -10
- package/src/commands/config/config-command.ts +7 -11
- package/src/commands/config/env-command.ts +16 -35
- package/src/commands/config/kv-command.ts +33 -72
- package/src/commands/db/db-command.ts +23 -25
- package/src/commands/deploy/deploy-command.ts +133 -52
- package/src/commands/deploy/telegram-service.ts +21 -6
- package/src/commands/dev/dev-command.ts +7 -16
- package/src/commands/infra/infra-command.test.ts +2 -2
- package/src/commands/infra/infra-command.ts +45 -11
- package/src/commands/init/init-command.test.ts +2 -2
- package/src/commands/monitor/monitor-command.test.ts +44 -16
- package/src/commands/monitor/monitor-command.ts +45 -21
- package/src/commands/monitor/monitor-service.ts +19 -4
- package/src/commands/repair/repair-command.test.ts +35 -15
- package/src/commands/repair/repair-command.ts +39 -18
- package/src/commands/repair/repair-service.ts +48 -12
- package/src/commands/test/test-command.ts +4 -10
- package/src/commands/tui/index.ts +1 -0
- package/src/commands/tui/tui-command.ts +87 -0
- package/src/commands/update/index.ts +1 -0
- package/src/commands/update/update-command.ts +51 -0
- package/src/commands/waf/waf-command.test.ts +1 -1
- package/src/commands/waf/waf-command.ts +11 -11
- package/src/index.ts +52 -1
- package/src/services/cloudflare/cloudflare-service.test.ts +12 -8
- package/src/services/cloudflare/cloudflare-service.ts +8 -10
- package/src/services/cloudflare/types.ts +6 -5
- package/src/services/db/db-service.ts +8 -17
- package/src/services/env/env-service.test.ts +41 -15
- package/src/services/env/env-service.ts +276 -46
- package/src/services/kv/kv-sync-service.ts +117 -34
- package/src/services/prerequisites/prerequisites-service.ts +137 -29
- package/src/services/secrets/index.ts +0 -1
- package/src/services/secrets/secrets-service.test.ts +34 -25
- package/src/services/secrets/secrets-service.ts +10 -16
- package/src/services/secrets/types.ts +4 -11
- package/src/services/update/index.ts +2 -0
- package/src/services/update/update-service.test.ts +76 -0
- package/src/services/update/update-service.ts +193 -0
- package/src/ui/banner.ts +5 -5
- package/src/ui/menu.ts +6 -1
- package/src/utils/formatters.ts +21 -4
- package/src/utils/theme.ts +1 -1
|
@@ -52,7 +52,7 @@ function displayListResult(
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const data = result.
|
|
55
|
+
const data = result.value;
|
|
56
56
|
|
|
57
57
|
// Try JSON parse for structured output
|
|
58
58
|
try {
|
|
@@ -486,33 +486,61 @@ async function doProvision(
|
|
|
486
486
|
// Vectorize handlers
|
|
487
487
|
// ---------------------------------------------------------------------------
|
|
488
488
|
|
|
489
|
-
async function doVectorizeList(
|
|
489
|
+
async function doVectorizeList(
|
|
490
|
+
opts: InfraOptions,
|
|
491
|
+
cf?: CloudflareService
|
|
492
|
+
): Promise<void> {
|
|
490
493
|
const cloudflare = cf ?? new CloudflareService();
|
|
491
494
|
const result = await cloudflare.vectorizeList();
|
|
492
495
|
displayListResult(result, opts, ["name", "id", "dimensions", "metric"]);
|
|
493
496
|
}
|
|
494
497
|
|
|
495
|
-
async function doVectorizeCreate(
|
|
498
|
+
async function doVectorizeCreate(
|
|
499
|
+
name: string,
|
|
500
|
+
opts: InfraOptions,
|
|
501
|
+
cf?: CloudflareService
|
|
502
|
+
): Promise<void> {
|
|
496
503
|
const cloudflare = cf ?? new CloudflareService();
|
|
497
|
-
await handleCreate(
|
|
504
|
+
await handleCreate(
|
|
505
|
+
name,
|
|
506
|
+
"Vectorize index",
|
|
507
|
+
(n) => cloudflare.vectorizeCreate(n),
|
|
508
|
+
opts
|
|
509
|
+
);
|
|
498
510
|
}
|
|
499
511
|
|
|
500
|
-
async function doVectorizeDelete(
|
|
512
|
+
async function doVectorizeDelete(
|
|
513
|
+
name: string,
|
|
514
|
+
opts: InfraOptions,
|
|
515
|
+
cf?: CloudflareService
|
|
516
|
+
): Promise<void> {
|
|
501
517
|
const cloudflare = cf ?? new CloudflareService();
|
|
502
|
-
await handleDelete(
|
|
518
|
+
await handleDelete(
|
|
519
|
+
name,
|
|
520
|
+
"Vectorize index",
|
|
521
|
+
(n) => cloudflare.vectorizeDelete(n),
|
|
522
|
+
opts
|
|
523
|
+
);
|
|
503
524
|
}
|
|
504
525
|
|
|
505
526
|
// ---------------------------------------------------------------------------
|
|
506
527
|
// Analytics handlers
|
|
507
528
|
// ---------------------------------------------------------------------------
|
|
508
529
|
|
|
509
|
-
async function doAnalyticsList(
|
|
530
|
+
async function doAnalyticsList(
|
|
531
|
+
opts: InfraOptions,
|
|
532
|
+
cf?: CloudflareService
|
|
533
|
+
): Promise<void> {
|
|
510
534
|
const cloudflare = cf ?? new CloudflareService();
|
|
511
535
|
const result = await cloudflare.analyticsList();
|
|
512
536
|
displayListResult(result, opts);
|
|
513
537
|
}
|
|
514
538
|
|
|
515
|
-
async function doAnalyticsCreate(
|
|
539
|
+
async function doAnalyticsCreate(
|
|
540
|
+
name: string,
|
|
541
|
+
opts: InfraOptions,
|
|
542
|
+
cf?: CloudflareService
|
|
543
|
+
): Promise<void> {
|
|
516
544
|
const cloudflare = cf ?? new CloudflareService();
|
|
517
545
|
const result = await cloudflare.analyticsCreate(name);
|
|
518
546
|
if (result.ok) {
|
|
@@ -541,7 +569,9 @@ async function doAnalyticsCreate(name: string, opts: InfraOptions, cf?: Cloudfla
|
|
|
541
569
|
export function registerInfraCommand(program: Command): void {
|
|
542
570
|
const infraCmd = program
|
|
543
571
|
.command("infra")
|
|
544
|
-
.summary(
|
|
572
|
+
.summary(
|
|
573
|
+
"Manage Cloudflare infrastructure (D1, KV, R2, Queues, Vectorize, Analytics)"
|
|
574
|
+
)
|
|
545
575
|
.description(
|
|
546
576
|
`Provision and manage Cloudflare infrastructure resources.
|
|
547
577
|
|
|
@@ -861,7 +891,9 @@ EXAMPLES:
|
|
|
861
891
|
vectorizeCmd
|
|
862
892
|
.command("create <name>")
|
|
863
893
|
.summary("Create a new Vectorize index")
|
|
864
|
-
.description(
|
|
894
|
+
.description(
|
|
895
|
+
"Create a new Vectorize index with default dimensions (768) and cosine metric."
|
|
896
|
+
)
|
|
865
897
|
.action(async function (this: Command, name: string) {
|
|
866
898
|
const opts = getOptions(this);
|
|
867
899
|
await doVectorizeCreate(name, opts);
|
|
@@ -904,7 +936,9 @@ EXAMPLES:
|
|
|
904
936
|
analyticsCmd
|
|
905
937
|
.command("create <name>")
|
|
906
938
|
.summary("Show instructions for creating an Analytics Engine dataset")
|
|
907
|
-
.description(
|
|
939
|
+
.description(
|
|
940
|
+
"Analytics Engine datasets must be created via Cloudflare Dashboard."
|
|
941
|
+
)
|
|
908
942
|
.action(async function (this: Command, name: string) {
|
|
909
943
|
const opts = getOptions(this);
|
|
910
944
|
await doAnalyticsCreate(name, opts);
|
|
@@ -120,10 +120,10 @@ const origWhoami = CloudflareService.prototype.whoami;
|
|
|
120
120
|
|
|
121
121
|
const mockWhoami = mock(
|
|
122
122
|
async (): Promise<
|
|
123
|
-
{ ok: true;
|
|
123
|
+
{ ok: true; value: string } | { ok: false; error: string }
|
|
124
124
|
> => ({
|
|
125
125
|
ok: true,
|
|
126
|
-
|
|
126
|
+
value: "user@example.com",
|
|
127
127
|
})
|
|
128
128
|
);
|
|
129
129
|
|
|
@@ -35,11 +35,14 @@ beforeEach(() => {
|
|
|
35
35
|
process.exitCode = 0;
|
|
36
36
|
|
|
37
37
|
// Restore originals
|
|
38
|
-
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
39
|
-
|
|
38
|
+
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
39
|
+
origCheckAll;
|
|
40
|
+
(DbService.prototype as Record<string, unknown>).resolveDbName =
|
|
41
|
+
origResolveDbName;
|
|
40
42
|
(DbService.prototype as Record<string, unknown>).query = origQuery;
|
|
41
43
|
(DbService.prototype as Record<string, unknown>).export = origExport;
|
|
42
|
-
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
44
|
+
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
45
|
+
origResolveNs;
|
|
43
46
|
(KvSyncService.prototype as Record<string, unknown>).get = origGet;
|
|
44
47
|
(KvSyncService.prototype as Record<string, unknown>).set = origSet;
|
|
45
48
|
|
|
@@ -55,29 +58,38 @@ beforeEach(() => {
|
|
|
55
58
|
}));
|
|
56
59
|
|
|
57
60
|
resolveDbNameMock = mock(async () => "trade-data-db");
|
|
58
|
-
queryMock = mock(
|
|
61
|
+
queryMock = mock(
|
|
62
|
+
async (_dbName: string, _sql: string, _remote: boolean) =>
|
|
63
|
+
"[mock query result]"
|
|
64
|
+
);
|
|
59
65
|
exportMock = mock(async (_dbName: string) => "backup-2026-05-13.sql");
|
|
60
66
|
resolveNamespaceIdMock = mock(async () => "ns-id-123");
|
|
61
67
|
getMock = mock(async (_nsId: string, _key: string) => "false");
|
|
62
68
|
setMock = mock(async (_nsId: string, _key: string, _value: string) => {});
|
|
63
69
|
|
|
64
70
|
// Install mocks on prototypes
|
|
65
|
-
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
66
|
-
|
|
71
|
+
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
72
|
+
checkAllWorkerHealthMock;
|
|
73
|
+
(DbService.prototype as Record<string, unknown>).resolveDbName =
|
|
74
|
+
resolveDbNameMock;
|
|
67
75
|
(DbService.prototype as Record<string, unknown>).query = queryMock;
|
|
68
76
|
(DbService.prototype as Record<string, unknown>).export = exportMock;
|
|
69
|
-
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
77
|
+
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
78
|
+
resolveNamespaceIdMock;
|
|
70
79
|
(KvSyncService.prototype as Record<string, unknown>).get = getMock;
|
|
71
80
|
(KvSyncService.prototype as Record<string, unknown>).set = setMock;
|
|
72
81
|
});
|
|
73
82
|
|
|
74
83
|
afterEach(() => {
|
|
75
84
|
mock.restore();
|
|
76
|
-
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
77
|
-
|
|
85
|
+
(MonitorService.prototype as Record<string, unknown>).checkAllWorkerHealth =
|
|
86
|
+
origCheckAll;
|
|
87
|
+
(DbService.prototype as Record<string, unknown>).resolveDbName =
|
|
88
|
+
origResolveDbName;
|
|
78
89
|
(DbService.prototype as Record<string, unknown>).query = origQuery;
|
|
79
90
|
(DbService.prototype as Record<string, unknown>).export = origExport;
|
|
80
|
-
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
91
|
+
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
92
|
+
origResolveNs;
|
|
81
93
|
(KvSyncService.prototype as Record<string, unknown>).get = origGet;
|
|
82
94
|
(KvSyncService.prototype as Record<string, unknown>).set = origSet;
|
|
83
95
|
});
|
|
@@ -172,7 +184,9 @@ describe("registerMonitorCommand", () => {
|
|
|
172
184
|
checkAllWorkerHealthMock = mock(async () => {
|
|
173
185
|
throw new Error("Connection failed");
|
|
174
186
|
});
|
|
175
|
-
(
|
|
187
|
+
(
|
|
188
|
+
MonitorService.prototype as Record<string, unknown>
|
|
189
|
+
).checkAllWorkerHealth = checkAllWorkerHealthMock;
|
|
176
190
|
|
|
177
191
|
const program = await createProgram();
|
|
178
192
|
await program.parseAsync(["monitor", "status"], { from: "user" });
|
|
@@ -206,20 +220,34 @@ describe("registerMonitorCommand", () => {
|
|
|
206
220
|
describe("monitor kill-switch", () => {
|
|
207
221
|
it("shows kill switch status", async () => {
|
|
208
222
|
const program = await createProgram();
|
|
209
|
-
await program.parseAsync(["monitor", "kill-switch", "show"], {
|
|
223
|
+
await program.parseAsync(["monitor", "kill-switch", "show"], {
|
|
224
|
+
from: "user",
|
|
225
|
+
});
|
|
210
226
|
expect(getMock).toHaveBeenCalledWith("ns-id-123", "trade:kill_switch");
|
|
211
227
|
});
|
|
212
228
|
|
|
213
229
|
it("turns kill switch on", async () => {
|
|
214
230
|
const program = await createProgram();
|
|
215
|
-
await program.parseAsync(["monitor", "kill-switch", "on"], {
|
|
216
|
-
|
|
231
|
+
await program.parseAsync(["monitor", "kill-switch", "on"], {
|
|
232
|
+
from: "user",
|
|
233
|
+
});
|
|
234
|
+
expect(setMock).toHaveBeenCalledWith(
|
|
235
|
+
"ns-id-123",
|
|
236
|
+
"trade:kill_switch",
|
|
237
|
+
"true"
|
|
238
|
+
);
|
|
217
239
|
});
|
|
218
240
|
|
|
219
241
|
it("turns kill switch off", async () => {
|
|
220
242
|
const program = await createProgram();
|
|
221
|
-
await program.parseAsync(["monitor", "kill-switch", "off"], {
|
|
222
|
-
|
|
243
|
+
await program.parseAsync(["monitor", "kill-switch", "off"], {
|
|
244
|
+
from: "user",
|
|
245
|
+
});
|
|
246
|
+
expect(setMock).toHaveBeenCalledWith(
|
|
247
|
+
"ns-id-123",
|
|
248
|
+
"trade:kill_switch",
|
|
249
|
+
"false"
|
|
250
|
+
);
|
|
223
251
|
});
|
|
224
252
|
});
|
|
225
253
|
|
|
@@ -2,16 +2,16 @@ import { Command } from "commander";
|
|
|
2
2
|
import { DbService } from "../../services/db/index.js";
|
|
3
3
|
import { MonitorService } from "./monitor-service.js";
|
|
4
4
|
import { KvSyncService } from "../../services/kv/kv-sync-service.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
formatSuccess,
|
|
7
|
+
formatError,
|
|
8
|
+
formatTable,
|
|
9
|
+
getFormatOptions,
|
|
10
|
+
} from "../../utils/formatters.js";
|
|
6
11
|
import type { FormatOptions } from "../../utils/formatters.js";
|
|
7
12
|
import { ExitCode } from "../../utils/errors.js";
|
|
8
13
|
import { theme } from "../../utils/theme.js";
|
|
9
14
|
|
|
10
|
-
function getFormatOptions(cmd: Command): FormatOptions {
|
|
11
|
-
const opts = cmd.optsWithGlobals();
|
|
12
|
-
return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
15
|
async function doMonitorStatus(fmt: FormatOptions): Promise<void> {
|
|
16
16
|
try {
|
|
17
17
|
const monitor = new MonitorService();
|
|
@@ -39,7 +39,10 @@ async function doMonitorStatus(fmt: FormatOptions): Promise<void> {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
async function doMonitorTrades(
|
|
42
|
+
async function doMonitorTrades(
|
|
43
|
+
limit: number,
|
|
44
|
+
fmt: FormatOptions
|
|
45
|
+
): Promise<void> {
|
|
43
46
|
try {
|
|
44
47
|
const db = new DbService();
|
|
45
48
|
const dbName = await db.resolveDbName();
|
|
@@ -52,7 +55,10 @@ async function doMonitorTrades(limit: number, fmt: FormatOptions): Promise<void>
|
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
async function doMonitorLogs(
|
|
58
|
+
async function doMonitorLogs(
|
|
59
|
+
workerName: string | undefined,
|
|
60
|
+
fmt: FormatOptions
|
|
61
|
+
): Promise<void> {
|
|
56
62
|
try {
|
|
57
63
|
const db = new DbService();
|
|
58
64
|
const dbName = await db.resolveDbName();
|
|
@@ -72,7 +78,7 @@ async function doMonitorLogs(workerName: string | undefined, fmt: FormatOptions)
|
|
|
72
78
|
|
|
73
79
|
async function doMonitorKillSwitch(
|
|
74
80
|
action: "show" | "on" | "off",
|
|
75
|
-
fmt: FormatOptions
|
|
81
|
+
fmt: FormatOptions
|
|
76
82
|
): Promise<void> {
|
|
77
83
|
try {
|
|
78
84
|
const kv = new KvSyncService();
|
|
@@ -81,16 +87,20 @@ async function doMonitorKillSwitch(
|
|
|
81
87
|
|
|
82
88
|
if (action === "show") {
|
|
83
89
|
const value = await kv.get(namespaceId, key);
|
|
84
|
-
const status =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const status =
|
|
91
|
+
value === "true"
|
|
92
|
+
? "KILL SWITCH IS ON (trading halted)"
|
|
93
|
+
: value === "false"
|
|
94
|
+
? "Kill switch is off (trading active)"
|
|
95
|
+
: `Kill switch value: ${value ?? "(not set)"}`;
|
|
89
96
|
process.stdout.write(`${theme.info(status)}\n`);
|
|
90
97
|
} else {
|
|
91
98
|
const newValue = action === "on" ? "true" : "false";
|
|
92
99
|
await kv.set(namespaceId, key, newValue);
|
|
93
|
-
formatSuccess(
|
|
100
|
+
formatSuccess(
|
|
101
|
+
`Kill switch turned ${action === "on" ? "ON" : "OFF"}`,
|
|
102
|
+
fmt
|
|
103
|
+
);
|
|
94
104
|
}
|
|
95
105
|
} catch (err) {
|
|
96
106
|
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
@@ -170,7 +180,11 @@ EXAMPLES:
|
|
|
170
180
|
.command("trades")
|
|
171
181
|
.summary("Show recent trades from D1")
|
|
172
182
|
.description("Query the trades table for the most recent entries.")
|
|
173
|
-
.argument(
|
|
183
|
+
.argument(
|
|
184
|
+
"[limit]",
|
|
185
|
+
"Number of trades to show (default: 10, max: 100)",
|
|
186
|
+
"10"
|
|
187
|
+
)
|
|
174
188
|
.action(async function (this: Command, limit: string) {
|
|
175
189
|
const fmt = getFormatOptions(this);
|
|
176
190
|
await doMonitorTrades(parseInt(limit, 10) || 10, fmt);
|
|
@@ -179,7 +193,9 @@ EXAMPLES:
|
|
|
179
193
|
monitorCmd
|
|
180
194
|
.command("logs")
|
|
181
195
|
.summary("Show recent system logs from D1")
|
|
182
|
-
.description(
|
|
196
|
+
.description(
|
|
197
|
+
"Query the system_logs table. Optionally filter by worker name."
|
|
198
|
+
)
|
|
183
199
|
.argument("[worker]", "Worker name to filter (optional)")
|
|
184
200
|
.action(async function (this: Command, worker?: string) {
|
|
185
201
|
const fmt = getFormatOptions(this);
|
|
@@ -210,7 +226,9 @@ Commands:
|
|
|
210
226
|
ksCmd
|
|
211
227
|
.command("on")
|
|
212
228
|
.summary("Halt all trading")
|
|
213
|
-
.description(
|
|
229
|
+
.description(
|
|
230
|
+
"Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately."
|
|
231
|
+
)
|
|
214
232
|
.action(async function (this: Command) {
|
|
215
233
|
const fmt = getFormatOptions(this);
|
|
216
234
|
await doMonitorKillSwitch("on", fmt);
|
|
@@ -219,7 +237,9 @@ Commands:
|
|
|
219
237
|
ksCmd
|
|
220
238
|
.command("off")
|
|
221
239
|
.summary("Resume trading")
|
|
222
|
-
.description(
|
|
240
|
+
.description(
|
|
241
|
+
"Set trade:kill_switch=false to resume normal trading operations."
|
|
242
|
+
)
|
|
223
243
|
.action(async function (this: Command) {
|
|
224
244
|
const fmt = getFormatOptions(this);
|
|
225
245
|
await doMonitorKillSwitch("off", fmt);
|
|
@@ -228,7 +248,9 @@ Commands:
|
|
|
228
248
|
monitorCmd
|
|
229
249
|
.command("queue-depth")
|
|
230
250
|
.summary("Show queue details")
|
|
231
|
-
.description(
|
|
251
|
+
.description(
|
|
252
|
+
"List queues via wrangler queues list to show configured queues."
|
|
253
|
+
)
|
|
232
254
|
.action(async function (this: Command) {
|
|
233
255
|
const fmt = getFormatOptions(this);
|
|
234
256
|
await doMonitorQueueDepth(fmt);
|
|
@@ -237,7 +259,9 @@ Commands:
|
|
|
237
259
|
monitorCmd
|
|
238
260
|
.command("backup")
|
|
239
261
|
.summary("Export D1 database to .sql file")
|
|
240
|
-
.description(
|
|
262
|
+
.description(
|
|
263
|
+
"Export the D1 database to a timestamped .sql backup file via wrangler d1 export."
|
|
264
|
+
)
|
|
241
265
|
.action(async function (this: Command) {
|
|
242
266
|
const fmt = getFormatOptions(this);
|
|
243
267
|
await doMonitorBackup(fmt);
|
|
@@ -27,12 +27,22 @@ export class MonitorService {
|
|
|
27
27
|
for (const name of enabled) {
|
|
28
28
|
const url = `https://${name}.${prefix}.workers.dev/health`;
|
|
29
29
|
try {
|
|
30
|
-
const response = await fetch(url, {
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
signal: AbortSignal.timeout(5000),
|
|
32
|
+
});
|
|
31
33
|
if (response.ok) {
|
|
32
|
-
results.push({
|
|
34
|
+
results.push({
|
|
35
|
+
worker: name,
|
|
36
|
+
status: "healthy",
|
|
37
|
+
statusCode: response.status,
|
|
38
|
+
});
|
|
33
39
|
healthy++;
|
|
34
40
|
} else {
|
|
35
|
-
results.push({
|
|
41
|
+
results.push({
|
|
42
|
+
worker: name,
|
|
43
|
+
status: "degraded",
|
|
44
|
+
statusCode: response.status,
|
|
45
|
+
});
|
|
36
46
|
degraded++;
|
|
37
47
|
}
|
|
38
48
|
} catch (err) {
|
|
@@ -45,6 +55,11 @@ export class MonitorService {
|
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
workers: results,
|
|
60
|
+
healthyCount: healthy,
|
|
61
|
+
degradedCount: degraded,
|
|
62
|
+
unreachableCount: unreachable,
|
|
63
|
+
};
|
|
49
64
|
}
|
|
50
65
|
}
|
|
@@ -40,18 +40,22 @@ beforeEach(() => {
|
|
|
40
40
|
process.exitCode = 0;
|
|
41
41
|
|
|
42
42
|
// Restore originals
|
|
43
|
-
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
43
|
+
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
44
|
+
origRunSystemCheck;
|
|
44
45
|
(CloudflareService.prototype as Record<string, unknown>).deploy = origDeploy;
|
|
45
46
|
(CloudflareService.prototype as Record<string, unknown>).d1List = origD1List;
|
|
46
47
|
(CloudflareService.prototype as Record<string, unknown>).kvList = origKvList;
|
|
47
48
|
(CloudflareService.prototype as Record<string, unknown>).r2List = origR2List;
|
|
48
|
-
(CloudflareService.prototype as Record<string, unknown>).queueList =
|
|
49
|
-
|
|
49
|
+
(CloudflareService.prototype as Record<string, unknown>).queueList =
|
|
50
|
+
origQueueList;
|
|
51
|
+
(DbService.prototype as Record<string, unknown>).resolveDbName =
|
|
52
|
+
origResolveDbName;
|
|
50
53
|
(DbService.prototype as Record<string, unknown>).apply = origApply;
|
|
51
54
|
(DbService.prototype as Record<string, unknown>).migrate = origMigrate;
|
|
52
55
|
(DbService.prototype as Record<string, unknown>).export = origExport;
|
|
53
56
|
(DbService.prototype as Record<string, unknown>).reset = origReset;
|
|
54
|
-
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
57
|
+
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
58
|
+
origResolveNs;
|
|
55
59
|
(KvSyncService.prototype as Record<string, unknown>).set = origKvSet;
|
|
56
60
|
|
|
57
61
|
// Fresh mocks
|
|
@@ -73,24 +77,29 @@ beforeEach(() => {
|
|
|
73
77
|
data: { url: "https://test-worker.cryptolinx.workers.dev" },
|
|
74
78
|
}));
|
|
75
79
|
|
|
76
|
-
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
80
|
+
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
81
|
+
runSystemCheckMock;
|
|
77
82
|
(CloudflareService.prototype as Record<string, unknown>).deploy = deployMock;
|
|
78
83
|
});
|
|
79
84
|
|
|
80
85
|
afterEach(() => {
|
|
81
86
|
mock.restore();
|
|
82
|
-
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
87
|
+
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
88
|
+
origRunSystemCheck;
|
|
83
89
|
(CloudflareService.prototype as Record<string, unknown>).deploy = origDeploy;
|
|
84
90
|
(CloudflareService.prototype as Record<string, unknown>).d1List = origD1List;
|
|
85
91
|
(CloudflareService.prototype as Record<string, unknown>).kvList = origKvList;
|
|
86
92
|
(CloudflareService.prototype as Record<string, unknown>).r2List = origR2List;
|
|
87
|
-
(CloudflareService.prototype as Record<string, unknown>).queueList =
|
|
88
|
-
|
|
93
|
+
(CloudflareService.prototype as Record<string, unknown>).queueList =
|
|
94
|
+
origQueueList;
|
|
95
|
+
(DbService.prototype as Record<string, unknown>).resolveDbName =
|
|
96
|
+
origResolveDbName;
|
|
89
97
|
(DbService.prototype as Record<string, unknown>).apply = origApply;
|
|
90
98
|
(DbService.prototype as Record<string, unknown>).migrate = origMigrate;
|
|
91
99
|
(DbService.prototype as Record<string, unknown>).export = origExport;
|
|
92
100
|
(DbService.prototype as Record<string, unknown>).reset = origReset;
|
|
93
|
-
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
101
|
+
(KvSyncService.prototype as Record<string, unknown>).resolveNamespaceId =
|
|
102
|
+
origResolveNs;
|
|
94
103
|
(KvSyncService.prototype as Record<string, unknown>).set = origKvSet;
|
|
95
104
|
});
|
|
96
105
|
|
|
@@ -127,7 +136,9 @@ describe("registerRepairCommand", () => {
|
|
|
127
136
|
const repairCmd = program.commands.find((c) => c.name() === "repair")!;
|
|
128
137
|
const workerCmd = repairCmd.commands.find((c) => c.name() === "worker");
|
|
129
138
|
expect(workerCmd).toBeDefined();
|
|
130
|
-
expect(
|
|
139
|
+
expect(
|
|
140
|
+
workerCmd!.registeredArguments.some((a) => a.name() === "name")
|
|
141
|
+
).toBe(true);
|
|
131
142
|
});
|
|
132
143
|
|
|
133
144
|
it("registers 'repair infra' subcommand", async () => {
|
|
@@ -139,7 +150,9 @@ describe("registerRepairCommand", () => {
|
|
|
139
150
|
it("registers 'repair secrets' subcommand", async () => {
|
|
140
151
|
const program = await createProgram();
|
|
141
152
|
const repairCmd = program.commands.find((c) => c.name() === "repair")!;
|
|
142
|
-
expect(
|
|
153
|
+
expect(
|
|
154
|
+
repairCmd.commands.find((c) => c.name() === "secrets")
|
|
155
|
+
).toBeDefined();
|
|
143
156
|
});
|
|
144
157
|
|
|
145
158
|
it("registers 'repair kv' subcommand", async () => {
|
|
@@ -157,7 +170,9 @@ describe("registerRepairCommand", () => {
|
|
|
157
170
|
it("registers 'repair rebuild' subcommand", async () => {
|
|
158
171
|
const program = await createProgram();
|
|
159
172
|
const repairCmd = program.commands.find((c) => c.name() === "repair")!;
|
|
160
|
-
expect(
|
|
173
|
+
expect(
|
|
174
|
+
repairCmd.commands.find((c) => c.name() === "rebuild")
|
|
175
|
+
).toBeDefined();
|
|
161
176
|
});
|
|
162
177
|
|
|
163
178
|
// -- repair check ---------------------------------------------------------
|
|
@@ -176,8 +191,11 @@ describe("registerRepairCommand", () => {
|
|
|
176
191
|
});
|
|
177
192
|
|
|
178
193
|
it("sets exitCode on failure", async () => {
|
|
179
|
-
runSystemCheckMock = mock(async () => {
|
|
180
|
-
|
|
194
|
+
runSystemCheckMock = mock(async () => {
|
|
195
|
+
throw new Error("Check failed");
|
|
196
|
+
});
|
|
197
|
+
(RepairService.prototype as Record<string, unknown>).runSystemCheck =
|
|
198
|
+
runSystemCheckMock;
|
|
181
199
|
|
|
182
200
|
const program = await createProgram();
|
|
183
201
|
await program.parseAsync(["repair", "check"], { from: "user" });
|
|
@@ -197,7 +215,9 @@ describe("registerRepairCommand", () => {
|
|
|
197
215
|
it("handles unknown worker name", async () => {
|
|
198
216
|
// Mock getWorker to return undefined
|
|
199
217
|
const program = await createProgram();
|
|
200
|
-
await program.parseAsync(["repair", "worker", "nonexistent"], {
|
|
218
|
+
await program.parseAsync(["repair", "worker", "nonexistent"], {
|
|
219
|
+
from: "user",
|
|
220
|
+
});
|
|
201
221
|
expect(process.exitCode).toBe(1);
|
|
202
222
|
});
|
|
203
223
|
});
|
|
@@ -4,17 +4,14 @@ import { CloudflareService } from "../../services/cloudflare/index.js";
|
|
|
4
4
|
import { DbService } from "../../services/db/index.js";
|
|
5
5
|
import { KvSyncService } from "../../services/kv/kv-sync-service.js";
|
|
6
6
|
import { SecretsService } from "../../services/secrets/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
formatError,
|
|
9
|
+
formatSuccess,
|
|
10
|
+
type FormatOptions,
|
|
11
|
+
getFormatOptions,
|
|
12
|
+
} from "../../utils/formatters.js";
|
|
8
13
|
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
9
14
|
|
|
10
|
-
function getFormatOptions(cmd: Command): FormatOptions {
|
|
11
|
-
const rootCmd = cmd.parent?.parent as Command | undefined;
|
|
12
|
-
return {
|
|
13
|
-
json: Boolean(rootCmd?.optsWithGlobals()?.json),
|
|
14
|
-
quiet: Boolean(rootCmd?.optsWithGlobals()?.quiet),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
15
|
async function handleCheck(fmt: FormatOptions): Promise<void> {
|
|
19
16
|
try {
|
|
20
17
|
const svc = new RepairService();
|
|
@@ -22,7 +19,10 @@ async function handleCheck(fmt: FormatOptions): Promise<void> {
|
|
|
22
19
|
if (result.allPassed) {
|
|
23
20
|
formatSuccess("All checks passed", fmt);
|
|
24
21
|
} else {
|
|
25
|
-
formatError(
|
|
22
|
+
formatError(
|
|
23
|
+
new CLIError(`${result.failedCount} check(s) failed`, ExitCode.ERROR),
|
|
24
|
+
fmt
|
|
25
|
+
);
|
|
26
26
|
process.exitCode = ExitCode.ERROR;
|
|
27
27
|
}
|
|
28
28
|
} catch (err) {
|
|
@@ -38,16 +38,25 @@ async function handleWorker(name: string, fmt: FormatOptions): Promise<void> {
|
|
|
38
38
|
await config.load();
|
|
39
39
|
const workerConfig = config.getWorker(name);
|
|
40
40
|
if (!workerConfig) {
|
|
41
|
-
formatError(
|
|
41
|
+
formatError(
|
|
42
|
+
new CLIError(`Worker "${name}" not found`, ExitCode.ERROR),
|
|
43
|
+
fmt
|
|
44
|
+
);
|
|
42
45
|
process.exitCode = ExitCode.ERROR;
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
48
|
const cf = new CloudflareService();
|
|
46
49
|
const result = await cf.deploy(workerConfig.path);
|
|
47
50
|
if (result.ok) {
|
|
48
|
-
formatSuccess(`Worker "${name}" deployed — ${result.
|
|
51
|
+
formatSuccess(`Worker "${name}" deployed — ${result.value.url}`, fmt);
|
|
49
52
|
} else {
|
|
50
|
-
formatError(
|
|
53
|
+
formatError(
|
|
54
|
+
new CLIError(
|
|
55
|
+
`Failed to deploy "${name}": ${result.error}`,
|
|
56
|
+
ExitCode.ERROR
|
|
57
|
+
),
|
|
58
|
+
fmt
|
|
59
|
+
);
|
|
51
60
|
process.exitCode = ExitCode.ERROR;
|
|
52
61
|
}
|
|
53
62
|
} catch (err) {
|
|
@@ -63,7 +72,10 @@ async function handleInfra(fmt: FormatOptions): Promise<void> {
|
|
|
63
72
|
const kv = await cf.kvList();
|
|
64
73
|
const r2 = await cf.r2List();
|
|
65
74
|
const q = await cf.queueList();
|
|
66
|
-
formatSuccess(
|
|
75
|
+
formatSuccess(
|
|
76
|
+
`D1: ${d1.ok ? "ok" : "fail"}, KV: ${kv.ok ? "ok" : "fail"}, R2: ${r2.ok ? "ok" : "fail"}, Queues: ${q.ok ? "ok" : "fail"}`,
|
|
77
|
+
fmt
|
|
78
|
+
);
|
|
67
79
|
} catch (err) {
|
|
68
80
|
formatError(err instanceof Error ? err : String(err), fmt);
|
|
69
81
|
process.exitCode = ExitCode.ERROR;
|
|
@@ -80,7 +92,10 @@ async function handleSecrets(fmt: FormatOptions): Promise<void> {
|
|
|
80
92
|
missing += check.missing.length;
|
|
81
93
|
}
|
|
82
94
|
if (missing > 0) {
|
|
83
|
-
formatError(
|
|
95
|
+
formatError(
|
|
96
|
+
new CLIError(`${missing} secret(s) missing`, ExitCode.ERROR),
|
|
97
|
+
fmt
|
|
98
|
+
);
|
|
84
99
|
process.exitCode = ExitCode.ERROR;
|
|
85
100
|
} else {
|
|
86
101
|
formatSuccess("All secrets present", fmt);
|
|
@@ -139,11 +154,15 @@ export function registerRepairCommand(program: Command): void {
|
|
|
139
154
|
const repairCmd = program
|
|
140
155
|
.command("repair")
|
|
141
156
|
.summary("Diagnose and repair the Hoox system")
|
|
142
|
-
.description(
|
|
157
|
+
.description(
|
|
158
|
+
"Run checks, deploy workers, or fix infrastructure, secrets, KV, and DB issues."
|
|
159
|
+
);
|
|
143
160
|
|
|
144
161
|
repairCmd
|
|
145
162
|
.command("check")
|
|
146
|
-
.description(
|
|
163
|
+
.description(
|
|
164
|
+
"Run system diagnostics (workers, deps, types, infra, secrets)"
|
|
165
|
+
)
|
|
147
166
|
.action(async () => {
|
|
148
167
|
const fmt = getFormatOptions(repairCmd);
|
|
149
168
|
await handleCheck(fmt);
|
|
@@ -191,7 +210,9 @@ export function registerRepairCommand(program: Command): void {
|
|
|
191
210
|
|
|
192
211
|
repairCmd
|
|
193
212
|
.command("rebuild")
|
|
194
|
-
.description(
|
|
213
|
+
.description(
|
|
214
|
+
"Full rebuild: check, deploy all workers, fix infra/secrets/db"
|
|
215
|
+
)
|
|
195
216
|
.action(async () => {
|
|
196
217
|
const fmt = getFormatOptions(repairCmd);
|
|
197
218
|
await handleRebuild(fmt);
|