@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.
Files changed (106) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32545 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1144
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -96
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -442
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -897
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -295
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -128
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -312
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -304
  27. package/src/commands/deploy/deploy-command.ts +0 -1135
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -383
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/infra/index.ts +0 -5
  35. package/src/commands/infra/infra-command.test.ts +0 -719
  36. package/src/commands/infra/infra-command.ts +0 -974
  37. package/src/commands/infra/types.ts +0 -23
  38. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  39. package/src/commands/init/cli-provisioner.ts +0 -71
  40. package/src/commands/init/index.ts +0 -1
  41. package/src/commands/init/init-command.test.ts +0 -876
  42. package/src/commands/init/init-command.ts +0 -593
  43. package/src/commands/init/types.ts +0 -23
  44. package/src/commands/logs/index.ts +0 -1
  45. package/src/commands/logs/logs-command.test.ts +0 -294
  46. package/src/commands/logs/logs-command.ts +0 -452
  47. package/src/commands/monitor/index.ts +0 -2
  48. package/src/commands/monitor/monitor-command.test.ts +0 -263
  49. package/src/commands/monitor/monitor-command.ts +0 -269
  50. package/src/commands/monitor/monitor-service.ts +0 -65
  51. package/src/commands/monitor/types.ts +0 -13
  52. package/src/commands/repair/index.ts +0 -2
  53. package/src/commands/repair/repair-command.test.ts +0 -224
  54. package/src/commands/repair/repair-command.ts +0 -220
  55. package/src/commands/repair/repair-service.ts +0 -138
  56. package/src/commands/repair/types.ts +0 -13
  57. package/src/commands/test/index.ts +0 -2
  58. package/src/commands/test/test-command.test.ts +0 -319
  59. package/src/commands/test/test-command.ts +0 -406
  60. package/src/commands/tui/index.ts +0 -1
  61. package/src/commands/tui/tui-command.ts +0 -87
  62. package/src/commands/update/index.ts +0 -1
  63. package/src/commands/update/update-command.ts +0 -168
  64. package/src/commands/waf/index.ts +0 -2
  65. package/src/commands/waf/types.ts +0 -48
  66. package/src/commands/waf/waf-command.test.ts +0 -506
  67. package/src/commands/waf/waf-command.ts +0 -548
  68. package/src/index.ts +0 -269
  69. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  70. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  71. package/src/services/cloudflare/index.ts +0 -2
  72. package/src/services/cloudflare/types.ts +0 -30
  73. package/src/services/config/config-service.test.ts +0 -395
  74. package/src/services/config/config-service.ts +0 -207
  75. package/src/services/config/index.ts +0 -9
  76. package/src/services/config/types.ts +0 -66
  77. package/src/services/db/db-service.test.ts +0 -51
  78. package/src/services/db/db-service.ts +0 -131
  79. package/src/services/db/index.ts +0 -1
  80. package/src/services/docker/docker-service.ts +0 -155
  81. package/src/services/docker/index.ts +0 -1
  82. package/src/services/env/env-service.test.ts +0 -236
  83. package/src/services/env/env-service.ts +0 -386
  84. package/src/services/env/index.ts +0 -1
  85. package/src/services/kv/index.ts +0 -1
  86. package/src/services/kv/kv-sync-service.test.ts +0 -38
  87. package/src/services/kv/kv-sync-service.ts +0 -234
  88. package/src/services/prerequisites/index.ts +0 -1
  89. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  90. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  91. package/src/services/prerequisites/types.ts +0 -48
  92. package/src/services/secrets/index.ts +0 -11
  93. package/src/services/secrets/secrets-service.test.ts +0 -495
  94. package/src/services/secrets/secrets-service.ts +0 -287
  95. package/src/services/secrets/types.ts +0 -50
  96. package/src/services/update/index.ts +0 -2
  97. package/src/services/update/update-service.test.ts +0 -76
  98. package/src/services/update/update-service.ts +0 -193
  99. package/src/ui/banner.ts +0 -141
  100. package/src/ui/index.ts +0 -8
  101. package/src/ui/menu.ts +0 -490
  102. package/src/utils/errors.test.ts +0 -69
  103. package/src/utils/errors.ts +0 -23
  104. package/src/utils/formatters.test.ts +0 -180
  105. package/src/utils/formatters.ts +0 -269
  106. package/src/utils/theme.ts +0 -94
