@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,406 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `hoox test` command group — run tests and CI pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* all — Run full CI pipeline (lint → typecheck → unit → integration)
|
|
6
|
-
* unit — Run bun test (unit tests)
|
|
7
|
-
* integration — Run vitest integration tests
|
|
8
|
-
* worker — Run tests for a specific worker directory
|
|
9
|
-
* live — Run live Cloudflare service tests (no mocks)
|
|
10
|
-
*/
|
|
11
|
-
import { Command } from "commander";
|
|
12
|
-
import { spinner } from "@clack/prompts";
|
|
13
|
-
import { ConfigService } from "../../services/config/index.js";
|
|
14
|
-
import { theme, icons } from "../../utils/theme.js";
|
|
15
|
-
import {
|
|
16
|
-
formatSuccess,
|
|
17
|
-
formatError,
|
|
18
|
-
formatTable,
|
|
19
|
-
formatJson,
|
|
20
|
-
getFormatOptions,
|
|
21
|
-
} from "../../utils/formatters.js";
|
|
22
|
-
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Types
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
/** Result of a single pipeline step or test run. */
|
|
29
|
-
export interface TestStepResult {
|
|
30
|
-
/** Human-readable step name (e.g. "Lint", "Unit Tests"). */
|
|
31
|
-
step: string;
|
|
32
|
-
/** The raw command string that was executed. */
|
|
33
|
-
command: string;
|
|
34
|
-
/** Whether the step exited with code 0. */
|
|
35
|
-
success: boolean;
|
|
36
|
-
/** Process exit code (null if the process failed to spawn). */
|
|
37
|
-
exitCode: number | null;
|
|
38
|
-
/** Wall-clock duration in milliseconds. */
|
|
39
|
-
duration: number;
|
|
40
|
-
/** Captured stdout (trimmed). */
|
|
41
|
-
output?: string;
|
|
42
|
-
/** Captured stderr or exception message. */
|
|
43
|
-
error?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Aggregated summary of a test pipeline run. */
|
|
47
|
-
export interface TestSummary {
|
|
48
|
-
total: number;
|
|
49
|
-
passed: number;
|
|
50
|
-
failed: number;
|
|
51
|
-
results: TestStepResult[];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Spawn a command with stdout/stderr piped, capture output, and return a
|
|
56
|
-
* structured result. Does NOT print anything — the caller decides display.
|
|
57
|
-
*/
|
|
58
|
-
export async function runStep(
|
|
59
|
-
args: string[],
|
|
60
|
-
cwd?: string
|
|
61
|
-
): Promise<TestStepResult> {
|
|
62
|
-
const command = args.join(" ");
|
|
63
|
-
const start = Date.now();
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const proc = Bun.spawn(args, {
|
|
67
|
-
cwd: cwd ?? process.cwd(),
|
|
68
|
-
stdout: "pipe",
|
|
69
|
-
stderr: "pipe",
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const exitCode = await proc.exited;
|
|
73
|
-
const stdout = await new Response(proc.stdout).text();
|
|
74
|
-
const stderr = await new Response(proc.stderr).text();
|
|
75
|
-
const duration = Date.now() - start;
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
step: command,
|
|
79
|
-
command,
|
|
80
|
-
success: exitCode === 0,
|
|
81
|
-
exitCode,
|
|
82
|
-
duration,
|
|
83
|
-
output: stdout.trim() || undefined,
|
|
84
|
-
error: stderr.trim() ? stderr.trim() : undefined,
|
|
85
|
-
};
|
|
86
|
-
} catch (err) {
|
|
87
|
-
return {
|
|
88
|
-
step: command,
|
|
89
|
-
command,
|
|
90
|
-
success: false,
|
|
91
|
-
exitCode: null,
|
|
92
|
-
duration: Date.now() - start,
|
|
93
|
-
error: err instanceof Error ? err.message : String(err),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Spawn a command with stdout/stderr inherited so the user sees live output.
|
|
100
|
-
* Returns a minimal result with exit code.
|
|
101
|
-
*/
|
|
102
|
-
async function runWithInherit(
|
|
103
|
-
args: string[],
|
|
104
|
-
cwd?: string
|
|
105
|
-
): Promise<TestStepResult> {
|
|
106
|
-
const command = args.join(" ");
|
|
107
|
-
const start = Date.now();
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const proc = Bun.spawn(args, {
|
|
111
|
-
cwd: cwd ?? process.cwd(),
|
|
112
|
-
stdout: "inherit",
|
|
113
|
-
stderr: "inherit",
|
|
114
|
-
});
|
|
115
|
-
const exitCode = await proc.exited;
|
|
116
|
-
const duration = Date.now() - start;
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
step: command,
|
|
120
|
-
command,
|
|
121
|
-
success: exitCode === 0,
|
|
122
|
-
exitCode,
|
|
123
|
-
duration,
|
|
124
|
-
};
|
|
125
|
-
} catch (err) {
|
|
126
|
-
return {
|
|
127
|
-
step: command,
|
|
128
|
-
command,
|
|
129
|
-
success: false,
|
|
130
|
-
exitCode: null,
|
|
131
|
-
duration: Date.now() - start,
|
|
132
|
-
error: err instanceof Error ? err.message : String(err),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Print a pipeline summary table (or JSON if --json is set).
|
|
139
|
-
* Quiet mode suppresses all output.
|
|
140
|
-
*/
|
|
141
|
-
export function printSummary(
|
|
142
|
-
summary: TestSummary,
|
|
143
|
-
opts: { json?: boolean; quiet?: boolean }
|
|
144
|
-
): void {
|
|
145
|
-
if (opts.quiet) return;
|
|
146
|
-
|
|
147
|
-
if (opts.json) {
|
|
148
|
-
formatJson(summary, opts);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const rows = summary.results.map((r) => ({
|
|
153
|
-
Step: r.step,
|
|
154
|
-
Status: r.success ? "passed" : "failed",
|
|
155
|
-
Time: `${r.duration}ms`,
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
formatTable(rows, opts);
|
|
159
|
-
|
|
160
|
-
// Final status line
|
|
161
|
-
if (summary.failed > 0) {
|
|
162
|
-
formatError(
|
|
163
|
-
new CLIError(
|
|
164
|
-
`${summary.passed}/${summary.total} passed — ${summary.failed} failed`,
|
|
165
|
-
ExitCode.ERROR
|
|
166
|
-
),
|
|
167
|
-
opts
|
|
168
|
-
);
|
|
169
|
-
} else {
|
|
170
|
-
formatSuccess(`All ${summary.total} steps passed`, opts);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// Pipeline step definitions for `all` subcommand
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
const PIPELINE_STEPS: { label: string; args: string[] }[] = [
|
|
179
|
-
{ label: "Lint", args: ["bun", "run", "lint"] },
|
|
180
|
-
{ label: "TypeCheck", args: ["bun", "run", "typecheck"] },
|
|
181
|
-
{ label: "Unit Tests", args: ["bun", "test", "--coverage"] },
|
|
182
|
-
{
|
|
183
|
-
label: "Integration Tests",
|
|
184
|
-
args: ["vitest", "run", "--config", "vitest.config.ts"],
|
|
185
|
-
},
|
|
186
|
-
];
|
|
187
|
-
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// Command registration
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Register the `hoox test` command group with subcommands:
|
|
194
|
-
* all, unit, integration, worker, live.
|
|
195
|
-
*/
|
|
196
|
-
export function registerTestCommand(program: Command): void {
|
|
197
|
-
const testCmd = program
|
|
198
|
-
.command("test")
|
|
199
|
-
.description("Run tests and CI pipeline");
|
|
200
|
-
|
|
201
|
-
// -- test all ----------------------------------------------------------
|
|
202
|
-
testCmd
|
|
203
|
-
.command("all")
|
|
204
|
-
.description(
|
|
205
|
-
"Run full CI pipeline: lint → typecheck → unit tests → integration tests"
|
|
206
|
-
)
|
|
207
|
-
.option("--json", "Output results as JSON")
|
|
208
|
-
.action(async (options: { json?: boolean }) => {
|
|
209
|
-
const fmt = getFormatOptions(program);
|
|
210
|
-
// Allow local --json to override global
|
|
211
|
-
const useJson = options.json || fmt.json;
|
|
212
|
-
const opts = { json: useJson, quiet: fmt.quiet };
|
|
213
|
-
|
|
214
|
-
const results: TestStepResult[] = [];
|
|
215
|
-
const s = spinner();
|
|
216
|
-
s.start("Running CI pipeline...");
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
for (const step of PIPELINE_STEPS) {
|
|
220
|
-
s.message(`${theme.info(icons.info)} ${step.label}...`);
|
|
221
|
-
const result = await runStep(step.args, process.cwd());
|
|
222
|
-
results.push({ ...result, step: step.label });
|
|
223
|
-
|
|
224
|
-
if (result.success) {
|
|
225
|
-
s.message(
|
|
226
|
-
` ${theme.success(icons.success)} ${step.label} passed (${result.duration}ms)`
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
s.message(
|
|
230
|
-
` ${theme.error(icons.error)} ${step.label} failed (${result.duration}ms)`
|
|
231
|
-
);
|
|
232
|
-
if (result.error) {
|
|
233
|
-
s.message(theme.dim(result.error.slice(0, 500)));
|
|
234
|
-
}
|
|
235
|
-
// Stop on first failure
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch (err) {
|
|
240
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
241
|
-
formatError(message, opts);
|
|
242
|
-
process.exitCode = ExitCode.ERROR;
|
|
243
|
-
s.stop("Pipeline aborted with unexpected error");
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const summary: TestSummary = {
|
|
248
|
-
total: results.length,
|
|
249
|
-
passed: results.filter((r) => r.success).length,
|
|
250
|
-
failed: results.filter((r) => !r.success).length,
|
|
251
|
-
results,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
s.stop(
|
|
255
|
-
summary.failed > 0
|
|
256
|
-
? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed`
|
|
257
|
-
: "Pipeline complete"
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
printSummary(summary, opts);
|
|
261
|
-
|
|
262
|
-
if (summary.failed > 0) {
|
|
263
|
-
process.exitCode = ExitCode.ERROR;
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// -- test unit ---------------------------------------------------------
|
|
268
|
-
testCmd
|
|
269
|
-
.command("unit")
|
|
270
|
-
.description("Run unit tests with bun test")
|
|
271
|
-
.option("--coverage", "Run with coverage reporting")
|
|
272
|
-
.action(async (options: { coverage?: boolean }) => {
|
|
273
|
-
const fmt = getFormatOptions(program);
|
|
274
|
-
const args = ["bun", "test"];
|
|
275
|
-
if (options.coverage) args.push("--coverage");
|
|
276
|
-
|
|
277
|
-
const result = await runWithInherit(args, process.cwd());
|
|
278
|
-
|
|
279
|
-
if (result.success) {
|
|
280
|
-
formatSuccess("Unit tests passed", fmt);
|
|
281
|
-
} else {
|
|
282
|
-
formatError(
|
|
283
|
-
new CLIError(
|
|
284
|
-
`Unit tests failed (exit code ${result.exitCode})`,
|
|
285
|
-
ExitCode.ERROR
|
|
286
|
-
),
|
|
287
|
-
fmt
|
|
288
|
-
);
|
|
289
|
-
process.exitCode = ExitCode.ERROR;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// -- test integration --------------------------------------------------
|
|
294
|
-
testCmd
|
|
295
|
-
.command("integration")
|
|
296
|
-
.description("Run integration tests with vitest")
|
|
297
|
-
.option("--coverage", "Run with coverage reporting")
|
|
298
|
-
.action(async (options: { coverage?: boolean }) => {
|
|
299
|
-
const fmt = getFormatOptions(program);
|
|
300
|
-
const args = ["vitest", "run", "--config", "vitest.config.ts"];
|
|
301
|
-
if (options.coverage) args.push("--coverage");
|
|
302
|
-
|
|
303
|
-
const result = await runWithInherit(args, process.cwd());
|
|
304
|
-
|
|
305
|
-
if (result.success) {
|
|
306
|
-
formatSuccess("Integration tests passed", fmt);
|
|
307
|
-
} else {
|
|
308
|
-
formatError(
|
|
309
|
-
new CLIError(
|
|
310
|
-
`Integration tests failed (exit code ${result.exitCode})`,
|
|
311
|
-
ExitCode.ERROR
|
|
312
|
-
),
|
|
313
|
-
fmt
|
|
314
|
-
);
|
|
315
|
-
process.exitCode = ExitCode.ERROR;
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// -- test worker <name> ------------------------------------------------
|
|
320
|
-
testCmd
|
|
321
|
-
.command("worker <name>")
|
|
322
|
-
.description("Run tests for a specific worker by name")
|
|
323
|
-
.option("--coverage", "Run with coverage reporting")
|
|
324
|
-
.action(async (name: string, options: { coverage?: boolean }) => {
|
|
325
|
-
const fmt = getFormatOptions(program);
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const configService = new ConfigService();
|
|
329
|
-
await configService.load();
|
|
330
|
-
const workerConfig = configService.getWorker(name);
|
|
331
|
-
|
|
332
|
-
if (!workerConfig) {
|
|
333
|
-
formatError(
|
|
334
|
-
new CLIError(
|
|
335
|
-
`Worker "${name}" not found in wrangler.jsonc`,
|
|
336
|
-
ExitCode.INVALID_USAGE
|
|
337
|
-
),
|
|
338
|
-
fmt
|
|
339
|
-
);
|
|
340
|
-
process.exitCode = ExitCode.INVALID_USAGE;
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const workerDir = `${process.cwd()}/${workerConfig.path}`;
|
|
345
|
-
const args = ["bun", "test"];
|
|
346
|
-
if (options.coverage) args.push("--coverage");
|
|
347
|
-
|
|
348
|
-
const result = await runWithInherit(args, workerDir);
|
|
349
|
-
|
|
350
|
-
if (result.success) {
|
|
351
|
-
formatSuccess(`Worker "${name}" tests passed`, fmt);
|
|
352
|
-
} else {
|
|
353
|
-
formatError(
|
|
354
|
-
new CLIError(
|
|
355
|
-
`Worker "${name}" tests failed (exit code ${result.exitCode})`,
|
|
356
|
-
ExitCode.ERROR
|
|
357
|
-
),
|
|
358
|
-
fmt
|
|
359
|
-
);
|
|
360
|
-
process.exitCode = ExitCode.ERROR;
|
|
361
|
-
}
|
|
362
|
-
} catch (err) {
|
|
363
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
364
|
-
formatError(message, fmt);
|
|
365
|
-
process.exitCode = ExitCode.ERROR;
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// -- test live ---------------------------------------------------------
|
|
370
|
-
testCmd
|
|
371
|
-
.command("live")
|
|
372
|
-
.description("Run live Cloudflare service integration tests (no mocks)")
|
|
373
|
-
.option(
|
|
374
|
-
"--service <name>",
|
|
375
|
-
"Test a specific service: d1, kv, r2, queues, ai, api, secrets, durable-objects"
|
|
376
|
-
)
|
|
377
|
-
.action(async (options: { service?: string }) => {
|
|
378
|
-
const fmt = getFormatOptions(program);
|
|
379
|
-
const s = spinner();
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
let filePattern = options.service
|
|
383
|
-
? `tests/live/${options.service}.test.ts`
|
|
384
|
-
: "tests/live/";
|
|
385
|
-
|
|
386
|
-
s.start(
|
|
387
|
-
`Running live tests${options.service ? ` for ${options.service}` : ""}...`
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
const args = ["bun", "test", filePattern, "--jobs", "1"];
|
|
391
|
-
const result = await runWithInherit(args, process.cwd());
|
|
392
|
-
|
|
393
|
-
if (result.success) {
|
|
394
|
-
s.stop("Live tests complete");
|
|
395
|
-
} else {
|
|
396
|
-
s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
|
|
397
|
-
process.exitCode = ExitCode.ERROR;
|
|
398
|
-
}
|
|
399
|
-
} catch (err) {
|
|
400
|
-
s.stop("Live tests aborted");
|
|
401
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
402
|
-
formatError(message, fmt);
|
|
403
|
-
process.exitCode = ExitCode.ERROR;
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerTUICommand } from "./tui-command.js";
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `hoox tui` command — launch the OpenTUI terminal operations center.
|
|
3
|
-
*
|
|
4
|
-
* Spawns the TUI as a child Bun process so it can take over the terminal
|
|
5
|
-
* with alternate screen mode. When the TUI exits, control returns to the CLI.
|
|
6
|
-
*/
|
|
7
|
-
import { Command } from "commander";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
import { resolve, dirname } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
12
|
-
import { theme } from "../../utils/theme.js";
|
|
13
|
-
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
14
|
-
|
|
15
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
|
|
17
|
-
/** Resolve the TUI entry point relative to the monorepo root */
|
|
18
|
-
function resolveTUIEntry(): string {
|
|
19
|
-
// Try monorepo-relative paths first
|
|
20
|
-
const candidates = [
|
|
21
|
-
resolve(__dirname, "../../../../tui/src/main.tsx"), // packages/cli → packages/tui
|
|
22
|
-
resolve(process.cwd(), "packages/tui/src/main.tsx"), // CWD = repo root
|
|
23
|
-
resolve(process.cwd(), "../tui/src/main.tsx"), // CWD = packages/cli
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
for (const path of candidates) {
|
|
27
|
-
if (existsSync(path)) return path;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
throw new CLIError(
|
|
31
|
-
"Could not find TUI entry point. Ensure packages/tui/src/main.tsx exists.",
|
|
32
|
-
ExitCode.ERROR
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function registerTUICommand(program: Command): void {
|
|
37
|
-
program
|
|
38
|
-
.command("tui")
|
|
39
|
-
.description("Launch the OpenTUI terminal operations center")
|
|
40
|
-
.option("--fps <number>", "Target frames per second", "30")
|
|
41
|
-
.option("--no-mouse", "Disable mouse support")
|
|
42
|
-
.action(async (options) => {
|
|
43
|
-
const tuiEntry = resolveTUIEntry();
|
|
44
|
-
|
|
45
|
-
console.log(
|
|
46
|
-
theme.heading("\nLaunching HOOX Terminal Operations Center...\n")
|
|
47
|
-
);
|
|
48
|
-
console.log(theme.dim(` Entry: ${tuiEntry}`));
|
|
49
|
-
console.log(theme.dim(` FPS: ${options.fps}`));
|
|
50
|
-
console.log(
|
|
51
|
-
theme.dim(` Mouse: ${options.mouse ? "enabled" : "disabled"}\n`)
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// Spawn the TUI as a child process — it takes over the terminal
|
|
55
|
-
const child = spawn("bun", ["run", tuiEntry], {
|
|
56
|
-
stdio: "inherit", // TUI gets full terminal control
|
|
57
|
-
env: {
|
|
58
|
-
...process.env,
|
|
59
|
-
TUI_FPS: options.fps,
|
|
60
|
-
TUI_MOUSE: options.mouse ? "1" : "0",
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Wait for TUI to exit
|
|
65
|
-
await new Promise<void>((resolveChild, reject) => {
|
|
66
|
-
child.on("close", (code) => {
|
|
67
|
-
if (code === 0) {
|
|
68
|
-
console.log(theme.dim("\nTUI session ended.\n"));
|
|
69
|
-
resolveChild();
|
|
70
|
-
} else if (code !== null) {
|
|
71
|
-
console.log(theme.dim(`\nTUI exited with code ${code}\n`));
|
|
72
|
-
resolveChild();
|
|
73
|
-
} else {
|
|
74
|
-
reject(
|
|
75
|
-
new CLIError("TUI process terminated abnormally", ExitCode.ERROR)
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
child.on("error", (err) => {
|
|
81
|
-
reject(
|
|
82
|
-
new CLIError(`Failed to launch TUI: ${err.message}`, ExitCode.ERROR)
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerUpdateCommand } from "./update-command.js";
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { spinner } from "@clack/prompts";
|
|
3
|
-
import { ConfigService } from "../../services/config/index.js";
|
|
4
|
-
import { UpdateService } from "../../services/update/index.js";
|
|
5
|
-
import { formatError, getFormatOptions } from "../../utils/formatters.js";
|
|
6
|
-
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
7
|
-
import { theme } from "../../utils/theme.js";
|
|
8
|
-
import {
|
|
9
|
-
gitPull,
|
|
10
|
-
gitSubmoduleUpdate,
|
|
11
|
-
isGitRepo,
|
|
12
|
-
isSubmodule,
|
|
13
|
-
} from "../../utils/git.js";
|
|
14
|
-
|
|
15
|
-
export function registerUpdateCommand(program: Command): void {
|
|
16
|
-
program
|
|
17
|
-
.command("update")
|
|
18
|
-
.summary("Sync repo from GitHub or update wrangler")
|
|
19
|
-
.description(
|
|
20
|
-
`Update your local project from GitHub or update wrangler.
|
|
21
|
-
|
|
22
|
-
Without arguments, pulls latest changes for the main repo.
|
|
23
|
-
With a worker name, updates that worker's submodule from its remote.
|
|
24
|
-
With "wrangler", updates wrangler to the latest version.
|
|
25
|
-
|
|
26
|
-
EXAMPLES:
|
|
27
|
-
hoox update Pull latest for main repo
|
|
28
|
-
hoox update d1-worker Update the d1-worker submodule
|
|
29
|
-
hoox update wrangler Update wrangler to latest version`
|
|
30
|
-
)
|
|
31
|
-
.argument(
|
|
32
|
-
"[target]",
|
|
33
|
-
'What to update: worker name (e.g. d1-worker) or "wrangler"'
|
|
34
|
-
)
|
|
35
|
-
.action(async (target?: string) => {
|
|
36
|
-
const fmt = getFormatOptions(program);
|
|
37
|
-
const cwd = process.cwd();
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
if (target === "wrangler") {
|
|
41
|
-
const service = new UpdateService();
|
|
42
|
-
const result = await service.updateWrangler();
|
|
43
|
-
|
|
44
|
-
if (result.error) {
|
|
45
|
-
formatError(
|
|
46
|
-
new CLIError(`Update failed: ${result.error}`, ExitCode.ERROR),
|
|
47
|
-
fmt
|
|
48
|
-
);
|
|
49
|
-
process.exitCode = ExitCode.ERROR;
|
|
50
|
-
}
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!(await isGitRepo(cwd))) {
|
|
55
|
-
throw new CLIError(
|
|
56
|
-
"Not a git repository — run this command from the project root",
|
|
57
|
-
ExitCode.INVALID_USAGE
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (target) {
|
|
62
|
-
const configService = new ConfigService();
|
|
63
|
-
await configService.load();
|
|
64
|
-
const worker = configService.getWorker(target);
|
|
65
|
-
|
|
66
|
-
if (!worker) {
|
|
67
|
-
throw new CLIError(
|
|
68
|
-
`Unknown worker "${target}" — not found in wrangler.jsonc`,
|
|
69
|
-
ExitCode.INVALID_USAGE
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const submodulePath = worker.path;
|
|
74
|
-
if (!(await isSubmodule(cwd, submodulePath))) {
|
|
75
|
-
throw new CLIError(
|
|
76
|
-
`"${submodulePath}" is not a git submodule — nothing to update`,
|
|
77
|
-
ExitCode.INVALID_USAGE
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const s = spinner();
|
|
82
|
-
s.start(`Updating ${target}...`);
|
|
83
|
-
|
|
84
|
-
const output = await gitSubmoduleUpdate(cwd, submodulePath);
|
|
85
|
-
|
|
86
|
-
s.stop(
|
|
87
|
-
output
|
|
88
|
-
? theme.success(`Updated ${target}`)
|
|
89
|
-
: theme.muted(`${target} already up to date`)
|
|
90
|
-
);
|
|
91
|
-
} else {
|
|
92
|
-
const s = spinner();
|
|
93
|
-
s.start("Pulling latest from remote...");
|
|
94
|
-
|
|
95
|
-
const output = await gitPull(cwd);
|
|
96
|
-
|
|
97
|
-
s.stop(
|
|
98
|
-
output
|
|
99
|
-
? theme.success("Repository up to date")
|
|
100
|
-
: theme.muted("Already up to date")
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
-
formatError(message, fmt);
|
|
106
|
-
process.exitCode = ExitCode.ERROR;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Waf command type definitions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/** WAF status summary returned by `hoox waf status`. */
|
|
6
|
-
export interface WafStatus {
|
|
7
|
-
/** Whether WAF protection is enabled on the zone. */
|
|
8
|
-
enabled: boolean;
|
|
9
|
-
/** WAF mode (e.g. "on", "off", "simulate"). */
|
|
10
|
-
mode: string;
|
|
11
|
-
/** Number of active firewall rules on the zone. */
|
|
12
|
-
activeRulesCount: number;
|
|
13
|
-
/** Approximate count of recent blocked requests (last 24h). */
|
|
14
|
-
recentBlocks: number;
|
|
15
|
-
/** Cloudflare zone ID. */
|
|
16
|
-
zoneId: string;
|
|
17
|
-
/** Cloudflare zone name (domain). */
|
|
18
|
-
zoneName: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** A Cloudflare WAF / firewall rule. */
|
|
22
|
-
export interface WafRule {
|
|
23
|
-
/** Unique rule identifier. */
|
|
24
|
-
id: string;
|
|
25
|
-
/** Human-readable description of the rule. */
|
|
26
|
-
description: string;
|
|
27
|
-
/** Rule mode: "block", "challenge", "js_challenge", "allow", "log". */
|
|
28
|
-
mode: string;
|
|
29
|
-
/** Cloudflare Filters expression that triggers the rule. */
|
|
30
|
-
expression: string;
|
|
31
|
-
/** ISO 8601 creation timestamp. */
|
|
32
|
-
created_at: string;
|
|
33
|
-
/** ISO 8601 last-modified timestamp. */
|
|
34
|
-
updated_at: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** WAF toggle mode. */
|
|
38
|
-
export type WafMode = "on" | "off";
|
|
39
|
-
|
|
40
|
-
/** Input for creating a new WAF rule. */
|
|
41
|
-
export interface WafRuleInput {
|
|
42
|
-
/** Rule type: "ip-allowlist", "ip-blocklist", "rate-limit", "custom". */
|
|
43
|
-
type: "ip-allowlist" | "ip-blocklist" | "rate-limit" | "custom";
|
|
44
|
-
/** Value associated with the rule type (IP address, requests/period, etc.). */
|
|
45
|
-
value: string;
|
|
46
|
-
/** Optional human-readable description. */
|
|
47
|
-
description?: string;
|
|
48
|
-
}
|