// Running the spectra program: setup commands, and the bridge. import * as cp from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import * as vscode from "vscode"; export interface Ran { code: number; stdout: string; stderr: string; } /** The spectra program: the setting, else PATH, else ~/.local/bin. */ export function binary(): string | undefined { const set = vscode.workspace.getConfiguration("spectra").get("path")?.trim(); if (set) return set.replace(/^~(?=\/)/, os.homedir()); for (const dir of (process.env.PATH ?? "").split(path.delimiter)) { const p = path.join(dir, "spectra"); if (dir && fs.existsSync(p)) return p; } const local = path.join(os.homedir(), ".local", "bin", "spectra"); return fs.existsSync(local) ? local : undefined; } export function run(args: string[], cwd?: string, input?: string): Promise { const bin = binary(); if (!bin) return Promise.resolve({ code: 127, stdout: "", stderr: "spectra is not installed (Spectra: Check for spectra)" }); return new Promise((resolve) => { const child = cp.execFile(bin, args, { cwd, env: { ...process.env, NO_COLOR: "1" }, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => { const code = err && typeof (err as cp.ExecFileException).code === "number" ? ((err as cp.ExecFileException).code as number) : err ? 1 : 0; resolve({ code, stdout: String(stdout), stderr: String(stderr) }); }); if (input !== undefined) { child.stdin?.end(input); } }); } /** Run, and parse stdout as JSON; throw with spectra's own words on failure. */ export async function json(args: string[], cwd?: string): Promise { const r = await run([...args], cwd); if (r.code !== 0) throw new Error(why(r)); return JSON.parse(r.stdout) as T; } /** Run; throw with spectra's own words on failure. */ export async function ok(args: string[], cwd?: string, input?: string): Promise { const r = await run(args, cwd, input); if (r.code !== 0) throw new Error(why(r)); return r.stdout; } /** The error spectra printed, without its `error:` prefix. */ export function why(r: Ran): string { const text = (r.stderr.trim() || r.stdout.trim()).split("\n").filter((l) => l.trim()).pop() ?? `spectra exited ${r.code}`; return text.replace(/^error:\s*/, ""); } export interface Status { version: string; home: string; identity: { name: string; key: string } | null; server: { url: string; instance: string } | null; local: { data: string; initialised: boolean; serving: boolean }; reachable: boolean; } export function status(): Promise { return json(["status", "--json"]); }