@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,137 +0,0 @@
|
|
|
1
|
-
import { resolve } from "path";
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { SchemaService } from "../../services/schema/schema-service.js";
|
|
4
|
-
import {
|
|
5
|
-
formatSuccess,
|
|
6
|
-
formatError,
|
|
7
|
-
formatKeyValue,
|
|
8
|
-
formatTable,
|
|
9
|
-
getFormatOptions,
|
|
10
|
-
} from "../../utils/formatters.js";
|
|
11
|
-
import { CLIError, ExitCode } from "../../utils/errors.js";
|
|
12
|
-
import { withErrorHandling } from "../../utils/error-handler.js";
|
|
13
|
-
import {
|
|
14
|
-
generateWranglerJsonc,
|
|
15
|
-
generateDevVars,
|
|
16
|
-
} from "@jango-blockchained/hoox-shared";
|
|
17
|
-
|
|
18
|
-
export function registerSchemaCommand(program: Command): void {
|
|
19
|
-
const schemaCmd = program
|
|
20
|
-
.command("schema")
|
|
21
|
-
.summary("Validate and manage worker configuration")
|
|
22
|
-
.description(
|
|
23
|
-
"Validate, generate, and repair worker wrangler.jsonc against canonical manifests."
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
schemaCmd
|
|
27
|
-
.command("validate [worker]")
|
|
28
|
-
.description(
|
|
29
|
-
"Validate worker(s) against manifest (omit worker to validate all)"
|
|
30
|
-
)
|
|
31
|
-
.action(
|
|
32
|
-
withErrorHandling(async (worker?: string) => {
|
|
33
|
-
const fmt = getFormatOptions(schemaCmd);
|
|
34
|
-
const svc = new SchemaService();
|
|
35
|
-
const results = worker
|
|
36
|
-
? [svc.validateWorker(worker)]
|
|
37
|
-
: svc.validateAll();
|
|
38
|
-
let totalErrors = 0;
|
|
39
|
-
for (const r of results) {
|
|
40
|
-
if (r.passed) {
|
|
41
|
-
formatSuccess(`${r.worker}: ✅ passed`, fmt);
|
|
42
|
-
} else {
|
|
43
|
-
formatError(
|
|
44
|
-
new CLIError(
|
|
45
|
-
`${r.worker}: ❌ failed (${r.errors.length} issues)`,
|
|
46
|
-
ExitCode.ERROR
|
|
47
|
-
),
|
|
48
|
-
fmt
|
|
49
|
-
);
|
|
50
|
-
for (const e of r.errors.filter((e) => e.severity === "error")) {
|
|
51
|
-
process.stderr.write(` ✗ ${e.message}\n`);
|
|
52
|
-
}
|
|
53
|
-
totalErrors += r.errors.filter(
|
|
54
|
-
(e) => e.severity === "error"
|
|
55
|
-
).length;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (totalErrors > 0) process.exitCode = ExitCode.ERROR;
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
schemaCmd
|
|
63
|
-
.command("list")
|
|
64
|
-
.description("List all workers with their binding counts")
|
|
65
|
-
.action(
|
|
66
|
-
withErrorHandling(async () => {
|
|
67
|
-
const fmt = getFormatOptions(schemaCmd);
|
|
68
|
-
const svc = new SchemaService();
|
|
69
|
-
const rows: Record<string, string>[] = [];
|
|
70
|
-
for (const name of svc.getWorkerNames()) {
|
|
71
|
-
const m = svc.getManifest(name)!;
|
|
72
|
-
const secretCount = Object.values(m.vars).filter(
|
|
73
|
-
(v) => v.type === "secret"
|
|
74
|
-
).length;
|
|
75
|
-
const svcCount = m.services.length;
|
|
76
|
-
const infraCount = Object.keys(m.infrastructure).length;
|
|
77
|
-
const middleware = m.middleware.length
|
|
78
|
-
? m.middleware.join(", ")
|
|
79
|
-
: "\u2014";
|
|
80
|
-
rows.push({
|
|
81
|
-
worker: name,
|
|
82
|
-
secrets: String(secretCount),
|
|
83
|
-
services: String(svcCount),
|
|
84
|
-
infra: String(infraCount),
|
|
85
|
-
middleware,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
formatTable(rows, fmt);
|
|
89
|
-
})
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
schemaCmd
|
|
93
|
-
.command("generate <worker>")
|
|
94
|
-
.description("Generate wrangler.jsonc and .dev.vars from manifest")
|
|
95
|
-
.option("--dry-run", "Print generated content without writing")
|
|
96
|
-
.action(
|
|
97
|
-
withErrorHandling(async (worker: string, opts: { dryRun?: boolean }) => {
|
|
98
|
-
const fmt = getFormatOptions(schemaCmd);
|
|
99
|
-
const svc = new SchemaService();
|
|
100
|
-
const manifest = svc.getManifest(worker);
|
|
101
|
-
if (!manifest) {
|
|
102
|
-
formatError(
|
|
103
|
-
new CLIError(`Unknown worker "${worker}"`, ExitCode.INVALID_USAGE),
|
|
104
|
-
fmt
|
|
105
|
-
);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const wranglerContent = generateWranglerJsonc(manifest);
|
|
110
|
-
const devVarsContent = generateDevVars(manifest);
|
|
111
|
-
|
|
112
|
-
if (opts.dryRun) {
|
|
113
|
-
process.stdout.write(`--- wrangler.jsonc (${worker}) ---\n`);
|
|
114
|
-
process.stdout.write(wranglerContent + "\n");
|
|
115
|
-
process.stdout.write(`--- .dev.vars (${worker}) ---\n`);
|
|
116
|
-
process.stdout.write(devVarsContent + "\n");
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const workersDir = resolve(process.cwd(), "workers", worker);
|
|
121
|
-
const wranglerPath = resolve(workersDir, "wrangler.jsonc");
|
|
122
|
-
const devVarsPath = resolve(workersDir, ".dev.vars");
|
|
123
|
-
|
|
124
|
-
await Bun.write(wranglerPath, wranglerContent);
|
|
125
|
-
await Bun.write(devVarsPath, devVarsContent);
|
|
126
|
-
|
|
127
|
-
formatKeyValue(
|
|
128
|
-
{
|
|
129
|
-
"wrangler.jsonc": wranglerPath,
|
|
130
|
-
".dev.vars": devVarsPath,
|
|
131
|
-
},
|
|
132
|
-
{ ...fmt, json: false }
|
|
133
|
-
);
|
|
134
|
-
formatSuccess(`Generated files for ${worker}`, fmt);
|
|
135
|
-
})
|
|
136
|
-
);
|
|
137
|
-
}
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { registerTestCommand, runStep, printSummary } from "./test-command.js";
|
|
4
|
-
import type { TestSummary } from "./test-command.js";
|
|
5
|
-
import { ExitCode } from "../../utils/errors.js";
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Helpers
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
const originalSpawn = Bun.spawn;
|
|
12
|
-
|
|
13
|
-
/** Create a mock Bun.spawn that returns the given exit code and output. */
|
|
14
|
-
function mockSpawn(exitCode: number, stdout = "", stderr = "") {
|
|
15
|
-
return mock(() => ({
|
|
16
|
-
exited: Promise.resolve(exitCode),
|
|
17
|
-
stdout: new Blob([stdout]),
|
|
18
|
-
stderr: new Blob([stderr]),
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Capture stdout for assertion. */
|
|
23
|
-
function captureStdout(): { output: () => string; restore: () => void } {
|
|
24
|
-
const chunks: string[] = [];
|
|
25
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
26
|
-
const writeMock = mock((chunk: string | Buffer) => {
|
|
27
|
-
chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
28
|
-
return true;
|
|
29
|
-
});
|
|
30
|
-
process.stdout.write = writeMock as unknown as typeof process.stdout.write;
|
|
31
|
-
return {
|
|
32
|
-
output: () => chunks.join(""),
|
|
33
|
-
restore: () => {
|
|
34
|
-
process.stdout.write = originalWrite;
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Create a fresh Commander program with global --json / --quiet options. */
|
|
40
|
-
function makeProgram(): Command {
|
|
41
|
-
const program = new Command();
|
|
42
|
-
program.name("hoox");
|
|
43
|
-
program.option("--json", "");
|
|
44
|
-
program.option("--quiet", "");
|
|
45
|
-
return program;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// runStep
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
describe("runStep", () => {
|
|
53
|
-
afterEach(() => {
|
|
54
|
-
(Bun as any).spawn = originalSpawn;
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("returns success when exit code is 0", async () => {
|
|
58
|
-
(Bun as any).spawn = mockSpawn(0, "all tests passed");
|
|
59
|
-
const result = await runStep(["bun", "test"]);
|
|
60
|
-
expect(result.success).toBe(true);
|
|
61
|
-
expect(result.exitCode).toBe(0);
|
|
62
|
-
expect(result.output).toBe("all tests passed");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("returns failure when exit code is non-zero", async () => {
|
|
66
|
-
(Bun as any).spawn = mockSpawn(1, "", "3 tests failed");
|
|
67
|
-
const result = await runStep(["bun", "test"]);
|
|
68
|
-
expect(result.success).toBe(false);
|
|
69
|
-
expect(result.exitCode).toBe(1);
|
|
70
|
-
expect(result.error).toBe("3 tests failed");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("captures both stdout and stderr", async () => {
|
|
74
|
-
(Bun as any).spawn = mockSpawn(0, "stdout text", "stderr text");
|
|
75
|
-
const result = await runStep(["echo", "hello"]);
|
|
76
|
-
expect(result.output).toBe("stdout text");
|
|
77
|
-
expect(result.error).toBe("stderr text");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("records duration", async () => {
|
|
81
|
-
(Bun as any).spawn = mockSpawn(0);
|
|
82
|
-
const result = await runStep(["bun", "test"]);
|
|
83
|
-
expect(result.duration).toBeGreaterThanOrEqual(0);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("returns the command string", async () => {
|
|
87
|
-
(Bun as any).spawn = mockSpawn(0);
|
|
88
|
-
const result = await runStep([
|
|
89
|
-
"vitest",
|
|
90
|
-
"run",
|
|
91
|
-
"--config",
|
|
92
|
-
"vite.config.ts",
|
|
93
|
-
]);
|
|
94
|
-
expect(result.command).toBe("vitest run --config vite.config.ts");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("handles spawn errors gracefully", async () => {
|
|
98
|
-
(Bun as any).spawn = mock(() => {
|
|
99
|
-
throw new Error("ENOENT: no such file");
|
|
100
|
-
});
|
|
101
|
-
const result = await runStep(["nonexistent"]);
|
|
102
|
-
expect(result.success).toBe(false);
|
|
103
|
-
expect(result.exitCode).toBeNull();
|
|
104
|
-
expect(result.error).toContain("ENOENT");
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// printSummary
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
describe("printSummary", () => {
|
|
113
|
-
let capture: ReturnType<typeof captureStdout>;
|
|
114
|
-
|
|
115
|
-
beforeEach(() => {
|
|
116
|
-
capture = captureStdout();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
afterEach(() => {
|
|
120
|
-
capture.restore();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const passingSummary: TestSummary = {
|
|
124
|
-
total: 4,
|
|
125
|
-
passed: 4,
|
|
126
|
-
failed: 0,
|
|
127
|
-
results: [
|
|
128
|
-
{
|
|
129
|
-
step: "Lint",
|
|
130
|
-
command: "bun run lint",
|
|
131
|
-
success: true,
|
|
132
|
-
exitCode: 0,
|
|
133
|
-
duration: 1200,
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
step: "TypeCheck",
|
|
137
|
-
command: "bun run typecheck",
|
|
138
|
-
success: true,
|
|
139
|
-
exitCode: 0,
|
|
140
|
-
duration: 3400,
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
step: "Unit Tests",
|
|
144
|
-
command: "bun test --coverage",
|
|
145
|
-
success: true,
|
|
146
|
-
exitCode: 0,
|
|
147
|
-
duration: 8900,
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
step: "Integration Tests",
|
|
151
|
-
command: "vitest run...",
|
|
152
|
-
success: true,
|
|
153
|
-
exitCode: 0,
|
|
154
|
-
duration: 15000,
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const failingSummary: TestSummary = {
|
|
160
|
-
total: 2,
|
|
161
|
-
passed: 1,
|
|
162
|
-
failed: 1,
|
|
163
|
-
results: [
|
|
164
|
-
{
|
|
165
|
-
step: "Lint",
|
|
166
|
-
command: "bun run lint",
|
|
167
|
-
success: true,
|
|
168
|
-
exitCode: 0,
|
|
169
|
-
duration: 500,
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
step: "TypeCheck",
|
|
173
|
-
command: "bun run typecheck",
|
|
174
|
-
success: false,
|
|
175
|
-
exitCode: 2,
|
|
176
|
-
duration: 800,
|
|
177
|
-
error: "TS2322: type error",
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
it("prints a table for human output", () => {
|
|
183
|
-
printSummary(passingSummary, {});
|
|
184
|
-
const out = capture.output();
|
|
185
|
-
expect(out).toContain("All 4 steps passed");
|
|
186
|
-
expect(out).toContain("Lint");
|
|
187
|
-
expect(out).toContain("passed");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("shows failure count when some steps fail", () => {
|
|
191
|
-
printSummary(failingSummary, {});
|
|
192
|
-
const out = capture.output();
|
|
193
|
-
expect(out).toContain("1/2 passed");
|
|
194
|
-
expect(out).toContain("TypeCheck");
|
|
195
|
-
expect(out).toContain("failed");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("outputs JSON when json=true", () => {
|
|
199
|
-
printSummary(passingSummary, { json: true });
|
|
200
|
-
const out = capture.output();
|
|
201
|
-
const parsed = JSON.parse(out);
|
|
202
|
-
expect(parsed.total).toBe(4);
|
|
203
|
-
expect(parsed.passed).toBe(4);
|
|
204
|
-
expect(parsed.failed).toBe(0);
|
|
205
|
-
expect(parsed.results).toHaveLength(4);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("outputs nothing when quiet=true", () => {
|
|
209
|
-
printSummary(passingSummary, { quiet: true });
|
|
210
|
-
expect(capture.output()).toBe("");
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
// registerTestCommand — structure verification
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
|
|
218
|
-
describe("registerTestCommand", () => {
|
|
219
|
-
it("registers test command with all subcommands", () => {
|
|
220
|
-
const program = makeProgram();
|
|
221
|
-
registerTestCommand(program);
|
|
222
|
-
|
|
223
|
-
const testCmd = program.commands.find((c) => c.name() === "test");
|
|
224
|
-
expect(testCmd).toBeDefined();
|
|
225
|
-
expect(testCmd?.description()).toContain("Run tests");
|
|
226
|
-
|
|
227
|
-
const subNames = testCmd?.commands.map((c) => c.name()) ?? [];
|
|
228
|
-
expect(subNames).toContain("all");
|
|
229
|
-
expect(subNames).toContain("unit");
|
|
230
|
-
expect(subNames).toContain("integration");
|
|
231
|
-
expect(subNames).toContain("worker");
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("all subcommand has --json option", () => {
|
|
235
|
-
const program = makeProgram();
|
|
236
|
-
registerTestCommand(program);
|
|
237
|
-
|
|
238
|
-
const testCmd = program.commands.find((c) => c.name() === "test");
|
|
239
|
-
const allCmd = testCmd?.commands.find((c) => c.name() === "all");
|
|
240
|
-
expect(allCmd).toBeDefined();
|
|
241
|
-
|
|
242
|
-
// Commander stores options; verify --json is registered
|
|
243
|
-
const optionNames = allCmd?.options.map((o) => o.long) ?? [];
|
|
244
|
-
expect(optionNames).toContain("--json");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("unit subcommand has --coverage option", () => {
|
|
248
|
-
const program = makeProgram();
|
|
249
|
-
registerTestCommand(program);
|
|
250
|
-
|
|
251
|
-
const testCmd = program.commands.find((c) => c.name() === "test");
|
|
252
|
-
const unitCmd = testCmd?.commands.find((c) => c.name() === "unit");
|
|
253
|
-
expect(unitCmd).toBeDefined();
|
|
254
|
-
|
|
255
|
-
const optionNames = unitCmd?.options.map((o) => o.long) ?? [];
|
|
256
|
-
expect(optionNames).toContain("--coverage");
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("integration subcommand has --coverage option", () => {
|
|
260
|
-
const program = makeProgram();
|
|
261
|
-
registerTestCommand(program);
|
|
262
|
-
|
|
263
|
-
const testCmd = program.commands.find((c) => c.name() === "test");
|
|
264
|
-
const intCmd = testCmd?.commands.find((c) => c.name() === "integration");
|
|
265
|
-
expect(intCmd).toBeDefined();
|
|
266
|
-
|
|
267
|
-
const optionNames = intCmd?.options.map((o) => o.long) ?? [];
|
|
268
|
-
expect(optionNames).toContain("--coverage");
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("worker subcommand requires a name argument and has --coverage option", () => {
|
|
272
|
-
const program = makeProgram();
|
|
273
|
-
registerTestCommand(program);
|
|
274
|
-
|
|
275
|
-
const testCmd = program.commands.find((c) => c.name() === "test");
|
|
276
|
-
const workerCmd = testCmd?.commands.find((c) => c.name() === "worker");
|
|
277
|
-
expect(workerCmd).toBeDefined();
|
|
278
|
-
|
|
279
|
-
// Verify it expects a required argument
|
|
280
|
-
const args = workerCmd?.registeredArguments ?? [];
|
|
281
|
-
expect(args.length).toBeGreaterThan(0);
|
|
282
|
-
|
|
283
|
-
const optionNames = workerCmd?.options.map((o) => o.long) ?? [];
|
|
284
|
-
expect(optionNames).toContain("--coverage");
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("all subcommand runs pipeline steps and stops on first failure", async () => {
|
|
288
|
-
const program = makeProgram();
|
|
289
|
-
registerTestCommand(program);
|
|
290
|
-
|
|
291
|
-
// First lint passes, typecheck fails → should stop after 2 steps
|
|
292
|
-
let callCount = 0;
|
|
293
|
-
const mockSpawnFn = mock((args: string[]) => {
|
|
294
|
-
callCount++;
|
|
295
|
-
const cmd = args.join(" ");
|
|
296
|
-
// Lint passes (call 1), TypeCheck fails (call 2)
|
|
297
|
-
const exitCode = cmd.includes("lint") ? 0 : 1;
|
|
298
|
-
const stderr = cmd.includes("lint") ? "" : "TS2322: type error";
|
|
299
|
-
return {
|
|
300
|
-
exited: Promise.resolve(exitCode),
|
|
301
|
-
stdout: new Blob([]),
|
|
302
|
-
stderr: new Blob([stderr]),
|
|
303
|
-
};
|
|
304
|
-
});
|
|
305
|
-
(Bun as any).spawn = mockSpawnFn;
|
|
306
|
-
|
|
307
|
-
// Suppress spinner output by running without tty-like env
|
|
308
|
-
await program.parseAsync(["test", "all"], {
|
|
309
|
-
from: "user",
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Should only run 2 steps (lint + typecheck) before stopping
|
|
313
|
-
expect(callCount).toBe(2);
|
|
314
|
-
expect(process.exitCode).toBe(ExitCode.ERROR);
|
|
315
|
-
|
|
316
|
-
(Bun as any).spawn = originalSpawn;
|
|
317
|
-
});
|
|
318
|
-
});
|