@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,271 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the monitor command.
|
|
3
|
-
*
|
|
4
|
-
* Stubs MonitorService, DbService, and KvSyncService prototypes to verify
|
|
5
|
-
* the monitor command logic in isolation. Uses Commander's exitOverride to
|
|
6
|
-
* suppress process exits during test runs.
|
|
7
|
-
*/
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
9
|
-
import { Command } from "commander";
|
|
10
|
-
import { MonitorService } from "./monitor-service.js";
|
|
11
|
-
import { DbService } from "../../services/db/db-service.js";
|
|
12
|
-
import { KvSyncService } from "../../services/kv/kv-sync-service.js";
|
|
13
|
-
|
|
14
|
-
// Stub variables
|
|
15
|
-
let checkAllWorkerHealthMock: ReturnType<typeof mock>;
|
|
16
|
-
let resolveDbNameMock: ReturnType<typeof mock>;
|
|
17
|
-
let queryMock: ReturnType<typeof mock>;
|
|
18
|
-
let exportMock: ReturnType<typeof mock>;
|
|
19
|
-
let resolveNamespaceIdMock: ReturnType<typeof mock>;
|
|
20
|
-
let getMock: ReturnType<typeof mock>;
|
|
21
|
-
let setMock: ReturnType<typeof mock>;
|
|
22
|
-
|
|
23
|
-
// Preserve originals
|
|
24
|
-
const origCheckAll = MonitorService.prototype.checkAllWorkerHealth;
|
|
25
|
-
const origResolveDbName = DbService.prototype.resolveDbName;
|
|
26
|
-
const origQuery = DbService.prototype.query;
|
|
27
|
-
const origExport = DbService.prototype.export;
|
|
28
|
-
const origResolveNs = KvSyncService.prototype.resolveNamespaceId;
|
|
29
|
-
const origGet = KvSyncService.prototype.get;
|
|
30
|
-
const origSet = KvSyncService.prototype.set;
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
mock.restore();
|
|
34
|
-
process.exitCode = 0;
|
|
35
|
-
|
|
36
|
-
// Restore originals
|
|
37
|
-
(
|
|
38
|
-
MonitorService.prototype as unknown as Record<string, unknown>
|
|
39
|
-
).checkAllWorkerHealth = origCheckAll;
|
|
40
|
-
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
41
|
-
origResolveDbName;
|
|
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;
|
|
50
|
-
|
|
51
|
-
// Fresh mocks
|
|
52
|
-
checkAllWorkerHealthMock = mock(async () => ({
|
|
53
|
-
workers: [
|
|
54
|
-
{ worker: "hoox", status: "healthy", statusCode: 200 },
|
|
55
|
-
{ worker: "trade-worker", status: "healthy", statusCode: 200 },
|
|
56
|
-
],
|
|
57
|
-
healthyCount: 2,
|
|
58
|
-
degradedCount: 0,
|
|
59
|
-
unreachableCount: 0,
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
resolveDbNameMock = mock(async () => "trade-data-db");
|
|
63
|
-
queryMock = mock(
|
|
64
|
-
async (_dbName: string, _sql: string, _remote: boolean) =>
|
|
65
|
-
"[mock query result]"
|
|
66
|
-
);
|
|
67
|
-
exportMock = mock(async (_dbName: string) => "backup-2026-05-13.sql");
|
|
68
|
-
resolveNamespaceIdMock = mock(async () => "ns-id-123");
|
|
69
|
-
getMock = mock(async (_nsId: string, _key: string) => "false");
|
|
70
|
-
setMock = mock(async (_nsId: string, _key: string, _value: string) => {});
|
|
71
|
-
|
|
72
|
-
// Install mocks on prototypes
|
|
73
|
-
(
|
|
74
|
-
MonitorService.prototype as unknown as Record<string, unknown>
|
|
75
|
-
).checkAllWorkerHealth = checkAllWorkerHealthMock;
|
|
76
|
-
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
77
|
-
resolveDbNameMock;
|
|
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;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
afterEach(() => {
|
|
89
|
-
mock.restore();
|
|
90
|
-
(
|
|
91
|
-
MonitorService.prototype as unknown as Record<string, unknown>
|
|
92
|
-
).checkAllWorkerHealth = origCheckAll;
|
|
93
|
-
(DbService.prototype as unknown as Record<string, unknown>).resolveDbName =
|
|
94
|
-
origResolveDbName;
|
|
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;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
async function importMonitorCommand(): Promise<{
|
|
106
|
-
registerMonitorCommand: typeof import("./monitor-command.js").registerMonitorCommand;
|
|
107
|
-
}> {
|
|
108
|
-
return import("./monitor-command.js");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function createProgram(): Promise<Command> {
|
|
112
|
-
const { registerMonitorCommand } = await importMonitorCommand();
|
|
113
|
-
const program = new Command().name("hoox-test").exitOverride(() => {});
|
|
114
|
-
registerMonitorCommand(program);
|
|
115
|
-
return program;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
describe("registerMonitorCommand", () => {
|
|
119
|
-
// -- Command registration -------------------------------------------------
|
|
120
|
-
|
|
121
|
-
it("registers 'monitor' as a command on the program", async () => {
|
|
122
|
-
const program = await createProgram();
|
|
123
|
-
const cmd = program.commands.find((c) => c.name() === "monitor");
|
|
124
|
-
expect(cmd).toBeDefined();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("registers 'monitor status' subcommand", async () => {
|
|
128
|
-
const program = await createProgram();
|
|
129
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
130
|
-
const statusCmd = monitorCmd.commands.find((c) => c.name() === "status");
|
|
131
|
-
expect(statusCmd).toBeDefined();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("registers 'monitor trades' subcommand with argument", async () => {
|
|
135
|
-
const program = await createProgram();
|
|
136
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
137
|
-
const tradesCmd = monitorCmd.commands.find((c) => c.name() === "trades");
|
|
138
|
-
expect(tradesCmd).toBeDefined();
|
|
139
|
-
const args = tradesCmd!.registeredArguments;
|
|
140
|
-
expect(args.some((a) => a.name() === "limit")).toBe(true);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("registers 'monitor logs' subcommand with optional argument", async () => {
|
|
144
|
-
const program = await createProgram();
|
|
145
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
146
|
-
const logsCmd = monitorCmd.commands.find((c) => c.name() === "logs");
|
|
147
|
-
expect(logsCmd).toBeDefined();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("registers 'monitor kill-switch' with show/on/off subcommands", async () => {
|
|
151
|
-
const program = await createProgram();
|
|
152
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
153
|
-
const ksCmd = monitorCmd.commands.find((c) => c.name() === "kill-switch");
|
|
154
|
-
expect(ksCmd).toBeDefined();
|
|
155
|
-
expect(ksCmd!.commands.find((c) => c.name() === "show")).toBeDefined();
|
|
156
|
-
expect(ksCmd!.commands.find((c) => c.name() === "on")).toBeDefined();
|
|
157
|
-
expect(ksCmd!.commands.find((c) => c.name() === "off")).toBeDefined();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("registers 'monitor queue-depth' subcommand", async () => {
|
|
161
|
-
const program = await createProgram();
|
|
162
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
163
|
-
const qCmd = monitorCmd.commands.find((c) => c.name() === "queue-depth");
|
|
164
|
-
expect(qCmd).toBeDefined();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("registers 'monitor backup' subcommand", async () => {
|
|
168
|
-
const program = await createProgram();
|
|
169
|
-
const monitorCmd = program.commands.find((c) => c.name() === "monitor")!;
|
|
170
|
-
const backupCmd = monitorCmd.commands.find((c) => c.name() === "backup");
|
|
171
|
-
expect(backupCmd).toBeDefined();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// -- monitor status -------------------------------------------------------
|
|
175
|
-
|
|
176
|
-
describe("monitor status", () => {
|
|
177
|
-
it("calls checkAllWorkerHealth", async () => {
|
|
178
|
-
const program = await createProgram();
|
|
179
|
-
await program.parseAsync(["monitor", "status"], { from: "user" });
|
|
180
|
-
expect(checkAllWorkerHealthMock).toHaveBeenCalled();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("exits cleanly on success", async () => {
|
|
184
|
-
process.exitCode = 0;
|
|
185
|
-
const program = await createProgram();
|
|
186
|
-
process.exitCode = 0;
|
|
187
|
-
await program.parseAsync(["monitor", "status"], { from: "user" });
|
|
188
|
-
expect(process.exitCode).toBe(0);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("sets exitCode on service failure", async () => {
|
|
192
|
-
checkAllWorkerHealthMock = mock(async () => {
|
|
193
|
-
throw new Error("Connection failed");
|
|
194
|
-
});
|
|
195
|
-
(
|
|
196
|
-
MonitorService.prototype as unknown as Record<string, unknown>
|
|
197
|
-
).checkAllWorkerHealth = checkAllWorkerHealthMock;
|
|
198
|
-
|
|
199
|
-
const program = await createProgram();
|
|
200
|
-
await program.parseAsync(["monitor", "status"], { from: "user" });
|
|
201
|
-
expect(process.exitCode).toBe(1);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// -- monitor trades -------------------------------------------------------
|
|
206
|
-
|
|
207
|
-
describe("monitor trades", () => {
|
|
208
|
-
it("calls db.query with trades table", async () => {
|
|
209
|
-
const program = await createProgram();
|
|
210
|
-
await program.parseAsync(["monitor", "trades"], { from: "user" });
|
|
211
|
-
expect(resolveDbNameMock).toHaveBeenCalled();
|
|
212
|
-
expect(queryMock).toHaveBeenCalled();
|
|
213
|
-
const sql = queryMock.mock.calls[0][1] as string;
|
|
214
|
-
expect(sql).toContain("trades");
|
|
215
|
-
expect(sql).toContain("ORDER BY timestamp DESC");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("accepts custom limit", async () => {
|
|
219
|
-
const program = await createProgram();
|
|
220
|
-
await program.parseAsync(["monitor", "trades", "25"], { from: "user" });
|
|
221
|
-
const sql = queryMock.mock.calls[0][1] as string;
|
|
222
|
-
expect(sql).toContain("LIMIT 25");
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// -- monitor kill-switch --------------------------------------------------
|
|
227
|
-
|
|
228
|
-
describe("monitor kill-switch", () => {
|
|
229
|
-
it("shows kill switch status", async () => {
|
|
230
|
-
const program = await createProgram();
|
|
231
|
-
await program.parseAsync(["monitor", "kill-switch", "show"], {
|
|
232
|
-
from: "user",
|
|
233
|
-
});
|
|
234
|
-
expect(getMock).toHaveBeenCalledWith("ns-id-123", "trade:kill_switch");
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("turns kill switch on", async () => {
|
|
238
|
-
const program = await createProgram();
|
|
239
|
-
await program.parseAsync(["monitor", "kill-switch", "on"], {
|
|
240
|
-
from: "user",
|
|
241
|
-
});
|
|
242
|
-
expect(setMock).toHaveBeenCalledWith(
|
|
243
|
-
"ns-id-123",
|
|
244
|
-
"trade:kill_switch",
|
|
245
|
-
"true"
|
|
246
|
-
);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("turns kill switch off", async () => {
|
|
250
|
-
const program = await createProgram();
|
|
251
|
-
await program.parseAsync(["monitor", "kill-switch", "off"], {
|
|
252
|
-
from: "user",
|
|
253
|
-
});
|
|
254
|
-
expect(setMock).toHaveBeenCalledWith(
|
|
255
|
-
"ns-id-123",
|
|
256
|
-
"trade:kill_switch",
|
|
257
|
-
"false"
|
|
258
|
-
);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// -- monitor backup -------------------------------------------------------
|
|
263
|
-
|
|
264
|
-
describe("monitor backup", () => {
|
|
265
|
-
it("calls db.export", async () => {
|
|
266
|
-
const program = await createProgram();
|
|
267
|
-
await program.parseAsync(["monitor", "backup"], { from: "user" });
|
|
268
|
-
expect(exportMock).toHaveBeenCalled();
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { DbService } from "../../services/db/index.js";
|
|
3
|
-
import { MonitorService } from "./monitor-service.js";
|
|
4
|
-
import { KvSyncService } from "../../services/kv/kv-sync-service.js";
|
|
5
|
-
import {
|
|
6
|
-
formatSuccess,
|
|
7
|
-
formatError,
|
|
8
|
-
formatTable,
|
|
9
|
-
getFormatOptions,
|
|
10
|
-
} from "../../utils/formatters.js";
|
|
11
|
-
import type { FormatOptions } from "../../utils/formatters.js";
|
|
12
|
-
import { ExitCode } from "../../utils/errors.js";
|
|
13
|
-
import { theme } from "../../utils/theme.js";
|
|
14
|
-
|
|
15
|
-
async function doMonitorStatus(fmt: FormatOptions): Promise<void> {
|
|
16
|
-
try {
|
|
17
|
-
const monitor = new MonitorService();
|
|
18
|
-
const result = await monitor.checkAllWorkerHealth();
|
|
19
|
-
|
|
20
|
-
if (fmt.json) {
|
|
21
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const rows = result.workers.map((w) => ({
|
|
26
|
-
Worker: w.worker,
|
|
27
|
-
Status: w.status,
|
|
28
|
-
"Status Code": String(w.statusCode ?? "-"),
|
|
29
|
-
Error: w.error ?? "-",
|
|
30
|
-
}));
|
|
31
|
-
formatTable(rows, fmt);
|
|
32
|
-
|
|
33
|
-
process.stdout.write(
|
|
34
|
-
`\n${theme.heading("Summary:")} ${result.healthyCount} healthy, ${result.degradedCount} degraded, ${result.unreachableCount} unreachable\n`
|
|
35
|
-
);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
38
|
-
process.exitCode = ExitCode.ERROR;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function doMonitorTrades(
|
|
43
|
-
limit: number,
|
|
44
|
-
fmt: FormatOptions
|
|
45
|
-
): Promise<void> {
|
|
46
|
-
try {
|
|
47
|
-
const db = new DbService();
|
|
48
|
-
const dbName = await db.resolveDbName();
|
|
49
|
-
const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${Math.min(limit, 100)}`;
|
|
50
|
-
const output = await db.query(dbName, sql, true);
|
|
51
|
-
process.stdout.write(output + "\n");
|
|
52
|
-
} catch (err) {
|
|
53
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
54
|
-
process.exitCode = ExitCode.ERROR;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function doMonitorLogs(
|
|
59
|
-
workerName: string | undefined,
|
|
60
|
-
fmt: FormatOptions
|
|
61
|
-
): Promise<void> {
|
|
62
|
-
try {
|
|
63
|
-
const db = new DbService();
|
|
64
|
-
const dbName = await db.resolveDbName();
|
|
65
|
-
let sql: string;
|
|
66
|
-
if (workerName) {
|
|
67
|
-
sql = `SELECT * FROM system_logs WHERE worker = '${workerName.replace(/'/g, "''")}' ORDER BY timestamp DESC LIMIT 20`;
|
|
68
|
-
} else {
|
|
69
|
-
sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
|
|
70
|
-
}
|
|
71
|
-
const output = await db.query(dbName, sql, true);
|
|
72
|
-
process.stdout.write(output + "\n");
|
|
73
|
-
} catch (err) {
|
|
74
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
75
|
-
process.exitCode = ExitCode.ERROR;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function doMonitorKillSwitch(
|
|
80
|
-
action: "show" | "on" | "off",
|
|
81
|
-
fmt: FormatOptions
|
|
82
|
-
): Promise<void> {
|
|
83
|
-
try {
|
|
84
|
-
const kv = new KvSyncService();
|
|
85
|
-
const namespaceId = await kv.resolveNamespaceId();
|
|
86
|
-
const key = "trade:kill_switch";
|
|
87
|
-
|
|
88
|
-
if (action === "show") {
|
|
89
|
-
const value = await kv.get(namespaceId, key);
|
|
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)"}`;
|
|
96
|
-
process.stdout.write(`${theme.info(status)}\n`);
|
|
97
|
-
} else {
|
|
98
|
-
const newValue = action === "on" ? "true" : "false";
|
|
99
|
-
await kv.set(namespaceId, key, newValue);
|
|
100
|
-
formatSuccess(
|
|
101
|
-
`Kill switch turned ${action === "on" ? "ON" : "OFF"}`,
|
|
102
|
-
fmt
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
107
|
-
process.exitCode = ExitCode.ERROR;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function doMonitorQueueDepth(fmt: FormatOptions): Promise<void> {
|
|
112
|
-
try {
|
|
113
|
-
const proc = Bun.spawn(["wrangler", "queues", "list"], {
|
|
114
|
-
stdout: "pipe",
|
|
115
|
-
stderr: "pipe",
|
|
116
|
-
});
|
|
117
|
-
const stdout = await new Response(proc.stdout).text();
|
|
118
|
-
const stderr = await new Response(proc.stderr).text();
|
|
119
|
-
const exitCode = await proc.exited;
|
|
120
|
-
|
|
121
|
-
if (exitCode !== 0) {
|
|
122
|
-
throw new Error(stderr.trim() || `wrangler exited with code ${exitCode}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
process.stdout.write(stdout + "\n");
|
|
126
|
-
} catch (err) {
|
|
127
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
128
|
-
process.exitCode = ExitCode.ERROR;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function doMonitorBackup(fmt: FormatOptions): Promise<void> {
|
|
133
|
-
try {
|
|
134
|
-
const db = new DbService();
|
|
135
|
-
const dbName = await db.resolveDbName();
|
|
136
|
-
const outputPath = await db.export(dbName);
|
|
137
|
-
formatSuccess(`Database exported to ${outputPath}`, fmt);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
formatError(err instanceof Error ? err.message : String(err), fmt);
|
|
140
|
-
process.exitCode = ExitCode.ERROR;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function registerMonitorCommand(program: Command): void {
|
|
145
|
-
const monitorCmd = program
|
|
146
|
-
.command("monitor")
|
|
147
|
-
.summary("Monitor and operate the trading system")
|
|
148
|
-
.description(
|
|
149
|
-
`Monitor worker health, trades, logs, and perform operational tasks.
|
|
150
|
-
|
|
151
|
-
SUBCOMMANDS:
|
|
152
|
-
status Check health of all workers
|
|
153
|
-
trades [N] Show N most recent trades (default: 10)
|
|
154
|
-
logs [worker] Show recent system logs from D1
|
|
155
|
-
kill-switch Emergency stop/resume trading
|
|
156
|
-
queue-depth List queues and pending messages
|
|
157
|
-
backup Export D1 database to timestamped .sql file
|
|
158
|
-
|
|
159
|
-
EXAMPLES:
|
|
160
|
-
hoox monitor status Check all workers health
|
|
161
|
-
hoox monitor trades 20 Show 20 recent trades
|
|
162
|
-
hoox monitor logs hoox Show logs for hoox worker
|
|
163
|
-
hoox monitor kill-switch show Check kill switch status
|
|
164
|
-
hoox monitor kill-switch on Halt all trading
|
|
165
|
-
hoox monitor kill-switch off Resume trading
|
|
166
|
-
hoox monitor queue-depth Show queue info
|
|
167
|
-
hoox monitor backup Export D1 database`
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
monitorCmd
|
|
171
|
-
.command("status")
|
|
172
|
-
.summary("Check health of all workers")
|
|
173
|
-
.description("Probe each worker's /health endpoint and report status.")
|
|
174
|
-
.action(async function (this: Command) {
|
|
175
|
-
const fmt = getFormatOptions(this);
|
|
176
|
-
await doMonitorStatus(fmt);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
monitorCmd
|
|
180
|
-
.command("trades")
|
|
181
|
-
.summary("Show recent trades from D1")
|
|
182
|
-
.description("Query the trades table for the most recent entries.")
|
|
183
|
-
.argument(
|
|
184
|
-
"[limit]",
|
|
185
|
-
"Number of trades to show (default: 10, max: 100)",
|
|
186
|
-
"10"
|
|
187
|
-
)
|
|
188
|
-
.action(async function (this: Command, limit: string) {
|
|
189
|
-
const fmt = getFormatOptions(this);
|
|
190
|
-
await doMonitorTrades(parseInt(limit, 10) || 10, fmt);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
monitorCmd
|
|
194
|
-
.command("logs")
|
|
195
|
-
.summary("Show recent system logs from D1")
|
|
196
|
-
.description(
|
|
197
|
-
"Query the system_logs table. Optionally filter by worker name."
|
|
198
|
-
)
|
|
199
|
-
.argument("[worker]", "Worker name to filter (optional)")
|
|
200
|
-
.action(async function (this: Command, worker?: string) {
|
|
201
|
-
const fmt = getFormatOptions(this);
|
|
202
|
-
await doMonitorLogs(worker, fmt);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const ksCmd = monitorCmd
|
|
206
|
-
.command("kill-switch")
|
|
207
|
-
.summary("Emergency stop or resume trading")
|
|
208
|
-
.description(
|
|
209
|
-
`Control the trade:kill_switch KV key.
|
|
210
|
-
|
|
211
|
-
Commands:
|
|
212
|
-
show Display current kill switch status
|
|
213
|
-
on Halt all trading (set kill_switch=true)
|
|
214
|
-
off Resume trading (set kill_switch=false)`
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
ksCmd
|
|
218
|
-
.command("show")
|
|
219
|
-
.summary("Show kill switch status")
|
|
220
|
-
.description("Display whether trading is halted or active.")
|
|
221
|
-
.action(async function (this: Command) {
|
|
222
|
-
const fmt = getFormatOptions(this);
|
|
223
|
-
await doMonitorKillSwitch("show", fmt);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
ksCmd
|
|
227
|
-
.command("on")
|
|
228
|
-
.summary("Halt all trading")
|
|
229
|
-
.description(
|
|
230
|
-
"Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately."
|
|
231
|
-
)
|
|
232
|
-
.action(async function (this: Command) {
|
|
233
|
-
const fmt = getFormatOptions(this);
|
|
234
|
-
await doMonitorKillSwitch("on", fmt);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
ksCmd
|
|
238
|
-
.command("off")
|
|
239
|
-
.summary("Resume trading")
|
|
240
|
-
.description(
|
|
241
|
-
"Set trade:kill_switch=false to resume normal trading operations."
|
|
242
|
-
)
|
|
243
|
-
.action(async function (this: Command) {
|
|
244
|
-
const fmt = getFormatOptions(this);
|
|
245
|
-
await doMonitorKillSwitch("off", fmt);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
monitorCmd
|
|
249
|
-
.command("queue-depth")
|
|
250
|
-
.summary("Show queue details")
|
|
251
|
-
.description(
|
|
252
|
-
"List queues via wrangler queues list to show configured queues."
|
|
253
|
-
)
|
|
254
|
-
.action(async function (this: Command) {
|
|
255
|
-
const fmt = getFormatOptions(this);
|
|
256
|
-
await doMonitorQueueDepth(fmt);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
monitorCmd
|
|
260
|
-
.command("backup")
|
|
261
|
-
.summary("Export D1 database to .sql file")
|
|
262
|
-
.description(
|
|
263
|
-
"Export the D1 database to a timestamped .sql backup file via wrangler d1 export."
|
|
264
|
-
)
|
|
265
|
-
.action(async function (this: Command) {
|
|
266
|
-
const fmt = getFormatOptions(this);
|
|
267
|
-
await doMonitorBackup(fmt);
|
|
268
|
-
});
|
|
269
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { ConfigService } from "../../services/config/index.js";
|
|
2
|
-
import type { WorkerHealthResult } from "./types.js";
|
|
3
|
-
|
|
4
|
-
export class MonitorService {
|
|
5
|
-
private configService: ConfigService;
|
|
6
|
-
|
|
7
|
-
constructor(configService?: ConfigService) {
|
|
8
|
-
this.configService = configService ?? new ConfigService();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async checkAllWorkerHealth(): Promise<{
|
|
12
|
-
workers: WorkerHealthResult[];
|
|
13
|
-
healthyCount: number;
|
|
14
|
-
degradedCount: number;
|
|
15
|
-
unreachableCount: number;
|
|
16
|
-
}> {
|
|
17
|
-
await this.configService.load();
|
|
18
|
-
const global = this.configService.getGlobal();
|
|
19
|
-
const prefix = global.subdomain_prefix ?? "hoox";
|
|
20
|
-
const enabled = this.configService.listEnabledWorkers();
|
|
21
|
-
|
|
22
|
-
const results: WorkerHealthResult[] = [];
|
|
23
|
-
let healthy = 0;
|
|
24
|
-
let degraded = 0;
|
|
25
|
-
let unreachable = 0;
|
|
26
|
-
|
|
27
|
-
for (const name of enabled) {
|
|
28
|
-
const url = `https://${name}.${prefix}.workers.dev/health`;
|
|
29
|
-
try {
|
|
30
|
-
const response = await fetch(url, {
|
|
31
|
-
signal: AbortSignal.timeout(5000),
|
|
32
|
-
});
|
|
33
|
-
if (response.ok) {
|
|
34
|
-
results.push({
|
|
35
|
-
worker: name,
|
|
36
|
-
status: "healthy",
|
|
37
|
-
statusCode: response.status,
|
|
38
|
-
});
|
|
39
|
-
healthy++;
|
|
40
|
-
} else {
|
|
41
|
-
results.push({
|
|
42
|
-
worker: name,
|
|
43
|
-
status: "degraded",
|
|
44
|
-
statusCode: response.status,
|
|
45
|
-
});
|
|
46
|
-
degraded++;
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
results.push({
|
|
50
|
-
worker: name,
|
|
51
|
-
status: "unreachable",
|
|
52
|
-
error: err instanceof Error ? err.message : String(err),
|
|
53
|
-
});
|
|
54
|
-
unreachable++;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
workers: results,
|
|
60
|
-
healthyCount: healthy,
|
|
61
|
-
degradedCount: degraded,
|
|
62
|
-
unreachableCount: unreachable,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface WorkerHealthResult {
|
|
2
|
-
worker: string;
|
|
3
|
-
status: "healthy" | "degraded" | "unreachable";
|
|
4
|
-
statusCode?: number;
|
|
5
|
-
error?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface MonitorStatusResult {
|
|
9
|
-
workers: WorkerHealthResult[];
|
|
10
|
-
healthyCount: number;
|
|
11
|
-
degradedCount: number;
|
|
12
|
-
unreachableCount: number;
|
|
13
|
-
}
|