feat: initialize unidesk platform
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
import { readConfig } from "./src/config";
|
||||
import { debugDispatch, debugHealth } from "./src/debug";
|
||||
import { stackLogs, stackStatus, startStack, stopStack } from "./src/docker";
|
||||
import { runE2E } from "./src/e2e";
|
||||
import { emitError, emitJson } from "./src/output";
|
||||
import { jobWithTail, listJobs, readJob, runJob } from "./src/jobs";
|
||||
import { runChecks } from "./src/check";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const commandName = args.join(" ") || "help";
|
||||
|
||||
function help(): unknown {
|
||||
return {
|
||||
entry: "bun scripts/cli.ts",
|
||||
output: "json",
|
||||
commands: [
|
||||
{ command: "help", description: "List supported commands." },
|
||||
{ command: "config show", description: "Validate and print config.json as the single source of truth." },
|
||||
{ command: "check", description: "Run config, TypeScript, file presence, and docker-compose config checks." },
|
||||
{ command: "server start", description: "Fire-and-forget build/start for database, backend-core, frontend, and provider gateway." },
|
||||
{ command: "server stop", description: "Fire-and-forget docker-compose down for the fixed UniDesk stack." },
|
||||
{ command: "server status", description: "Show fixed ports, containers, service health, and public URLs." },
|
||||
{ command: "server logs [--tail-bytes N]", description: "Return bounded tails from file logs and docker logs." },
|
||||
{ command: "job list", description: "List async jobs from .state/jobs." },
|
||||
{ command: "job status <jobId|latest> [--tail-bytes N]", description: "Show job state with bounded stdout/stderr tails." },
|
||||
{ command: "debug health", description: "Probe core, overview, nodes, and frontend using real HTTP endpoints." },
|
||||
{ command: "debug dispatch [providerId] [docker.ps|echo]", description: "Submit a real dispatch request to core for CLI debugging." },
|
||||
{ command: "e2e run", description: "Run public API, database, provider, and Playwright frontend E2E checks." },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function numberOption(name: string, defaultValue: number): number {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) return defaultValue;
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function latestJobId(): string {
|
||||
const jobs = listJobs();
|
||||
if (jobs.length === 0) throw new Error("No jobs found");
|
||||
return jobs[0].id;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const [top, sub, third, fourth] = args;
|
||||
if (top === undefined || top === "help" || top === "--help" || top === "-h") {
|
||||
emitJson(commandName, help());
|
||||
return;
|
||||
}
|
||||
|
||||
if (top === "internal" && sub === "run-job") {
|
||||
if (!third) throw new Error("internal run-job requires job id");
|
||||
emitJson(commandName, await runJob(third));
|
||||
return;
|
||||
}
|
||||
|
||||
const config = readConfig();
|
||||
|
||||
if (top === "config" && sub === "show") {
|
||||
emitJson(commandName, { config });
|
||||
return;
|
||||
}
|
||||
|
||||
if (top === "check") {
|
||||
const result = runChecks(config);
|
||||
emitJson(commandName, result, result.ok);
|
||||
if (!result.ok) process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (top === "server") {
|
||||
if (sub === "start") {
|
||||
emitJson(commandName, startStack(config));
|
||||
return;
|
||||
}
|
||||
if (sub === "stop") {
|
||||
emitJson(commandName, stopStack(config));
|
||||
return;
|
||||
}
|
||||
if (sub === "status") {
|
||||
emitJson(commandName, await stackStatus(config));
|
||||
return;
|
||||
}
|
||||
if (sub === "logs") {
|
||||
emitJson(commandName, stackLogs(config, numberOption("--tail-bytes", 3000)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (top === "job") {
|
||||
if (sub === "list") {
|
||||
emitJson(commandName, { jobs: listJobs() });
|
||||
return;
|
||||
}
|
||||
if (sub === "status") {
|
||||
const id = third === "latest" || third === undefined ? latestJobId() : third;
|
||||
emitJson(commandName, { job: jobWithTail(readJob(id), numberOption("--tail-bytes", 12000)) });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (top === "debug") {
|
||||
if (sub === "health") {
|
||||
emitJson(commandName, await debugHealth(config));
|
||||
return;
|
||||
}
|
||||
if (sub === "dispatch") {
|
||||
const providerId = third ?? config.providerGateway.id;
|
||||
const dispatchCommand = fourth === "docker.ps" || fourth === "echo" ? fourth : "docker.ps";
|
||||
emitJson(commandName, await debugDispatch(config, providerId, dispatchCommand));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (top === "e2e" && sub === "run") {
|
||||
const result = await runE2E(config);
|
||||
const ok = (result as { ok?: unknown }).ok === true;
|
||||
emitJson(commandName, result, ok);
|
||||
if (!ok) process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown command: ${commandName}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
emitError(commandName, error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user