@@ -1,10 +0,0 @@
1
- export { registerCheckCommand } from "./check-command.js";
2
- export { registerPrerequisitesCommand } from "./prerequisites-command.js";
3
- export type {
4
- CheckResult,
5
- CheckCategory,
6
- CheckReport,
7
- HealthCheckResult,
8
- FixAction,
9
- FixReport,
10
- } from "./types.js";
@@ -1,19 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { runPrerequisitesCheck } from "./prerequisites-command.js";
3
- import { PrerequisitesService } from "../../services/prerequisites/index.js";
4
-
5
- describe("prerequisites command", () => {
6
- describe("runPrerequisitesCheck", () => {
7
- it("runs all checks and returns a report", async () => {
8
- const svc = new PrerequisitesService();
9
- const report = await runPrerequisitesCheck(svc);
10
- expect(report.checks.length).toBeGreaterThan(0);
11
- expect(typeof report.allPassed).toBe("boolean");
12
- });
13
-
14
- it("uses a new PrerequisitesService when none provided", async () => {
15
- const report = await runPrerequisitesCheck();
16
- expect(report.checks.length).toBeGreaterThan(0);
17
- });
18
- });
19
- });
@@ -1,96 +0,0 @@
1
- import { Command } from "commander";
2
- import { PrerequisitesService } from "../../services/prerequisites/index.js";
3
- import type { PrerequisitesReport } from "../../services/prerequisites/prerequisites-service.js";
4
- import { theme, icons } from "../../utils/theme.js";
5
- import { formatError, formatJson } from "../../utils/formatters.js";
6
- import { CLIError, ExitCode } from "../../utils/errors.js";
7
- import type { FormatOptions } from "../../utils/formatters.js";
8
-
9
- export async function runPrerequisitesCheck(
10
- service?: PrerequisitesService
11
- ): Promise<PrerequisitesReport> {
12
- const svc = service ?? new PrerequisitesService();
13
- return await svc.runAll();
14
- }
15
-
16
- function renderReport(report: PrerequisitesReport): void {
17
- for (const check of report.checks) {
18
- const icon = check.passed
19
- ? theme.success(icons.success)
20
- : theme.error(icons.error);
21
- process.stdout.write(`${icon} ${check.name}\n`);
22
- process.stdout.write(` ${theme.dim("Version:")} ${check.version}\n`);
23
- process.stdout.write(` ${theme.dim("Required:")} ${check.required}\n`);
24
- if (check.hint) {
25
- process.stdout.write(
26
- ` ${theme.warning(`${icons.warning} ${check.hint}`)}\n`
27
- );
28
- }
29
- process.stdout.write("\n");
30
- }
31
-
32
- const summary = report.allPassed
33
- ? `${theme.success(icons.success)} All prerequisites met`
34
- : `${theme.error(icons.error)} Some prerequisites not met`;
35
- process.stdout.write(summary + "\n");
36
- }
37
-
38
- async function handlePrerequisites(
39
- opts: FormatOptions,
40
- tool?: string
41
- ): Promise<void> {
42
- try {
43
- const svc = new PrerequisitesService();
44
- const report = tool ? await svc.runAll(tool) : await svc.runAll();
45
-
46
- if (opts.json) {
47
- formatJson(report, opts);
48
- } else if (opts.quiet) {
49
- } else {
50
- renderReport(report);
51
- }
52
-
53
- if (!report.allPassed) {
54
- process.exitCode = ExitCode.ERROR;
55
- }
56
- } catch (err) {
57
- const message = err instanceof Error ? err.message : String(err);
58
- formatError(new CLIError(message, ExitCode.ERROR), opts);
59
- process.exitCode = ExitCode.ERROR;
60
- }
61
- }
62
-
63
- export function registerPrerequisitesCommand(parentCmd: Command): void {
64
- parentCmd
65
- .command("prerequisites")
66
- .summary("Validate toolchain and account prerequisites")
67
- .description(
68
- `Check that all required tools and accounts are set up correctly.
69
-
70
- Checks performed:
71
- 1. Bun version (>=1.2)
72
- 2. Git version (>=2.40)
73
- 3. Node.js version (>=18, advisory)
74
- 4. Wrangler CLI version (latest)
75
- 5. Cloudflare authentication
76
- 6. Docker availability (advisory)
77
- 7. Repository integrity (wrangler.jsonc, submodules)
78
-
79
- OPTIONS:
80
- --tool <name> Only check a specific tool (e.g. "bun", "git", "wrangler")
81
-
82
- EXAMPLES:
83
- hoox check prerequisites
84
- hoox check prerequisites --tool bun
85
- hoox check prerequisites --json`
86
- )
87
- .option("--tool <name>", "Only check a specific tool")
88
- .action(async (options: { tool?: string }, cmd: Command) => {
89
- const rootCmd = cmd.parent?.parent as Command | undefined;
90
- const opts: FormatOptions = {
91
- json: Boolean(rootCmd?.optsWithGlobals()?.json),
92
- quiet: Boolean(rootCmd?.optsWithGlobals()?.quiet),
93
- };
94
- await handlePrerequisites(opts, options.tool);
95
- });
96
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * `hoox check` command types — validation check results, health status,
3
- * and fix reports for the check command group.
4
- */
5
-
6
- /**
7
- * Result of a single validation check within a category.
8
- */
9
- export interface CheckResult {
10
- /** Human-readable check name (e.g. "D1 Database", "Wrangler Configs"). */
11
- name: string;
12
- /** Whether this check passed (no errors). */
13
- success: boolean;
14
- /** Failing conditions — must be addressed before deployment. */
15
- errors: string[];
16
- /** Non-blocking issues — should be reviewed. */
17
- warnings: string[];
18
- }
19
-
20
- /**
21
- * A logical group of related checks (Config, Infrastructure, Secrets, Database).
22
- */
23
- export interface CheckCategory {
24
- /** Category label displayed in the report. */
25
- name: string;
26
- /** Per-check results within this category. */
27
- checks: CheckResult[];
28
- }
29
-
30
- /**
31
- * Top-level report produced by `hoox check setup`.
32
- * Rendered as a table (human mode) or JSON (--json mode).
33
- */
34
- export interface CheckReport {
35
- /** True when every check in every category passed (zero errors). */
36
- success: boolean;
37
- /** Grouped category results. */
38
- categories: CheckCategory[];
39
- /** Aggregated counts across all checks. */
40
- summary: {
41
- total: number;
42
- passed: number;
43
- failed: number;
44
- warnings: number;
45
- };
46
- }
47
-
48
- // ---------------------------------------------------------------------------
49
- // Health check types
50
- // ---------------------------------------------------------------------------
51
-
52
- /**
53
- * Status of a single worker after a health probe.
54
- */
55
- export interface HealthCheckResult {
56
- /** Worker name as defined in wrangler.jsonc. */
57
- worker: string;
58
- /** Categorised health status. */
59
- status: "healthy" | "degraded" | "down";
60
- /** Whether `wrangler tail` successfully connected to the worker. */
61
- connectivity: boolean;
62
- /** Approximate tail response time in ms (if measurable). */
63
- responseTime?: number;
64
- /** Human-readable error when status is "degraded" or "down". */
65
- error?: string;
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // Fix / repair types
70
- // ---------------------------------------------------------------------------
71
-
72
- /**
73
- * A single fix action to be applied (or simulated in --dry-run) by `hoox check fix`.
74
- */
75
- export interface FixAction {
76
- description: string;
77
- type: "file" | "binding" | "flag" | "config";
78
- /** Target path or identifier (e.g. worker path, binding name). */
79
- target: string;
80
- /** Textual description of the exact change to apply. */
81
- change: string;
82
- /** Whether the fix was successfully applied (always false in --dry-run). */
83
- applied: boolean;
84
- /** Error message if application failed. */
85
- error?: string;
86
- }
87
-
88
- /**
89
- * Report produced by `hoox check fix` summarising all fix actions.
90
- */
91
- export interface FixReport {
92
- /** Ordered list of fix actions. */
93
- actions: FixAction[];
94
- /** Whether this was a dry-run (no filesystem writes). */
95
- dryRun: boolean;
96
- /** Aggregated counts. */
97
- summary: {
98
- total: number;
99
- applied: number;
100
- skipped: number;
101
- failed: number;
102
- };
103
- }
@@ -1,442 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * Unit tests for the clone command.
4
- *
5
- * Uses temp directories with real wrangler.jsonc and .git directories so
6
- * ConfigService operates normally. Only Bun.spawn (git subcommands) is
7
- * mocked to keep tests fast and side-effect free.
8
- */
9
- import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
10
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
- import { Command } from "commander";
14
- import { registerCloneCommand } from "./clone-command.js";
15
-
16
- // ---------------------------------------------------------------------------
17
- // Bun.spawn mocking (same pattern as cloudflare-service.test.ts)
18
- // ---------------------------------------------------------------------------
19
-
20
- type MockSpawnResult = {
21
- stdout: Blob;
22
- stderr: Blob;
23
- exited: Promise<number>;
24
- stdin?: { write: ReturnType<typeof mock>; end: ReturnType<typeof mock> };
25
- kill: ReturnType<typeof mock>;
26
- };
27
-
28
- const realSpawn = Bun.spawn;
29
- let origCwd: string;
30
- let lastSpawnCmd: string[] = [];
31
-
32
- function successSpawn(stdout = ""): MockSpawnResult {
33
- return {
34
- stdout: new Blob([stdout]),
35
- stderr: new Blob([""]),
36
- exited: Promise.resolve(0),
37
- stdin: { write: mock(() => {}), end: mock(() => {}) },
38
- kill: mock(() => {}),
39
- };
40
- }
41
-
42
- function errorSpawn(stderr = "", exitCode = 1): MockSpawnResult {
43
- return {
44
- stdout: new Blob([""]),
45
- stderr: new Blob([stderr]),
46
- exited: Promise.resolve(exitCode),
47
- stdin: { write: mock(() => {}), end: mock(() => {}) },
48
- kill: mock(() => {}),
49
- };
50
- }
51
-
52
- /** Queue of spawn results to return in order. */
53
- let spawnQueue: MockSpawnResult[] = [];
54
-
55
- function enqueueSpawn(result: MockSpawnResult): void {
56
- spawnQueue.push(result);
57
- }
58
-
59
- function installSpawnMock(): void {
60
- lastSpawnCmd = [];
61
- const spawnMock = mock((cmd: string[], _opts?: { cwd?: string }) => {
62
- lastSpawnCmd = [...cmd];
63
- return spawnQueue.shift() ?? errorSpawn("unexpected spawn call", 127);
64
- });
65
- (Bun as Record<string, unknown>).spawn = spawnMock;
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // Test helpers
70
- // ---------------------------------------------------------------------------
71
-
72
- let tmpDir: string;
73
-
74
- /** Write a minimal wrangler.jsonc into tmpDir for ConfigService to load. */
75
- async function writeTestConfig(
76
- workers: Record<string, { enabled: boolean; path: string }>
77
- ): Promise<string> {
78
- const content = JSON.stringify({
79
- global: { cloudflare_account_id: "test-account" },
80
- workers,
81
- });
82
- await Bun.write(join(tmpDir, "wrangler.jsonc"), content);
83
- return join(tmpDir, "wrangler.jsonc");
84
- }
85
-
86
- /** Create a real .git file to simulate an already-cloned git submodule. */
87
- function markCloned(workerName: string, workerPath: string): void {
88
- const workerDir = join(tmpDir, workerPath);
89
- mkdirSync(workerDir, { recursive: true });
90
- // Git submodules use a .git FILE (not directory) pointing to superproject
91
- writeFileSync(
92
- join(workerDir, ".git"),
93
- "gitdir: ../.git/modules/" + workerName
94
- );
95
- }
96
-
97
- /** Build a fresh commander program with the clone command registered. */
98
- function buildProgram(): Command {
99
- const program = new Command();
100
- program.name("hoox");
101
- program.option("--json", "JSON output");
102
- program.option("--quiet", "Minimal output");
103
- program.exitOverride();
104
- registerCloneCommand(program);
105
- return program;
106
- }
107
-
108
- /** Capture stdout during program.parseAsync, chdir'd to tmpDir first. */
109
- async function captureStdout(
110
- program: Command,
111
- args: string[]
112
- ): Promise<string> {
113
- const origCwd = process.cwd();
114
- process.chdir(tmpDir);
115
-
116
- let output = "";
117
- const orig = process.stdout.write.bind(process.stdout);
118
- (process.stdout as Record<string, unknown>).write = mock((chunk: string) => {
119
- output += chunk;
120
- return true;
121
- });
122
-
123
- try {
124
- await program.parseAsync([...args], { from: "user" });
125
- } catch {
126
- // exitOverride throws CommanderError — swallow
127
- }
128
-
129
- (process.stdout as Record<string, unknown>).write = orig;
130
- process.chdir(origCwd);
131
- return output;
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // Setup / Teardown
136
- // ---------------------------------------------------------------------------
137
-
138
- beforeEach(() => {
139
- tmpDir = mkdtempSync(join(tmpdir(), "hoox-clone-test-"));
140
- origCwd = process.cwd();
141
- spawnQueue = [];
142
- installSpawnMock();
143
- });
144
-
145
- afterEach(() => {
146
- (Bun as Record<string, unknown>).spawn = realSpawn;
147
- process.chdir(origCwd);
148
- rmSync(tmpDir, { recursive: true, force: true });
149
- mock.restore();
150
- });
151
-
152
- // ---------------------------------------------------------------------------
153
- // Tests
154
- // ---------------------------------------------------------------------------
155
-
156
- describe("registerCloneCommand", () => {
157
- // -- Registration ----------------------------------------------------------
158
-
159
- it("registers the clone command on the program", () => {
160
- const program = buildProgram();
161
- const names = program.commands.map((c) => c.name());
162
- expect(names).toContain("clone");
163
- });
164
-
165
- it("clone command has --all and --org options", () => {
166
- const program = buildProgram();
167
- const cmd = program.commands.find((c) => c.name() === "clone");
168
- expect(cmd).toBeDefined();
169
- const opts = cmd!.options.map((o) => o.long);
170
- expect(opts).toContain("--all");
171
- expect(opts).toContain("--org");
172
- });
173
-
174
- it("clone command accepts optional [name] argument", () => {
175
- const program = buildProgram();
176
- const cmd = program.commands.find((c) => c.name() === "clone");
177
- expect(cmd).toBeDefined();
178
- const args = cmd!.registeredArguments;
179
- expect(args).toHaveLength(1);
180
- expect(args[0].name()).toBe("name");
181
- expect(args[0].required).toBe(false);
182
- });
183
-
184
- // -- No-arg mode: list clone status ---------------------------------------
185
-
186
- describe("no-arg mode (list status)", () => {
187
- it("lists workers with clone status in table format", async () => {
188
- // git remote detection
189
- enqueueSpawn(successSpawn("https://github.com/test-org/hoox-setup.git"));
190
-
191
- await writeTestConfig({
192
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
193
- hoox: { enabled: true, path: "workers/hoox" },
194
- });
195
-
196
- const program = buildProgram();
197
- const output = await captureStdout(program, ["clone"]);
198
-
199
- expect(output).toContain("d1-worker");
200
- expect(output).toContain("hoox");
201
- expect(output).toContain("test-org");
202
- });
203
-
204
- it("marks already cloned workers", async () => {
205
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
206
- await writeTestConfig({
207
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
208
- "trade-worker": { enabled: true, path: "workers/trade-worker" },
209
- });
210
- markCloned("d1-worker", "workers/d1-worker");
211
-
212
- const program = buildProgram();
213
- const output = await captureStdout(program, ["clone"]);
214
-
215
- // d1-worker should be "cloned", trade-worker should be "not cloned"
216
- expect(output).toContain("cloned");
217
- expect(output).toContain("not cloned");
218
- });
219
-
220
- it("supports --json flag for machine-readable output", async () => {
221
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
222
-
223
- await writeTestConfig({
224
- hoox: { enabled: true, path: "workers/hoox" },
225
- });
226
-
227
- const program = buildProgram();
228
- const output = await captureStdout(program, ["--json", "clone"]);
229
-
230
- const parsed = JSON.parse(output.trim());
231
- expect(Array.isArray(parsed)).toBe(true);
232
- expect(parsed[0]).toHaveProperty("worker");
233
- expect(parsed[0]).toHaveProperty("cloned");
234
- expect(parsed[0]).toHaveProperty("repo");
235
- expect(parsed[0].worker).toBe("hoox");
236
- });
237
-
238
- it("supports --quiet flag with simple line-per-worker output", async () => {
239
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
240
-
241
- await writeTestConfig({
242
- hoox: { enabled: true, path: "workers/hoox" },
243
- });
244
-
245
- const program = buildProgram();
246
- const output = await captureStdout(program, ["--quiet", "clone"]);
247
-
248
- expect(output).toContain("hoox: not cloned");
249
- expect(output).not.toContain("┌"); // no table borders
250
- });
251
- });
252
-
253
- // -- --all mode ------------------------------------------------------------
254
-
255
- describe("--all mode", () => {
256
- it("clones all workers and prints summary table", async () => {
257
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
258
- // git submodule add for d1-worker
259
- enqueueSpawn(successSpawn());
260
- // git submodule add for hoox
261
- enqueueSpawn(successSpawn());
262
- // git submodule update
263
- enqueueSpawn(successSpawn());
264
-
265
- await writeTestConfig({
266
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
267
- hoox: { enabled: true, path: "workers/hoox" },
268
- });
269
-
270
- const program = buildProgram();
271
- const output = await captureStdout(program, ["clone", "--all"]);
272
-
273
- // Summary table is printed to stdout
274
- expect(output).toContain("d1-worker");
275
- expect(output).toContain("hoox");
276
- expect(output).toContain("cloned");
277
- });
278
-
279
- it("skips already cloned workers", async () => {
280
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
281
- // Only hoox needs cloning
282
- enqueueSpawn(successSpawn());
283
- enqueueSpawn(successSpawn());
284
-
285
- await writeTestConfig({
286
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
287
- hoox: { enabled: true, path: "workers/hoox" },
288
- });
289
- markCloned("d1-worker", "workers/d1-worker"); // already cloned
290
-
291
- const program = buildProgram();
292
- const output = await captureStdout(program, ["clone", "--all"]);
293
-
294
- // Both workers appear in the table as "cloned"
295
- expect(output).toContain("d1-worker");
296
- expect(output).toContain("hoox");
297
- });
298
-
299
- it("reports failures when some clones fail", async () => {
300
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
301
- // d1-worker succeeds
302
- enqueueSpawn(successSpawn());
303
- // hoox fails
304
- enqueueSpawn(errorSpawn("repository not found", 128));
305
- // submodule update (still runs)
306
- enqueueSpawn(successSpawn());
307
-
308
- await writeTestConfig({
309
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
310
- hoox: { enabled: true, path: "workers/hoox" },
311
- });
312
-
313
- const program = buildProgram();
314
- const output = await captureStdout(program, ["clone", "--all"]);
315
-
316
- // Table shows both cloned and failed
317
- expect(output).toContain("d1-worker");
318
- expect(output).toContain("hoox");
319
- expect(output).toContain("failed");
320
- });
321
-
322
- it("handles empty worker list", async () => {
323
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
324
-
325
- await writeTestConfig({});
326
-
327
- const program = buildProgram();
328
- const output = await captureStdout(program, ["clone", "--all"]);
329
-
330
- expect(output).toContain("No workers");
331
- });
332
- });
333
-
334
- // -- <name> mode (single worker) -------------------------------------------
335
-
336
- describe("<name> mode (single worker)", () => {
337
- it("clones a specific worker by name", async () => {
338
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
339
- enqueueSpawn(successSpawn());
340
- enqueueSpawn(successSpawn());
341
-
342
- await writeTestConfig({
343
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
344
- });
345
-
346
- const program = buildProgram();
347
- const output = await captureStdout(program, ["clone", "d1-worker"]);
348
-
349
- expect(output).toContain("Cloned d1-worker");
350
- expect(output).toContain("org");
351
- });
352
-
353
- it("validates worker name exists in config", async () => {
354
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
355
-
356
- await writeTestConfig({
357
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
358
- });
359
-
360
- const program = buildProgram();
361
- const output = await captureStdout(program, ["clone", "nonexistent"]);
362
-
363
- expect(output).toContain("not found");
364
- });
365
-
366
- it("exits early when worker is already cloned", async () => {
367
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
368
- await writeTestConfig({
369
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
370
- });
371
- markCloned("d1-worker", "workers/d1-worker");
372
-
373
- const program = buildProgram();
374
- const output = await captureStdout(program, ["clone", "d1-worker"]);
375
-
376
- expect(output).toContain("already cloned");
377
- });
378
-
379
- it("reports clone failure", async () => {
380
- enqueueSpawn(successSpawn("https://github.com/org/repo.git"));
381
- enqueueSpawn(errorSpawn("fatal: could not read from remote", 128));
382
-
383
- await writeTestConfig({
384
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
385
- });
386
-
387
- const program = buildProgram();
388
- const output = await captureStdout(program, ["clone", "d1-worker"]);
389
-
390
- expect(output).toContain("Failed to clone");
391
- });
392
- });
393
-
394
- // -- --org option ----------------------------------------------------------
395
-
396
- describe("--org option", () => {
397
- it("uses --org value for the repo URL", async () => {
398
- // No git remote call needed since --org is explicit
399
- enqueueSpawn(successSpawn());
400
- enqueueSpawn(successSpawn());
401
-
402
- await writeTestConfig({
403
- hoox: { enabled: true, path: "workers/hoox" },
404
- });
405
-
406
- const program = buildProgram();
407
- const output = await captureStdout(program, [
408
- "clone",
409
- "hoox",
410
- "--org",
411
- "custom-org",
412
- ]);
413
-
414
- expect(output).toContain("custom-org");
415
- });
416
- });
417
-
418
- // -- Error handling --------------------------------------------------------
419
-
420
- describe("error handling", () => {
421
- it("handles missing wrangler.jsonc gracefully", async () => {
422
- // Don't write a config file — ConfigService.load() will fail
423
- const program = buildProgram();
424
- const output = await captureStdout(program, ["clone"]);
425
-
426
- expect(output).toContain("not found");
427
- });
428
-
429
- it("falls back to default org when git remote fails", async () => {
430
- enqueueSpawn(errorSpawn("fatal: not a git repository", 128));
431
-
432
- await writeTestConfig({
433
- "d1-worker": { enabled: true, path: "workers/d1-worker" },
434
- });
435
-
436
- const program = buildProgram();
437
- const output = await captureStdout(program, ["clone"]);
438
-
439
- expect(output).toContain("jango-blockchained");
440
- });
441
- });
442
- });