@jango-blockchained/hoox-cli 0.3.4 → 0.3.5
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
|
@@ -9,7 +9,8 @@ export class RepairService {
|
|
|
9
9
|
// Step 1: Repository integrity (worker submodules)
|
|
10
10
|
try {
|
|
11
11
|
const proc = Bun.spawn(["bun", "run", "check:worker-submodules"], {
|
|
12
|
-
stdout: "pipe",
|
|
12
|
+
stdout: "pipe",
|
|
13
|
+
stderr: "pipe",
|
|
13
14
|
});
|
|
14
15
|
const exit = await proc.exited;
|
|
15
16
|
steps.push({
|
|
@@ -18,17 +19,25 @@ export class RepairService {
|
|
|
18
19
|
message: exit === 0 ? "All submodules present" : "Missing submodules",
|
|
19
20
|
});
|
|
20
21
|
} catch (err) {
|
|
21
|
-
steps.push({
|
|
22
|
+
steps.push({
|
|
23
|
+
step: "Worker submodules",
|
|
24
|
+
success: false,
|
|
25
|
+
error: String(err),
|
|
26
|
+
});
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
// Step 2: Dependencies
|
|
25
30
|
try {
|
|
26
|
-
const proc = Bun.spawn(["bun", "install"], {
|
|
31
|
+
const proc = Bun.spawn(["bun", "install"], {
|
|
32
|
+
stdout: "pipe",
|
|
33
|
+
stderr: "pipe",
|
|
34
|
+
});
|
|
27
35
|
const exit = await proc.exited;
|
|
28
36
|
steps.push({
|
|
29
37
|
step: "Dependencies",
|
|
30
38
|
success: exit === 0,
|
|
31
|
-
message:
|
|
39
|
+
message:
|
|
40
|
+
exit === 0 ? "All dependencies installed" : "bun install failed",
|
|
32
41
|
});
|
|
33
42
|
} catch (err) {
|
|
34
43
|
steps.push({ step: "Dependencies", success: false, error: String(err) });
|
|
@@ -36,7 +45,10 @@ export class RepairService {
|
|
|
36
45
|
|
|
37
46
|
// Step 3: TypeScript
|
|
38
47
|
try {
|
|
39
|
-
const proc = Bun.spawn(["bun", "run", "typecheck"], {
|
|
48
|
+
const proc = Bun.spawn(["bun", "run", "typecheck"], {
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
});
|
|
40
52
|
const exit = await proc.exited;
|
|
41
53
|
steps.push({
|
|
42
54
|
step: "TypeScript",
|
|
@@ -58,13 +70,25 @@ export class RepairService {
|
|
|
58
70
|
let infraOk = true;
|
|
59
71
|
const details: string[] = [];
|
|
60
72
|
if (d1Result.ok) details.push("D1: ✓");
|
|
61
|
-
else {
|
|
73
|
+
else {
|
|
74
|
+
infraOk = false;
|
|
75
|
+
details.push("D1: ✗");
|
|
76
|
+
}
|
|
62
77
|
if (kvResult.ok) details.push("KV: ✓");
|
|
63
|
-
else {
|
|
78
|
+
else {
|
|
79
|
+
infraOk = false;
|
|
80
|
+
details.push("KV: ✗");
|
|
81
|
+
}
|
|
64
82
|
if (r2Result.ok) details.push("R2: ✓");
|
|
65
|
-
else {
|
|
83
|
+
else {
|
|
84
|
+
infraOk = false;
|
|
85
|
+
details.push("R2: ✗");
|
|
86
|
+
}
|
|
66
87
|
if (queueResult.ok) details.push("Queues: ✓");
|
|
67
|
-
else {
|
|
88
|
+
else {
|
|
89
|
+
infraOk = false;
|
|
90
|
+
details.push("Queues: ✗");
|
|
91
|
+
}
|
|
68
92
|
|
|
69
93
|
steps.push({
|
|
70
94
|
step: "Infrastructure",
|
|
@@ -72,7 +96,11 @@ export class RepairService {
|
|
|
72
96
|
message: details.join(", "),
|
|
73
97
|
});
|
|
74
98
|
} catch (err) {
|
|
75
|
-
steps.push({
|
|
99
|
+
steps.push({
|
|
100
|
+
step: "Infrastructure",
|
|
101
|
+
success: false,
|
|
102
|
+
error: String(err),
|
|
103
|
+
});
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
// Step 5: Secrets
|
|
@@ -89,7 +117,10 @@ export class RepairService {
|
|
|
89
117
|
steps.push({
|
|
90
118
|
step: "Secrets",
|
|
91
119
|
success: missingCount === 0,
|
|
92
|
-
message:
|
|
120
|
+
message:
|
|
121
|
+
missingCount === 0
|
|
122
|
+
? `All ${totalSecrets} secrets present`
|
|
123
|
+
: `${missingCount}/${totalSecrets} missing`,
|
|
93
124
|
});
|
|
94
125
|
} catch (err) {
|
|
95
126
|
steps.push({ step: "Secrets", success: false, error: String(err) });
|
|
@@ -97,6 +128,11 @@ export class RepairService {
|
|
|
97
128
|
|
|
98
129
|
const passed = steps.filter((s) => s.success).length;
|
|
99
130
|
const failed = steps.filter((s) => !s.success).length;
|
|
100
|
-
return {
|
|
131
|
+
return {
|
|
132
|
+
steps,
|
|
133
|
+
allPassed: failed === 0,
|
|
134
|
+
passedCount: passed,
|
|
135
|
+
failedCount: failed,
|
|
136
|
+
};
|
|
101
137
|
}
|
|
102
138
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
formatError,
|
|
18
18
|
formatTable,
|
|
19
19
|
formatJson,
|
|
20
|
+
getFormatOptions,
|
|
20
21
|
} from "../../utils/formatters.js";
|
|
21
22
|
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
22
23
|
|
|
@@ -50,15 +51,6 @@ export interface TestSummary {
|
|
|
50
51
|
results: TestStepResult[];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Helpers
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
function getFormatOptions(cmd: Command) {
|
|
58
|
-
const opts = cmd.optsWithGlobals();
|
|
59
|
-
return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
/**
|
|
63
55
|
* Spawn a command with stdout/stderr piped, capture output, and return a
|
|
64
56
|
* structured result. Does NOT print anything — the caller decides display.
|
|
@@ -391,7 +383,9 @@ export function registerTestCommand(program: Command): void {
|
|
|
391
383
|
? `tests/live/${options.service}.test.ts`
|
|
392
384
|
: "tests/live/";
|
|
393
385
|
|
|
394
|
-
s.start(
|
|
386
|
+
s.start(
|
|
387
|
+
`Running live tests${options.service ? ` for ${options.service}` : ""}...`
|
|
388
|
+
);
|
|
395
389
|
|
|
396
390
|
const args = ["bun", "test", filePattern, "--jobs", "1"];
|
|
397
391
|
const result = await runWithInherit(args, process.cwd());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerTUICommand } from "./tui-command.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerUpdateCommand } from "./update-command.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { UpdateService } from "../../services/update/index.js";
|
|
3
|
+
import {
|
|
4
|
+
formatSuccess,
|
|
5
|
+
formatError,
|
|
6
|
+
getFormatOptions,
|
|
7
|
+
} from "../../utils/formatters.js";
|
|
8
|
+
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
9
|
+
|
|
10
|
+
export function registerUpdateCommand(program: Command): void {
|
|
11
|
+
program
|
|
12
|
+
.command("update")
|
|
13
|
+
.summary("Update project dependencies (default: wrangler)")
|
|
14
|
+
.description(
|
|
15
|
+
`Update wrangler to the latest available version.
|
|
16
|
+
|
|
17
|
+
Updates wrangler in the project's package.json
|
|
18
|
+
via \`bun update wrangler\`.
|
|
19
|
+
|
|
20
|
+
EXAMPLES:
|
|
21
|
+
hoox update Check and update wrangler`
|
|
22
|
+
)
|
|
23
|
+
.action(async () => {
|
|
24
|
+
const fmt = getFormatOptions(program);
|
|
25
|
+
try {
|
|
26
|
+
const service = new UpdateService();
|
|
27
|
+
const result = await service.updateWrangler();
|
|
28
|
+
|
|
29
|
+
if (result.updated) {
|
|
30
|
+
formatSuccess(
|
|
31
|
+
result.newVersion
|
|
32
|
+
? `Wrangler updated to ${result.newVersion}`
|
|
33
|
+
: "Wrangler updated",
|
|
34
|
+
fmt
|
|
35
|
+
);
|
|
36
|
+
} else if (result.error) {
|
|
37
|
+
formatError(
|
|
38
|
+
new CLIError(`Update failed: ${result.error}`, ExitCode.ERROR),
|
|
39
|
+
fmt
|
|
40
|
+
);
|
|
41
|
+
process.exitCode = ExitCode.ERROR;
|
|
42
|
+
} else {
|
|
43
|
+
formatSuccess("Wrangler is already up to date", fmt);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
formatError(message, fmt);
|
|
48
|
+
process.exitCode = ExitCode.ERROR;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -30,7 +30,7 @@ const realEnv = { ...process.env };
|
|
|
30
30
|
/** Mock CloudflareService.zonesList to return a known zone. */
|
|
31
31
|
function mockZonesList(output: string, ok = true, error?: string): void {
|
|
32
32
|
const result: WranglerResult<string> = ok
|
|
33
|
-
? { ok: true,
|
|
33
|
+
? { ok: true, value: output }
|
|
34
34
|
: { ok: false, error: error ?? "zone list failed" };
|
|
35
35
|
|
|
36
36
|
spyOn(CloudflareService.prototype, "zonesList").mockImplementation(() =>
|
|
@@ -86,7 +86,7 @@ async function cfApi<T>(
|
|
|
86
86
|
return { ok: false, error: errorMsg };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
return { ok: true,
|
|
89
|
+
return { ok: true, value: json.result };
|
|
90
90
|
} catch (err) {
|
|
91
91
|
const message = err instanceof Error ? err.message : String(err);
|
|
92
92
|
return { ok: false, error: `Cloudflare API request failed: ${message}` };
|
|
@@ -176,7 +176,7 @@ async function resolveZone(
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// wrangler zones list outputs lines like "zone-name (zone-id)"
|
|
179
|
-
const lines = result.
|
|
179
|
+
const lines = result.value.split("\n").filter(Boolean);
|
|
180
180
|
if (lines.length === 0) {
|
|
181
181
|
throw new CLIError(
|
|
182
182
|
"No zones found. Set CLOUDFLARE_ZONE_ID environment variable.",
|
|
@@ -220,7 +220,7 @@ async function handleStatus(opts: FormatOptions): Promise<void> {
|
|
|
220
220
|
"GET",
|
|
221
221
|
`/zones/${zone.id}/firewall/rules?per_page=50`
|
|
222
222
|
);
|
|
223
|
-
const activeRulesCount = rulesResult.ok ? rulesResult.
|
|
223
|
+
const activeRulesCount = rulesResult.ok ? rulesResult.value.length : 0;
|
|
224
224
|
|
|
225
225
|
// Fetch recent blocks (last 24h analytics overview)
|
|
226
226
|
let recentBlocks = 0;
|
|
@@ -230,12 +230,12 @@ async function handleStatus(opts: FormatOptions): Promise<void> {
|
|
|
230
230
|
`/zones/${zone.id}/analytics/dashboard?since=${encodeURIComponent(since)}&continuous=true`
|
|
231
231
|
);
|
|
232
232
|
if (analyticsResult.ok) {
|
|
233
|
-
recentBlocks = analyticsResult.
|
|
233
|
+
recentBlocks = analyticsResult.value.totals?.threats ?? 0;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
const status: WafStatus = {
|
|
237
|
-
enabled: wafResult.
|
|
238
|
-
mode: wafResult.
|
|
237
|
+
enabled: wafResult.value.value === "on",
|
|
238
|
+
mode: wafResult.value.value,
|
|
239
239
|
activeRulesCount,
|
|
240
240
|
recentBlocks,
|
|
241
241
|
zoneId: zone.id,
|
|
@@ -278,7 +278,7 @@ async function handleRulesList(opts: FormatOptions): Promise<void> {
|
|
|
278
278
|
);
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
const rules: WafRule[] = result.
|
|
281
|
+
const rules: WafRule[] = result.value.map((r) => ({
|
|
282
282
|
id: r.id,
|
|
283
283
|
description: r.description,
|
|
284
284
|
mode: r.action,
|
|
@@ -364,7 +364,7 @@ async function handleRulesAdd(
|
|
|
364
364
|
const ruleBody = {
|
|
365
365
|
description: body.description,
|
|
366
366
|
action,
|
|
367
|
-
filter: { id: filterResult.
|
|
367
|
+
filter: { id: filterResult.value.id },
|
|
368
368
|
};
|
|
369
369
|
|
|
370
370
|
const ruleResult = await cfApi<CfFirewallRule>(
|
|
@@ -380,9 +380,9 @@ async function handleRulesAdd(
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
// Cloudflare returns an array of created rules
|
|
383
|
-
const created = Array.isArray(ruleResult.
|
|
384
|
-
? (ruleResult.
|
|
385
|
-
: (ruleResult.
|
|
383
|
+
const created = Array.isArray(ruleResult.value)
|
|
384
|
+
? (ruleResult.value[0] as unknown as CfFirewallRule)
|
|
385
|
+
: (ruleResult.value as unknown as CfFirewallRule);
|
|
386
386
|
|
|
387
387
|
formatSuccess(
|
|
388
388
|
`WAF rule added (${created.id}) — ${created.action} for "${value}"`,
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { toError } from "@jango-blockchained/hoox-shared";
|
|
10
|
+
import {
|
|
11
|
+
FULL_LEGAL_NOTICE,
|
|
12
|
+
COPYRIGHT,
|
|
13
|
+
} from "@jango-blockchained/hoox-shared/legal";
|
|
10
14
|
import { CLIError, ExitCode } from "./utils/errors.js";
|
|
11
15
|
import { formatError } from "./utils/formatters.js";
|
|
12
16
|
import { theme } from "./utils/theme.js";
|
|
@@ -22,7 +26,7 @@ program
|
|
|
22
26
|
.description(
|
|
23
27
|
"Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments"
|
|
24
28
|
)
|
|
25
|
-
.version(
|
|
29
|
+
.version(`0.2.0\n\n${COPYRIGHT}`)
|
|
26
30
|
.addHelpText(
|
|
27
31
|
"beforeAll",
|
|
28
32
|
theme.heading("\nHoox CLI — Cloudflare Workers Platform\n")
|
|
@@ -37,6 +41,7 @@ program
|
|
|
37
41
|
// Global options — accessible by all commands via program.opts()
|
|
38
42
|
program.option("--json", "Output in JSON format");
|
|
39
43
|
program.option("--quiet", "Minimal output");
|
|
44
|
+
program.option("-y, --yes", "Skip confirmation prompts");
|
|
40
45
|
|
|
41
46
|
// ---------------------------------------------------------------------------
|
|
42
47
|
// Error handling — map CommanderError and CLIError to proper exit codes
|
|
@@ -139,8 +144,21 @@ import { registerDashboardCommand } from "./commands/dashboard/index.js";
|
|
|
139
144
|
import { registerDbCommand } from "./commands/db/index.js";
|
|
140
145
|
import { registerMonitorCommand } from "./commands/monitor/index.js";
|
|
141
146
|
import { registerRepairCommand } from "./commands/repair/index.js";
|
|
147
|
+
import { registerUpdateCommand } from "./commands/update/index.js";
|
|
148
|
+
import { registerTUICommand } from "./commands/tui/index.js";
|
|
142
149
|
import { runInteractiveTUI } from "./ui/index.js";
|
|
143
150
|
|
|
151
|
+
// ── Legal / Disclaimer command ──────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
program
|
|
154
|
+
.command("disclaimer")
|
|
155
|
+
.description("Display legal disclaimers and trademark information")
|
|
156
|
+
.action(() => {
|
|
157
|
+
console.log(FULL_LEGAL_NOTICE);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ── Command registration ────────────────────────────────────────────────
|
|
161
|
+
|
|
144
162
|
registerInitCommand(program);
|
|
145
163
|
registerDevCommand(program);
|
|
146
164
|
registerDeployCommand(program);
|
|
@@ -155,6 +173,39 @@ registerDashboardCommand(program);
|
|
|
155
173
|
registerDbCommand(program);
|
|
156
174
|
registerMonitorCommand(program);
|
|
157
175
|
registerRepairCommand(program);
|
|
176
|
+
registerUpdateCommand(program);
|
|
177
|
+
registerTUICommand(program);
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// preAction hooks — auto-check wrangler version before dev/deploy
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
import { UpdateService } from "./services/update/index.js";
|
|
184
|
+
|
|
185
|
+
const devCmd = program.commands.find((c) => c.name() === "dev");
|
|
186
|
+
if (devCmd) {
|
|
187
|
+
const devStartCmd = devCmd.commands.find((c) => c.name() === "start");
|
|
188
|
+
if (devStartCmd) {
|
|
189
|
+
devStartCmd.hook("preAction", async () => {
|
|
190
|
+
const service = new UpdateService();
|
|
191
|
+
await service.checkAndPromptUpdate({ yes: program.opts().yes });
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const deployCmd = program.commands.find((c) => c.name() === "deploy");
|
|
197
|
+
if (deployCmd) {
|
|
198
|
+
const deploySubs = ["all", "workers", "worker", "dashboard"];
|
|
199
|
+
for (const sub of deploySubs) {
|
|
200
|
+
const cmd = deployCmd.commands.find((c) => c.name() === sub);
|
|
201
|
+
if (cmd) {
|
|
202
|
+
cmd.hook("preAction", async () => {
|
|
203
|
+
const service = new UpdateService();
|
|
204
|
+
await service.checkAndPromptUpdate({ yes: program.opts().yes });
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
158
209
|
|
|
159
210
|
// ---------------------------------------------------------------------------
|
|
160
211
|
// Main entry — exported so bin/hoox.js can invoke it explicitly
|
|
@@ -116,7 +116,7 @@ describe("CloudflareService", () => {
|
|
|
116
116
|
|
|
117
117
|
expect(result.ok).toBe(true);
|
|
118
118
|
if (result.ok) {
|
|
119
|
-
expect(result.
|
|
119
|
+
expect(result.value).toBe("user@example.com");
|
|
120
120
|
}
|
|
121
121
|
expect(lastSpawnCmd).toEqual(["wrangler", "whoami"]);
|
|
122
122
|
});
|
|
@@ -147,7 +147,7 @@ describe("CloudflareService", () => {
|
|
|
147
147
|
|
|
148
148
|
expect(result.ok).toBe(true);
|
|
149
149
|
if (result.ok) {
|
|
150
|
-
expect(result.
|
|
150
|
+
expect(result.value.url).toBe(
|
|
151
151
|
"https://test-worker.cryptolinx.workers.dev"
|
|
152
152
|
);
|
|
153
153
|
}
|
|
@@ -164,7 +164,7 @@ describe("CloudflareService", () => {
|
|
|
164
164
|
|
|
165
165
|
expect(result.ok).toBe(true);
|
|
166
166
|
if (result.ok) {
|
|
167
|
-
expect(result.
|
|
167
|
+
expect(result.value.url).toBe(
|
|
168
168
|
"https://test-worker.cryptolinx.workers.dev"
|
|
169
169
|
);
|
|
170
170
|
}
|
|
@@ -184,7 +184,7 @@ describe("CloudflareService", () => {
|
|
|
184
184
|
|
|
185
185
|
expect(result.ok).toBe(true);
|
|
186
186
|
if (result.ok) {
|
|
187
|
-
expect(result.
|
|
187
|
+
expect(result.value.url).toBeUndefined();
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
190
|
|
|
@@ -212,7 +212,7 @@ describe("CloudflareService", () => {
|
|
|
212
212
|
|
|
213
213
|
expect(result.ok).toBe(true);
|
|
214
214
|
if (result.ok) {
|
|
215
|
-
expect(result.
|
|
215
|
+
expect(result.value.port).toBe(8787);
|
|
216
216
|
}
|
|
217
217
|
expect(lastSpawnCmd).toEqual(["wrangler", "dev", "--port", "8787"]);
|
|
218
218
|
});
|
|
@@ -225,7 +225,7 @@ describe("CloudflareService", () => {
|
|
|
225
225
|
|
|
226
226
|
expect(result.ok).toBe(true);
|
|
227
227
|
if (result.ok) {
|
|
228
|
-
expect(result.
|
|
228
|
+
expect(result.value.port).toBe(3000);
|
|
229
229
|
}
|
|
230
230
|
expect(lastSpawnCmd).toEqual(["wrangler", "dev", "--port", "3000"]);
|
|
231
231
|
});
|
|
@@ -438,7 +438,11 @@ describe("CloudflareService", () => {
|
|
|
438
438
|
mockSpawnWithCapture(successSpawn("Created index my-index"));
|
|
439
439
|
|
|
440
440
|
const service = new CloudflareService();
|
|
441
|
-
const result = await service.vectorizeCreate(
|
|
441
|
+
const result = await service.vectorizeCreate(
|
|
442
|
+
"my-index",
|
|
443
|
+
384,
|
|
444
|
+
"euclidean"
|
|
445
|
+
);
|
|
442
446
|
|
|
443
447
|
expect(result.ok).toBe(true);
|
|
444
448
|
expect(lastSpawnCmd).toEqual([
|
|
@@ -647,7 +651,7 @@ describe("CloudflareService", () => {
|
|
|
647
651
|
|
|
648
652
|
expect(result.ok).toBe(true);
|
|
649
653
|
if (result.ok) {
|
|
650
|
-
expect(result.
|
|
654
|
+
expect(result.value).toBe("hello world");
|
|
651
655
|
}
|
|
652
656
|
});
|
|
653
657
|
});
|
|
@@ -13,7 +13,7 @@ import type { WranglerResult, DeployResult, DevResult } from "./types.js";
|
|
|
13
13
|
* const cf = new CloudflareService();
|
|
14
14
|
* const result = await cf.whoami();
|
|
15
15
|
* if (result.ok) {
|
|
16
|
-
* console.log(result.
|
|
16
|
+
* console.log(result.value);
|
|
17
17
|
* } else {
|
|
18
18
|
* console.error(result.error);
|
|
19
19
|
* }
|
|
@@ -53,7 +53,7 @@ export class CloudflareService {
|
|
|
53
53
|
const exitCode = await proc.exited;
|
|
54
54
|
|
|
55
55
|
if (exitCode === 0) {
|
|
56
|
-
return { ok: true,
|
|
56
|
+
return { ok: true, value: stdout.trim() };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
return {
|
|
@@ -105,10 +105,10 @@ export class CloudflareService {
|
|
|
105
105
|
|
|
106
106
|
// Extract the worker URL from deploy output.
|
|
107
107
|
// wrangler prints lines like: https://name.subdomain.workers.dev
|
|
108
|
-
const url = this.extractUrl(result.
|
|
108
|
+
const url = this.extractUrl(result.value);
|
|
109
109
|
|
|
110
110
|
// Parse verbose output for metrics
|
|
111
|
-
const output = result.
|
|
111
|
+
const output = result.value;
|
|
112
112
|
const deployResult: DeployResult = { url, rawOutput: output };
|
|
113
113
|
|
|
114
114
|
// Extract worker name from path
|
|
@@ -135,7 +135,7 @@ export class CloudflareService {
|
|
|
135
135
|
deployResult.versionId = versionMatch[1];
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
return { ok: true,
|
|
138
|
+
return { ok: true, value: deployResult };
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// ---------------------------------------------------------------------------
|
|
@@ -167,7 +167,7 @@ export class CloudflareService {
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
// Dev runs indefinitely — return immediately with the known port.
|
|
170
|
-
return { ok: true,
|
|
170
|
+
return { ok: true, value: { port: devPort } };
|
|
171
171
|
} catch (err) {
|
|
172
172
|
const message = err instanceof Error ? err.message : String(err);
|
|
173
173
|
return { ok: false, error: `Failed to start dev server: ${message}` };
|
|
@@ -320,9 +320,7 @@ export class CloudflareService {
|
|
|
320
320
|
* Returns a helpful error — wrangler does not support creating analytics
|
|
321
321
|
* datasets from the CLI. Users must create them via the Cloudflare Dashboard.
|
|
322
322
|
*/
|
|
323
|
-
async analyticsCreate(
|
|
324
|
-
_name: string
|
|
325
|
-
): Promise<WranglerResult<string>> {
|
|
323
|
+
async analyticsCreate(_name: string): Promise<WranglerResult<string>> {
|
|
326
324
|
return {
|
|
327
325
|
ok: false,
|
|
328
326
|
error:
|
|
@@ -372,7 +370,7 @@ export class CloudflareService {
|
|
|
372
370
|
const exitCode = await proc.exited;
|
|
373
371
|
|
|
374
372
|
if (exitCode === 0) {
|
|
375
|
-
return { ok: true,
|
|
373
|
+
return { ok: true, value: stdout.trim() };
|
|
376
374
|
}
|
|
377
375
|
|
|
378
376
|
return {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { Result } from "@jango-blockchained/hoox-shared";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* WranglerResult<T> —
|
|
3
|
-
* Pattern: never throws; callers match on `ok`
|
|
4
|
+
* WranglerResult<T> — alias for the shared Result<T> type, used for all
|
|
5
|
+
* wrangler CLI operations. Pattern: never throws; callers match on `ok`
|
|
6
|
+
* to handle success/error.
|
|
4
7
|
*/
|
|
5
|
-
export type WranglerResult<T> =
|
|
6
|
-
| { ok: true; data: T }
|
|
7
|
-
| { ok: false; error: string };
|
|
8
|
+
export type WranglerResult<T> = Result<T>;
|
|
8
9
|
|
|
9
10
|
/** Result of a wrangler deploy operation. */
|
|
10
11
|
export interface DeployResult {
|