@jango-blockchained/hoox-cli 0.5.2 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hoox.js +3 -4
- package/dist/index.js +32545 -0
- package/package.json +10 -15
- package/src/commands/check/check-command.test.ts +0 -476
- package/src/commands/check/check-command.ts +0 -1087
- package/src/commands/check/index.ts +0 -10
- package/src/commands/check/prerequisites-command.test.ts +0 -19
- package/src/commands/check/prerequisites-command.ts +0 -97
- package/src/commands/check/types.ts +0 -103
- package/src/commands/clone/clone-command.test.ts +0 -440
- package/src/commands/clone/clone-command.ts +0 -431
- package/src/commands/clone/index.ts +0 -1
- package/src/commands/config/config-command.test.ts +0 -583
- package/src/commands/config/config-command.ts +0 -896
- package/src/commands/config/env-command.test.ts +0 -43
- package/src/commands/config/env-command.ts +0 -292
- package/src/commands/config/index.ts +0 -3
- package/src/commands/config/kv-command.test.ts +0 -14
- package/src/commands/config/kv-command.ts +0 -290
- package/src/commands/dashboard/dashboard-command.test.ts +0 -47
- package/src/commands/dashboard/dashboard-command.ts +0 -130
- package/src/commands/dashboard/index.ts +0 -1
- package/src/commands/db/db-command.test.ts +0 -21
- package/src/commands/db/db-command.ts +0 -311
- package/src/commands/db/index.ts +0 -1
- package/src/commands/deploy/deploy-command.test.ts +0 -324
- package/src/commands/deploy/deploy-command.ts +0 -1054
- package/src/commands/deploy/index.ts +0 -2
- package/src/commands/deploy/telegram-service.ts +0 -76
- package/src/commands/deploy/types.ts +0 -34
- package/src/commands/dev/dev-command.test.ts +0 -405
- package/src/commands/dev/dev-command.ts +0 -398
- package/src/commands/dev/index.ts +0 -1
- package/src/commands/disclaimer/disclaimer-command.ts +0 -21
- package/src/commands/disclaimer/index.ts +0 -1
- package/src/commands/infra/index.ts +0 -5
- package/src/commands/infra/infra-command.test.ts +0 -719
- package/src/commands/infra/infra-command.ts +0 -974
- package/src/commands/infra/types.ts +0 -23
- package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
- package/src/commands/init/cli-provisioner.ts +0 -71
- package/src/commands/init/index.ts +0 -1
- package/src/commands/init/init-command.test.ts +0 -854
- package/src/commands/init/init-command.ts +0 -597
- package/src/commands/init/types.ts +0 -23
- package/src/commands/logs/index.ts +0 -1
- package/src/commands/logs/logs-command.test.ts +0 -293
- package/src/commands/logs/logs-command.ts +0 -452
- package/src/commands/monitor/index.ts +0 -2
- package/src/commands/monitor/monitor-command.test.ts +0 -271
- package/src/commands/monitor/monitor-command.ts +0 -269
- package/src/commands/monitor/monitor-service.ts +0 -65
- package/src/commands/monitor/types.ts +0 -13
- package/src/commands/repair/index.ts +0 -2
- package/src/commands/repair/repair-command.test.ts +0 -243
- package/src/commands/repair/repair-command.ts +0 -220
- package/src/commands/repair/repair-service.ts +0 -164
- package/src/commands/repair/types.ts +0 -13
- package/src/commands/schema/index.ts +0 -1
- package/src/commands/schema/schema-command.ts +0 -137
- package/src/commands/test/index.ts +0 -2
- package/src/commands/test/test-command.test.ts +0 -318
- package/src/commands/test/test-command.ts +0 -406
- package/src/commands/tui/index.ts +0 -1
- package/src/commands/tui/tui-command.ts +0 -87
- package/src/commands/update/index.ts +0 -1
- package/src/commands/update/update-command.ts +0 -109
- package/src/commands/waf/index.ts +0 -2
- package/src/commands/waf/types.ts +0 -48
- package/src/commands/waf/waf-command.test.ts +0 -506
- package/src/commands/waf/waf-command.ts +0 -545
- package/src/index.ts +0 -261
- package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
- package/src/services/cloudflare/cloudflare-service.ts +0 -433
- package/src/services/cloudflare/index.ts +0 -2
- package/src/services/cloudflare/types.ts +0 -30
- package/src/services/config/config-service.test.ts +0 -395
- package/src/services/config/config-service.ts +0 -202
- package/src/services/config/index.ts +0 -9
- package/src/services/config/types.ts +0 -66
- package/src/services/db/db-service.test.ts +0 -63
- package/src/services/db/db-service.ts +0 -131
- package/src/services/db/index.ts +0 -1
- package/src/services/docker/docker-service.ts +0 -155
- package/src/services/docker/index.ts +0 -1
- package/src/services/env/env-service.test.ts +0 -259
- package/src/services/env/env-service.ts +0 -372
- package/src/services/env/index.ts +0 -1
- package/src/services/kv/index.ts +0 -1
- package/src/services/kv/kv-sync-service.test.ts +0 -38
- package/src/services/kv/kv-sync-service.ts +0 -243
- package/src/services/prerequisites/index.ts +0 -1
- package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
- package/src/services/prerequisites/prerequisites-service.ts +0 -377
- package/src/services/prerequisites/types.ts +0 -48
- package/src/services/schema/index.ts +0 -1
- package/src/services/schema/schema-service.ts +0 -99
- package/src/services/secrets/index.ts +0 -11
- package/src/services/secrets/secrets-service.test.ts +0 -502
- package/src/services/secrets/secrets-service.ts +0 -287
- package/src/services/secrets/types.ts +0 -50
- package/src/services/update/index.ts +0 -2
- package/src/services/update/update-service.test.ts +0 -76
- package/src/services/update/update-service.ts +0 -193
- package/src/ui/banner.ts +0 -141
- package/src/ui/index.ts +0 -8
- package/src/ui/menu.ts +0 -490
- package/src/utils/error-handler.ts +0 -62
- package/src/utils/errors.test.ts +0 -69
- package/src/utils/errors.ts +0 -24
- package/src/utils/formatters.test.ts +0 -180
- package/src/utils/formatters.ts +0 -269
- package/src/utils/git.ts +0 -134
- package/src/utils/theme.ts +0 -92
|
@@ -1,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
|
-
});
|