mirror of
https://github.com/przeprogramowani/10x-cli.git
synced 2026-09-19 03:30:01 +08:00
06d841f27a
- cli.ts: add 60s kill timer to spawnCli, export cleanupTempDirs() - auth-flow.test.ts: add earlyExit Promise.race + callback response check - auth-setup.ts: check callback response status - doctor.test.ts: use getE2EEnv().testEmail instead of hardcoded string - all test files: add afterAll(cleanupTempDirs) hooks - reviews/impl-review.md: save full review report Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
144 lines
3.3 KiB
TypeScript
144 lines
3.3 KiB
TypeScript
import { existsSync, rmSync } from "node:fs";
|
|
import { mkdtempSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { resolve, join } from "node:path";
|
|
import type { Subprocess } from "bun";
|
|
|
|
const BINARY_NAME = process.platform === "win32" ? "10x.exe" : "10x";
|
|
const BINARY_PATH = resolve(import.meta.dir, "../../../dist", BINARY_NAME);
|
|
|
|
export function getBinaryPath(): string {
|
|
return BINARY_PATH;
|
|
}
|
|
|
|
export function binaryExists(): boolean {
|
|
return existsSync(BINARY_PATH);
|
|
}
|
|
|
|
export interface CliResult {
|
|
exitCode: number;
|
|
stdout: string;
|
|
stderr: string;
|
|
json: <T>() => T;
|
|
}
|
|
|
|
interface CliOptions {
|
|
env?: Record<string, string>;
|
|
timeout?: number;
|
|
}
|
|
|
|
function normalize(raw: string): string {
|
|
return raw.replace(/\r\n/g, "\n");
|
|
}
|
|
|
|
function makeJson(stdout: string): <T>() => T {
|
|
return <T>() => {
|
|
const trimmed = stdout.trim();
|
|
if (!trimmed) throw new Error("stdout is empty — no JSON to parse");
|
|
return JSON.parse(trimmed) as T;
|
|
};
|
|
}
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
function makeTempConfigDir(): string {
|
|
const dir = mkdtempSync(join(tmpdir(), "10x-e2e-"));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
export function cleanupTempDirs(): void {
|
|
for (const dir of tempDirs) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
tempDirs.length = 0;
|
|
}
|
|
|
|
function buildEnv(
|
|
configDir: string,
|
|
extra?: Record<string, string>,
|
|
): Record<string, string> {
|
|
const base: Record<string, string> = {
|
|
PATH: process.env["PATH"] ?? "",
|
|
NO_COLOR: "1",
|
|
HOME: configDir,
|
|
USERPROFILE: configDir,
|
|
XDG_CONFIG_HOME: configDir,
|
|
APPDATA: configDir,
|
|
};
|
|
if (extra) Object.assign(base, extra);
|
|
return base;
|
|
}
|
|
|
|
export function runCli(
|
|
args: string[],
|
|
options: CliOptions = {},
|
|
): CliResult {
|
|
if (!binaryExists()) {
|
|
throw new Error(
|
|
`Binary not found at ${BINARY_PATH}. Run \`bun run build:binary\` first.`,
|
|
);
|
|
}
|
|
|
|
const configDir = makeTempConfigDir();
|
|
const env = buildEnv(configDir, options.env);
|
|
const timeout = options.timeout ?? 30_000;
|
|
|
|
const proc = Bun.spawnSync([BINARY_PATH, ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
timeout,
|
|
});
|
|
|
|
const stdout = normalize(proc.stdout.toString());
|
|
const stderr = normalize(proc.stderr.toString());
|
|
|
|
return {
|
|
exitCode: proc.exitCode,
|
|
stdout,
|
|
stderr,
|
|
json: makeJson(stdout),
|
|
};
|
|
}
|
|
|
|
export function spawnCli(
|
|
args: string[],
|
|
options: CliOptions = {},
|
|
): { proc: Subprocess; result: Promise<CliResult>; configDir: string } {
|
|
if (!binaryExists()) {
|
|
throw new Error(
|
|
`Binary not found at ${BINARY_PATH}. Run \`bun run build:binary\` first.`,
|
|
);
|
|
}
|
|
|
|
const configDir = makeTempConfigDir();
|
|
const env = buildEnv(configDir, options.env);
|
|
const timeout = options.timeout ?? 60_000;
|
|
|
|
const proc = Bun.spawn([BINARY_PATH, ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const killTimer = setTimeout(() => proc.kill(), timeout);
|
|
|
|
const result = (async (): Promise<CliResult> => {
|
|
const exitCode = await proc.exited;
|
|
clearTimeout(killTimer);
|
|
const stdoutRaw = await new Response(proc.stdout).text();
|
|
const stderrRaw = await new Response(proc.stderr).text();
|
|
const stdout = normalize(stdoutRaw);
|
|
const stderr = normalize(stderrRaw);
|
|
return {
|
|
exitCode,
|
|
stdout,
|
|
stderr,
|
|
json: makeJson(stdout),
|
|
};
|
|
})();
|
|
|
|
return { proc, result, configDir };
|
|
}
|