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