feat: initialize unidesk platform
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { repoRoot, rootPath } from "./config";
|
||||
import { runCommandToFiles, tailFile } from "./command";
|
||||
|
||||
export type JobStatus = "queued" | "running" | "succeeded" | "failed";
|
||||
|
||||
export interface JobRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
status: JobStatus;
|
||||
command: string[];
|
||||
cwd: string;
|
||||
createdAt: string;
|
||||
startedAt: string | null;
|
||||
finishedAt: string | null;
|
||||
exitCode: number | null;
|
||||
stdoutFile: string;
|
||||
stderrFile: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
function jobsDir(): string {
|
||||
const dir = rootPath(".state", "jobs");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
|
||||
function jobPath(id: string): string {
|
||||
return join(jobsDir(), `${id}.json`);
|
||||
}
|
||||
|
||||
function writeJob(job: JobRecord): void {
|
||||
writeFileSync(jobPath(job.id), `${JSON.stringify(job, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
export function readJob(id: string): JobRecord {
|
||||
const path = jobPath(id);
|
||||
if (!existsSync(path)) throw new Error(`job not found: ${id}`);
|
||||
return JSON.parse(readFileSync(path, "utf8")) as JobRecord;
|
||||
}
|
||||
|
||||
export function listJobs(): JobRecord[] {
|
||||
return readdirSync(jobsDir())
|
||||
.filter((name) => name.endsWith(".json"))
|
||||
.map((name) => JSON.parse(readFileSync(join(jobsDir(), name), "utf8")) as JobRecord)
|
||||
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
||||
}
|
||||
|
||||
export function startJob(name: string, command: string[], note: string): JobRecord {
|
||||
const id = `${name}_${new Date().toISOString().replace(/[-:.TZ]/g, "")}_${Math.random().toString(16).slice(2, 8)}`;
|
||||
const stdoutFile = rootPath(".state", "jobs", `${id}.stdout.log`);
|
||||
const stderrFile = rootPath(".state", "jobs", `${id}.stderr.log`);
|
||||
const job: JobRecord = {
|
||||
id,
|
||||
name,
|
||||
status: "queued",
|
||||
command,
|
||||
cwd: repoRoot,
|
||||
createdAt: new Date().toISOString(),
|
||||
startedAt: null,
|
||||
finishedAt: null,
|
||||
exitCode: null,
|
||||
stdoutFile,
|
||||
stderrFile,
|
||||
note,
|
||||
};
|
||||
writeJob(job);
|
||||
const child = spawn(process.execPath, [rootPath("scripts", "cli.ts"), "internal", "run-job", id], {
|
||||
cwd: repoRoot,
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
env: process.env,
|
||||
});
|
||||
child.unref();
|
||||
return job;
|
||||
}
|
||||
|
||||
export async function runJob(id: string): Promise<JobRecord> {
|
||||
const job = readJob(id);
|
||||
job.status = "running";
|
||||
job.startedAt = new Date().toISOString();
|
||||
writeJob(job);
|
||||
const exitCode = await runCommandToFiles(job.command, job.cwd, job.stdoutFile, job.stderrFile);
|
||||
job.exitCode = exitCode;
|
||||
job.status = exitCode === 0 ? "succeeded" : "failed";
|
||||
job.finishedAt = new Date().toISOString();
|
||||
writeJob(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
export function jobWithTail(job: JobRecord, maxBytes = 12000): JobRecord & { stdoutTail: string; stderrTail: string } {
|
||||
return { ...job, stdoutTail: tailFile(job.stdoutFile, maxBytes), stderrTail: tailFile(job.stderrFile, maxBytes) };
|
||||
}
|
||||
Reference in New Issue
Block a user