feat: initialize unidesk platform

This commit is contained in:
Codex
2026-05-04 11:09:35 +00:00
commit caa80ee5e7
56 changed files with 3273 additions and 0 deletions
+95
View File
@@ -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) };
}