@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.
Files changed (48) hide show
  1. package/README.md +2 -0
  2. package/package.json +5 -3
  3. package/src/commands/check/check-command.test.ts +28 -20
  4. package/src/commands/check/prerequisites-command.ts +9 -5
  5. package/src/commands/clone/clone-command.ts +1 -10
  6. package/src/commands/config/config-command.ts +7 -11
  7. package/src/commands/config/env-command.ts +16 -35
  8. package/src/commands/config/kv-command.ts +33 -72
  9. package/src/commands/db/db-command.ts +23 -25
  10. package/src/commands/deploy/deploy-command.ts +133 -52
  11. package/src/commands/deploy/telegram-service.ts +21 -6
  12. package/src/commands/dev/dev-command.ts +7 -16
  13. package/src/commands/infra/infra-command.test.ts +2 -2
  14. package/src/commands/infra/infra-command.ts +45 -11
  15. package/src/commands/init/init-command.test.ts +2 -2
  16. package/src/commands/monitor/monitor-command.test.ts +44 -16
  17. package/src/commands/monitor/monitor-command.ts +45 -21
  18. package/src/commands/monitor/monitor-service.ts +19 -4
  19. package/src/commands/repair/repair-command.test.ts +35 -15
  20. package/src/commands/repair/repair-command.ts +39 -18
  21. package/src/commands/repair/repair-service.ts +48 -12
  22. package/src/commands/test/test-command.ts +4 -10
  23. package/src/commands/tui/index.ts +1 -0
  24. package/src/commands/tui/tui-command.ts +87 -0
  25. package/src/commands/update/index.ts +1 -0
  26. package/src/commands/update/update-command.ts +51 -0
  27. package/src/commands/waf/waf-command.test.ts +1 -1
  28. package/src/commands/waf/waf-command.ts +11 -11
  29. package/src/index.ts +52 -1
  30. package/src/services/cloudflare/cloudflare-service.test.ts +12 -8
  31. package/src/services/cloudflare/cloudflare-service.ts +8 -10
  32. package/src/services/cloudflare/types.ts +6 -5
  33. package/src/services/db/db-service.ts +8 -17
  34. package/src/services/env/env-service.test.ts +41 -15
  35. package/src/services/env/env-service.ts +276 -46
  36. package/src/services/kv/kv-sync-service.ts +117 -34
  37. package/src/services/prerequisites/prerequisites-service.ts +137 -29
  38. package/src/services/secrets/index.ts +0 -1
  39. package/src/services/secrets/secrets-service.test.ts +34 -25
  40. package/src/services/secrets/secrets-service.ts +10 -16
  41. package/src/services/secrets/types.ts +4 -11
  42. package/src/services/update/index.ts +2 -0
  43. package/src/services/update/update-service.test.ts +76 -0
  44. package/src/services/update/update-service.ts +193 -0
  45. package/src/ui/banner.ts +5 -5
  46. package/src/ui/menu.ts +6 -1
  47. package/src/utils/formatters.ts +21 -4
  48. 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", stderr: "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({ step: "Worker submodules", success: false, error: String(err) });
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"], { stdout: "pipe", stderr: "pipe" });
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: exit === 0 ? "All dependencies installed" : "bun install failed",
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"], { stdout: "pipe", stderr: "pipe" });
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 { infraOk = false; details.push("D1: ✗"); }
73
+ else {
74
+ infraOk = false;
75
+ details.push("D1: ✗");
76
+ }
62
77
  if (kvResult.ok) details.push("KV: ✓");
63
- else { infraOk = false; details.push("KV: ✗"); }
78
+ else {
79
+ infraOk = false;
80
+ details.push("KV: ✗");
81
+ }
64
82
  if (r2Result.ok) details.push("R2: ✓");
65
- else { infraOk = false; details.push("R2: ✗"); }
83
+ else {
84
+ infraOk = false;
85
+ details.push("R2: ✗");
86
+ }
66
87
  if (queueResult.ok) details.push("Queues: ✓");
67
- else { infraOk = false; details.push("Queues: ✗"); }
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({ step: "Infrastructure", success: false, error: String(err) });
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: missingCount === 0 ? `All ${totalSecrets} secrets present` : `${missingCount}/${totalSecrets} missing`,
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 { steps, allPassed: failed === 0, passedCount: passed, failedCount: failed };
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(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
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, data: output }
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, data: json.result };
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.data.split("\n").filter(Boolean);
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.data.length : 0;
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.data.totals?.threats ?? 0;
233
+ recentBlocks = analyticsResult.value.totals?.threats ?? 0;
234
234
  }
235
235
 
236
236
  const status: WafStatus = {
237
- enabled: wafResult.data.value === "on",
238
- mode: wafResult.data.value,
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.data.map((r) => ({
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.data.id },
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.data)
384
- ? (ruleResult.data[0] as unknown as CfFirewallRule)
385
- : (ruleResult.data as unknown as CfFirewallRule);
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("0.2.0")
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.data).toBe("user@example.com");
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.data.url).toBe(
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.data.url).toBe(
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.data.url).toBeUndefined();
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.data.port).toBe(8787);
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.data.port).toBe(3000);
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("my-index", 384, "euclidean");
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.data).toBe("hello world");
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.data);
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, data: stdout.trim() };
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.data);
108
+ const url = this.extractUrl(result.value);
109
109
 
110
110
  // Parse verbose output for metrics
111
- const output = result.data;
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, data: deployResult };
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, data: { port: devPort } };
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, data: stdout.trim() };
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> — discriminated union for all wrangler CLI operations.
3
- * Pattern: never throws; callers match on `ok` to handle success/error.
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 {