@jango-blockchained/hoox-cli 0.5.1 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -1144
- 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 -96
- package/src/commands/check/types.ts +0 -103
- package/src/commands/clone/clone-command.test.ts +0 -442
- 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 -897
- package/src/commands/config/env-command.test.ts +0 -43
- package/src/commands/config/env-command.ts +0 -295
- 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 -128
- 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 -312
- package/src/commands/db/index.ts +0 -1
- package/src/commands/deploy/deploy-command.test.ts +0 -304
- package/src/commands/deploy/deploy-command.ts +0 -1135
- 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 -383
- package/src/commands/dev/dev-command.ts +0 -398
- package/src/commands/dev/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 -876
- package/src/commands/init/init-command.ts +0 -593
- 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 -294
- 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 -263
- 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 -224
- package/src/commands/repair/repair-command.ts +0 -220
- package/src/commands/repair/repair-service.ts +0 -138
- package/src/commands/repair/types.ts +0 -13
- package/src/commands/test/index.ts +0 -2
- package/src/commands/test/test-command.test.ts +0 -319
- 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 -168
- 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 -548
- package/src/index.ts +0 -269
- 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 -207
- 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 -51
- 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 -236
- package/src/services/env/env-service.ts +0 -386
- 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 -234
- 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/secrets/index.ts +0 -11
- package/src/services/secrets/secrets-service.test.ts +0 -495
- 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/errors.test.ts +0 -69
- package/src/utils/errors.ts +0 -23
- package/src/utils/formatters.test.ts +0 -180
- package/src/utils/formatters.ts +0 -269
- package/src/utils/theme.ts +0 -94
package/package.json
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jango-blockchained/hoox-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@jango-blockchained/hoox-cli)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./bin/hoox.js",
|
|
7
|
-
"types": "./src/index.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./bin/hoox.js"
|
|
10
|
-
},
|
|
11
6
|
"bin": {
|
|
12
|
-
"hoox": "
|
|
13
|
-
"hx": "
|
|
7
|
+
"hoox": "bin/hoox.js",
|
|
8
|
+
"hx": "bin/hoox.js"
|
|
14
9
|
},
|
|
15
10
|
"scripts": {
|
|
16
11
|
"test": "bun test",
|
|
@@ -22,23 +17,23 @@
|
|
|
22
17
|
},
|
|
23
18
|
"dependencies": {
|
|
24
19
|
"@clack/prompts": "^1.3.0",
|
|
25
|
-
"@jango-blockchained/hoox-shared": "workspace:*",
|
|
26
20
|
"ansis": "^4.2.0",
|
|
27
21
|
"commander": "^14.0.3",
|
|
28
22
|
"jsonc-parser": "^3.3.1",
|
|
29
|
-
"wrangler": "^4.
|
|
23
|
+
"wrangler": "^4.95.0"
|
|
30
24
|
},
|
|
31
25
|
"devDependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"
|
|
34
|
-
"
|
|
26
|
+
"@jango-blockchained/hoox-shared": "^1.0.8",
|
|
27
|
+
"@types/node": "^25.8.0",
|
|
28
|
+
"bun-types": "^1.3.14",
|
|
29
|
+
"typescript": "^6.0.3"
|
|
35
30
|
},
|
|
36
31
|
"publishConfig": {
|
|
37
32
|
"access": "public"
|
|
38
33
|
},
|
|
39
34
|
"repository": {
|
|
40
35
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/jango-blockchained/hoox-setup.git"
|
|
36
|
+
"url": "git+https://github.com/jango-blockchained/hoox-setup.git"
|
|
42
37
|
},
|
|
43
38
|
"keywords": [
|
|
44
39
|
"cloudflare",
|
|
@@ -56,7 +51,7 @@
|
|
|
56
51
|
},
|
|
57
52
|
"files": [
|
|
58
53
|
"bin",
|
|
59
|
-
"
|
|
54
|
+
"dist",
|
|
60
55
|
"README.md"
|
|
61
56
|
]
|
|
62
57
|
}
|
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the check command.
|
|
3
|
-
*
|
|
4
|
-
* Uses prototype mocking on ConfigService, CloudflareService, and SecretsService
|
|
5
|
-
* to verify the command logic in isolation. @clack/prompts spinner is not
|
|
6
|
-
* mocked — it degrades gracefully in non-TTY test environments.
|
|
7
|
-
*/
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Stub service prototypes
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
import { ConfigService } from "../../services/config/config-service.js";
|
|
15
|
-
import { CloudflareService } from "../../services/cloudflare/cloudflare-service.js";
|
|
16
|
-
import { SecretsService } from "../../services/secrets/secrets-service.js";
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Stub variables — reassigned in beforeEach
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
let loadMock: ReturnType<typeof mock>;
|
|
23
|
-
let validateMock: ReturnType<typeof mock>;
|
|
24
|
-
let getGlobalMock: ReturnType<typeof mock>;
|
|
25
|
-
let listWorkersMock: ReturnType<typeof mock>;
|
|
26
|
-
let listEnabledWorkersMock: ReturnType<typeof mock>;
|
|
27
|
-
let getWorkerMock: ReturnType<typeof mock>;
|
|
28
|
-
|
|
29
|
-
let d1ListMock: ReturnType<typeof mock>;
|
|
30
|
-
let kvListMock: ReturnType<typeof mock>;
|
|
31
|
-
let r2ListMock: ReturnType<typeof mock>;
|
|
32
|
-
let queueListMock: ReturnType<typeof mock>;
|
|
33
|
-
let tailMock: ReturnType<typeof mock>;
|
|
34
|
-
let secretListMock: ReturnType<typeof mock>;
|
|
35
|
-
|
|
36
|
-
let secretsCreateMock: ReturnType<typeof mock>;
|
|
37
|
-
let checkLocalSecretsMock: ReturnType<typeof mock>;
|
|
38
|
-
let listSecretsMock: ReturnType<typeof mock>;
|
|
39
|
-
|
|
40
|
-
// Preserve originals for cleanup
|
|
41
|
-
const origLoad = ConfigService.prototype.load;
|
|
42
|
-
const origValidate = ConfigService.prototype.validate;
|
|
43
|
-
const origGetGlobal = ConfigService.prototype.getGlobal;
|
|
44
|
-
const origListWorkers = ConfigService.prototype.listWorkers;
|
|
45
|
-
const origListEnabled = ConfigService.prototype.listEnabledWorkers;
|
|
46
|
-
const origGetWorker = ConfigService.prototype.getWorker;
|
|
47
|
-
|
|
48
|
-
const origD1List = CloudflareService.prototype.d1List;
|
|
49
|
-
const origKvList = CloudflareService.prototype.kvList;
|
|
50
|
-
const origR2List = CloudflareService.prototype.r2List;
|
|
51
|
-
const origQueueList = CloudflareService.prototype.queueList;
|
|
52
|
-
const origTail = CloudflareService.prototype.tail;
|
|
53
|
-
const origSecretList = CloudflareService.prototype.secretList;
|
|
54
|
-
|
|
55
|
-
const origCreate = SecretsService.create;
|
|
56
|
-
const origCheckLocal = SecretsService.prototype.checkLocalSecrets;
|
|
57
|
-
const origListSecrets = SecretsService.prototype.listSecrets;
|
|
58
|
-
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
mock.restore();
|
|
61
|
-
|
|
62
|
-
// Reset process.exitCode between tests
|
|
63
|
-
process.exitCode = undefined;
|
|
64
|
-
|
|
65
|
-
// Reset to originals on prototypes
|
|
66
|
-
ConfigService.prototype.load = origLoad;
|
|
67
|
-
ConfigService.prototype.validate = origValidate;
|
|
68
|
-
ConfigService.prototype.getGlobal = origGetGlobal;
|
|
69
|
-
ConfigService.prototype.listWorkers = origListWorkers;
|
|
70
|
-
ConfigService.prototype.listEnabledWorkers = origListEnabled;
|
|
71
|
-
ConfigService.prototype.getWorker = origGetWorker;
|
|
72
|
-
|
|
73
|
-
CloudflareService.prototype.d1List = origD1List;
|
|
74
|
-
CloudflareService.prototype.kvList = origKvList;
|
|
75
|
-
CloudflareService.prototype.r2List = origR2List;
|
|
76
|
-
CloudflareService.prototype.queueList = origQueueList;
|
|
77
|
-
CloudflareService.prototype.tail = origTail;
|
|
78
|
-
CloudflareService.prototype.secretList = origSecretList;
|
|
79
|
-
|
|
80
|
-
// Fresh mocks
|
|
81
|
-
loadMock = mock(async () => ({}));
|
|
82
|
-
validateMock = mock(() => ({ valid: true, errors: [] }));
|
|
83
|
-
getGlobalMock = mock(() => ({
|
|
84
|
-
cloudflare_account_id: "abc123",
|
|
85
|
-
}));
|
|
86
|
-
listWorkersMock = mock(() => ["d1-worker", "hoox", "trade-worker"]);
|
|
87
|
-
listEnabledWorkersMock = mock(() => ["d1-worker", "hoox", "trade-worker"]);
|
|
88
|
-
getWorkerMock = mock((_name: string) => ({
|
|
89
|
-
enabled: true,
|
|
90
|
-
path: "workers/test-worker",
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
d1ListMock = mock(async () => ({
|
|
94
|
-
ok: true as const,
|
|
95
|
-
data: "my-database (abc-123)",
|
|
96
|
-
}));
|
|
97
|
-
kvListMock = mock(async () => ({
|
|
98
|
-
ok: true as const,
|
|
99
|
-
data: "namespace-1 (id1)\nnamespace-2 (id2)",
|
|
100
|
-
}));
|
|
101
|
-
r2ListMock = mock(async () => ({
|
|
102
|
-
ok: true as const,
|
|
103
|
-
data: "bucket-1\nbucket-2",
|
|
104
|
-
}));
|
|
105
|
-
queueListMock = mock(async () => ({
|
|
106
|
-
ok: true as const,
|
|
107
|
-
data: "queue-1\nqueue-2",
|
|
108
|
-
}));
|
|
109
|
-
tailMock = mock(async () => ({
|
|
110
|
-
ok: true as const,
|
|
111
|
-
data: "Connected to worker",
|
|
112
|
-
}));
|
|
113
|
-
secretListMock = mock(async () => ({
|
|
114
|
-
ok: true as const,
|
|
115
|
-
data: "Secret names: API_KEY, DB_PASSWORD",
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
secretsCreateMock = mock(async () =>
|
|
119
|
-
Object.create(SecretsService.prototype, {
|
|
120
|
-
checkLocalSecrets: {
|
|
121
|
-
value: mock(async () => ({ allSet: true, missing: [], secrets: [] })),
|
|
122
|
-
},
|
|
123
|
-
listSecrets: { value: mock(() => []) },
|
|
124
|
-
})
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
checkLocalSecretsMock = mock(async () => ({
|
|
128
|
-
worker: "test-worker",
|
|
129
|
-
secrets: [],
|
|
130
|
-
allSet: true,
|
|
131
|
-
missing: [],
|
|
132
|
-
}));
|
|
133
|
-
listSecretsMock = mock(() => ["API_KEY"]);
|
|
134
|
-
|
|
135
|
-
// Apply stubs
|
|
136
|
-
ConfigService.prototype.load = loadMock;
|
|
137
|
-
ConfigService.prototype.validate = validateMock;
|
|
138
|
-
ConfigService.prototype.getGlobal = getGlobalMock;
|
|
139
|
-
ConfigService.prototype.listWorkers = listWorkersMock;
|
|
140
|
-
ConfigService.prototype.listEnabledWorkers = listEnabledWorkersMock;
|
|
141
|
-
ConfigService.prototype.getWorker = getWorkerMock;
|
|
142
|
-
|
|
143
|
-
CloudflareService.prototype.d1List = d1ListMock;
|
|
144
|
-
CloudflareService.prototype.kvList = kvListMock;
|
|
145
|
-
CloudflareService.prototype.r2List = r2ListMock;
|
|
146
|
-
CloudflareService.prototype.queueList = queueListMock;
|
|
147
|
-
CloudflareService.prototype.tail = tailMock;
|
|
148
|
-
CloudflareService.prototype.secretList = secretListMock;
|
|
149
|
-
|
|
150
|
-
// Replace static create on SecretsService
|
|
151
|
-
(SecretsService as unknown as Record<string, unknown>).create =
|
|
152
|
-
secretsCreateMock;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
afterEach(() => {
|
|
156
|
-
mock.restore();
|
|
157
|
-
|
|
158
|
-
// Restore originals
|
|
159
|
-
ConfigService.prototype.load = origLoad;
|
|
160
|
-
ConfigService.prototype.validate = origValidate;
|
|
161
|
-
ConfigService.prototype.getGlobal = origGetGlobal;
|
|
162
|
-
ConfigService.prototype.listWorkers = origListWorkers;
|
|
163
|
-
ConfigService.prototype.listEnabledWorkers = origListEnabled;
|
|
164
|
-
ConfigService.prototype.getWorker = origGetWorker;
|
|
165
|
-
|
|
166
|
-
CloudflareService.prototype.d1List = origD1List;
|
|
167
|
-
CloudflareService.prototype.kvList = origKvList;
|
|
168
|
-
CloudflareService.prototype.r2List = origR2List;
|
|
169
|
-
CloudflareService.prototype.queueList = origQueueList;
|
|
170
|
-
CloudflareService.prototype.tail = origTail;
|
|
171
|
-
CloudflareService.prototype.secretList = origSecretList;
|
|
172
|
-
|
|
173
|
-
(SecretsService as unknown as Record<string, unknown>).create = origCreate;
|
|
174
|
-
SecretsService.prototype.checkLocalSecrets = origCheckLocal;
|
|
175
|
-
SecretsService.prototype.listSecrets = origListSecrets;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// Helpers
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Dynamically import the check command after mocks are in place.
|
|
184
|
-
*/
|
|
185
|
-
async function importCheckCommand(): Promise<{
|
|
186
|
-
registerCheckCommand: typeof import("./check-command.js").registerCheckCommand;
|
|
187
|
-
}> {
|
|
188
|
-
return import("./check-command.js");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
import { Command } from "commander";
|
|
192
|
-
import { ExitCode } from "../../utils/errors.js";
|
|
193
|
-
|
|
194
|
-
async function createProgram() {
|
|
195
|
-
const { registerCheckCommand } = await importCheckCommand();
|
|
196
|
-
const program = new Command()
|
|
197
|
-
.name("hoox-test")
|
|
198
|
-
.option("--json", "Output in JSON format")
|
|
199
|
-
.option("--quiet", "Minimal output")
|
|
200
|
-
.exitOverride(() => {
|
|
201
|
-
// Suppress Commander's own exit during tests
|
|
202
|
-
});
|
|
203
|
-
registerCheckCommand(program);
|
|
204
|
-
return program;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
// Tests
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
|
|
211
|
-
describe("registerCheckCommand", () => {
|
|
212
|
-
// -- Command registration -------------------------------------------------
|
|
213
|
-
|
|
214
|
-
it("registers 'check' as a command on the program", async () => {
|
|
215
|
-
const program = await createProgram();
|
|
216
|
-
const checkCmd = program.commands.find((c) => c.name() === "check");
|
|
217
|
-
expect(checkCmd).toBeDefined();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("registers 'check setup' subcommand", async () => {
|
|
221
|
-
const program = await createProgram();
|
|
222
|
-
const checkCmd = program.commands.find((c) => c.name() === "check")!;
|
|
223
|
-
const setupCmd = checkCmd.commands.find((c) => c.name() === "setup");
|
|
224
|
-
expect(setupCmd).toBeDefined();
|
|
225
|
-
expect(setupCmd!.description()).toContain("validation");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("registers 'check health' subcommand", async () => {
|
|
229
|
-
const program = await createProgram();
|
|
230
|
-
const checkCmd = program.commands.find((c) => c.name() === "check")!;
|
|
231
|
-
const healthCmd = checkCmd.commands.find((c) => c.name() === "health");
|
|
232
|
-
expect(healthCmd).toBeDefined();
|
|
233
|
-
expect(healthCmd!.description()).toContain("health");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("registers 'check fix' subcommand", async () => {
|
|
237
|
-
const program = await createProgram();
|
|
238
|
-
const checkCmd = program.commands.find((c) => c.name() === "check")!;
|
|
239
|
-
const fixCmd = checkCmd.commands.find((c) => c.name() === "fix");
|
|
240
|
-
expect(fixCmd).toBeDefined();
|
|
241
|
-
expect(fixCmd!.description()).toContain("Repair");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// -- check setup ----------------------------------------------------------
|
|
245
|
-
|
|
246
|
-
describe("check setup", () => {
|
|
247
|
-
it(
|
|
248
|
-
"loads config and runs all 4 categories",
|
|
249
|
-
{ timeout: 30000 },
|
|
250
|
-
async () => {
|
|
251
|
-
const program = await createProgram();
|
|
252
|
-
await program.parseAsync(["check", "setup"], { from: "user" });
|
|
253
|
-
|
|
254
|
-
expect(loadMock).toHaveBeenCalled();
|
|
255
|
-
expect(d1ListMock).toHaveBeenCalled();
|
|
256
|
-
expect(kvListMock).toHaveBeenCalled();
|
|
257
|
-
expect(r2ListMock).toHaveBeenCalled();
|
|
258
|
-
expect(queueListMock).toHaveBeenCalled();
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
it("outputs JSON when --json flag is set", { timeout: 30000 }, async () => {
|
|
263
|
-
const program = await createProgram();
|
|
264
|
-
const logSpy = mock((..._args: unknown[]) => {});
|
|
265
|
-
const origWrite = process.stdout.write;
|
|
266
|
-
(process.stdout as unknown as Record<string, unknown>).write = logSpy;
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
await program.parseAsync(["--json", "check", "setup"], {
|
|
270
|
-
from: "user",
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Should have called stdout.write with JSON
|
|
274
|
-
const jsonCalls = (
|
|
275
|
-
logSpy as unknown as { mock: { calls: Array<unknown[]> } }
|
|
276
|
-
).mock.calls.filter((call: unknown[]) => {
|
|
277
|
-
const str = String(call[0] ?? "");
|
|
278
|
-
try {
|
|
279
|
-
JSON.parse(str);
|
|
280
|
-
return str.includes("categories") && str.includes("summary");
|
|
281
|
-
} catch {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
expect(jsonCalls.length).toBeGreaterThan(0);
|
|
286
|
-
} finally {
|
|
287
|
-
(process.stdout as unknown as Record<string, unknown>).write =
|
|
288
|
-
origWrite;
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it(
|
|
293
|
-
"sets exitCode to ERROR when validation fails",
|
|
294
|
-
{ timeout: 30000 },
|
|
295
|
-
async () => {
|
|
296
|
-
validateMock = mock(() => ({
|
|
297
|
-
valid: false,
|
|
298
|
-
errors: ["global.cloudflare_account_id is required"],
|
|
299
|
-
}));
|
|
300
|
-
ConfigService.prototype.validate = validateMock;
|
|
301
|
-
|
|
302
|
-
const program = await createProgram();
|
|
303
|
-
await program.parseAsync(["check", "setup"], { from: "user" });
|
|
304
|
-
|
|
305
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
it("sets exitCode to ERROR when infra check fails", async () => {
|
|
310
|
-
d1ListMock = mock(async () => ({
|
|
311
|
-
ok: false as const,
|
|
312
|
-
error: "Authentication failed",
|
|
313
|
-
}));
|
|
314
|
-
CloudflareService.prototype.d1List = d1ListMock;
|
|
315
|
-
|
|
316
|
-
const program = await createProgram();
|
|
317
|
-
await program.parseAsync(["check", "setup"], { from: "user" });
|
|
318
|
-
|
|
319
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("handles config load failure gracefully", async () => {
|
|
323
|
-
loadMock = mock(async () => {
|
|
324
|
-
throw new Error("Config file not found");
|
|
325
|
-
});
|
|
326
|
-
ConfigService.prototype.load = loadMock;
|
|
327
|
-
|
|
328
|
-
const program = await createProgram();
|
|
329
|
-
await program.parseAsync(["check", "setup"], { from: "user" });
|
|
330
|
-
|
|
331
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// -- check health ---------------------------------------------------------
|
|
336
|
-
|
|
337
|
-
describe("check health", () => {
|
|
338
|
-
it("checks connectivity for all enabled workers", async () => {
|
|
339
|
-
const program = await createProgram();
|
|
340
|
-
await program.parseAsync(["check", "health"], { from: "user" });
|
|
341
|
-
|
|
342
|
-
expect(loadMock).toHaveBeenCalled();
|
|
343
|
-
// tailMock should have been called for each enabled worker
|
|
344
|
-
expect(tailMock).toHaveBeenCalled();
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it("outputs JSON when --json flag is set", async () => {
|
|
348
|
-
const program = await createProgram();
|
|
349
|
-
const logSpy = mock((..._args: unknown[]) => {});
|
|
350
|
-
const origWrite = process.stdout.write;
|
|
351
|
-
(process.stdout as unknown as Record<string, unknown>).write = logSpy;
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
await program.parseAsync(["--json", "check", "health"], {
|
|
355
|
-
from: "user",
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
const jsonCalls = (
|
|
359
|
-
logSpy as unknown as { mock: { calls: Array<unknown[]> } }
|
|
360
|
-
).mock.calls.filter((call: unknown[]) => {
|
|
361
|
-
const str = String(call[0] ?? "");
|
|
362
|
-
try {
|
|
363
|
-
const parsed = JSON.parse(str);
|
|
364
|
-
return Array.isArray(parsed) && parsed.length > 0;
|
|
365
|
-
} catch {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
expect(jsonCalls.length).toBeGreaterThan(0);
|
|
370
|
-
} finally {
|
|
371
|
-
(process.stdout as unknown as Record<string, unknown>).write =
|
|
372
|
-
origWrite;
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("sets exitCode to ERROR when any worker is unhealthy", async () => {
|
|
377
|
-
tailMock = mock(async () => ({
|
|
378
|
-
ok: false as const,
|
|
379
|
-
error: "Worker not reachable",
|
|
380
|
-
}));
|
|
381
|
-
CloudflareService.prototype.tail = tailMock;
|
|
382
|
-
|
|
383
|
-
const program = await createProgram();
|
|
384
|
-
await program.parseAsync(["check", "health"], { from: "user" });
|
|
385
|
-
|
|
386
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("handles empty worker list gracefully", async () => {
|
|
390
|
-
listEnabledWorkersMock = mock(() => []);
|
|
391
|
-
ConfigService.prototype.listEnabledWorkers = listEnabledWorkersMock;
|
|
392
|
-
|
|
393
|
-
const program = await createProgram();
|
|
394
|
-
await program.parseAsync(["check", "health"], { from: "user" });
|
|
395
|
-
|
|
396
|
-
// Should exit cleanly — the handler outputs success message
|
|
397
|
-
expect(process.exitCode).toBe(1);
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// -- check fix ------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
describe("check fix", () => {
|
|
404
|
-
it("scans for issues and reports when dry-run", async () => {
|
|
405
|
-
const program = await createProgram();
|
|
406
|
-
|
|
407
|
-
// Mock Bun.file so .dev.vars doesn't exist (triggers fix action)
|
|
408
|
-
const origBunFile = Bun.file;
|
|
409
|
-
(Bun as Record<string, unknown>).file = mock((_path: string) => ({
|
|
410
|
-
exists: mock(async () => false),
|
|
411
|
-
text: mock(async () => ""),
|
|
412
|
-
name: _path,
|
|
413
|
-
}));
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
await program.parseAsync(["check", "fix", "--dry-run"], {
|
|
417
|
-
from: "user",
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// Should complete without errors
|
|
421
|
-
// In dry-run mode, exitCode should not be ERROR (no actual failures to apply)
|
|
422
|
-
} finally {
|
|
423
|
-
(Bun as Record<string, unknown>).file = origBunFile;
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it("outputs JSON when --json flag is set", async () => {
|
|
428
|
-
const program = await createProgram();
|
|
429
|
-
const logSpy = mock((..._args: unknown[]) => {});
|
|
430
|
-
const origWrite = process.stdout.write;
|
|
431
|
-
(process.stdout as unknown as Record<string, unknown>).write = logSpy;
|
|
432
|
-
|
|
433
|
-
const origBunFile = Bun.file;
|
|
434
|
-
(Bun as Record<string, unknown>).file = mock((_path: string) => ({
|
|
435
|
-
exists: mock(async () => false),
|
|
436
|
-
text: mock(async () => ""),
|
|
437
|
-
name: _path,
|
|
438
|
-
}));
|
|
439
|
-
|
|
440
|
-
try {
|
|
441
|
-
await program.parseAsync(["--json", "check", "fix", "--dry-run"], {
|
|
442
|
-
from: "user",
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const jsonCalls = (
|
|
446
|
-
logSpy as unknown as { mock: { calls: Array<unknown[]> } }
|
|
447
|
-
).mock.calls.filter((call: unknown[]) => {
|
|
448
|
-
const str = String(call[0] ?? "");
|
|
449
|
-
try {
|
|
450
|
-
const parsed = JSON.parse(str);
|
|
451
|
-
return parsed.actions !== undefined && parsed.summary !== undefined;
|
|
452
|
-
} catch {
|
|
453
|
-
return false;
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
expect(jsonCalls.length).toBeGreaterThan(0);
|
|
457
|
-
} finally {
|
|
458
|
-
(process.stdout as unknown as Record<string, unknown>).write =
|
|
459
|
-
origWrite;
|
|
460
|
-
(Bun as Record<string, unknown>).file = origBunFile;
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it("handles config load failure gracefully", async () => {
|
|
465
|
-
loadMock = mock(async () => {
|
|
466
|
-
throw new Error("Config file not found");
|
|
467
|
-
});
|
|
468
|
-
ConfigService.prototype.load = loadMock;
|
|
469
|
-
|
|
470
|
-
const program = await createProgram();
|
|
471
|
-
await program.parseAsync(["check", "fix"], { from: "user" });
|
|
472
|
-
|
|
473
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
});
|