| ff39a5e8 Andy McCall ↶ |
1 |
// Running the spectra program: setup commands, and the bridge. |
| 2 |
import * as cp from "node:child_process"; |
| 3 |
import * as fs from "node:fs"; |
| 4 |
import * as os from "node:os"; |
| 5 |
import * as path from "node:path"; |
| 6 |
import * as vscode from "vscode"; |
| 7 |
|
| 8 |
export interface Ran { |
| 9 |
code: number; |
| 10 |
stdout: string; |
| 11 |
stderr: string; |
| 12 |
} |
| 13 |
|
| 14 |
/** The spectra program: the setting, else PATH, else ~/.local/bin. */ |
| 15 |
export function binary(): string | undefined { |
| 16 |
const set = vscode.workspace.getConfiguration("spectra").get<string>("path")?.trim(); |
| 17 |
if (set) return set.replace(/^~(?=\/)/, os.homedir()); |
| 18 |
for (const dir of (process.env.PATH ?? "").split(path.delimiter)) { |
| 19 |
const p = path.join(dir, "spectra"); |
| 20 |
if (dir && fs.existsSync(p)) return p; |
| 21 |
} |
| 22 |
const local = path.join(os.homedir(), ".local", "bin", "spectra"); |
| 23 |
return fs.existsSync(local) ? local : undefined; |
| 24 |
} |
| 25 |
|
| 26 |
export function run(args: string[], cwd?: string, input?: string): Promise<Ran> { |
| 27 |
const bin = binary(); |
| 28 |
if (!bin) return Promise.resolve({ code: 127, stdout: "", stderr: "spectra is not installed (Spectra: Check for spectra)" }); |
| 29 |
return new Promise((resolve) => { |
| 30 |
const child = cp.execFile(bin, args, { cwd, env: { ...process.env, NO_COLOR: "1" }, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => { |
| 31 |
const code = err && typeof (err as cp.ExecFileException).code === "number" ? ((err as cp.ExecFileException).code as number) : err ? 1 : 0; |
| 32 |
resolve({ code, stdout: String(stdout), stderr: String(stderr) }); |
| 33 |
}); |
| 34 |
if (input !== undefined) { |
| 35 |
child.stdin?.end(input); |
| 36 |
} |
| 37 |
}); |
| 38 |
} |
| 39 |
|
| 40 |
/** Run, and parse stdout as JSON; throw with spectra's own words on failure. */ |
| 41 |
export async function json<T>(args: string[], cwd?: string): Promise<T> { |
| 42 |
const r = await run([...args], cwd); |
| 43 |
if (r.code !== 0) throw new Error(why(r)); |
| 44 |
return JSON.parse(r.stdout) as T; |
| 45 |
} |
| 46 |
|
| 47 |
/** Run; throw with spectra's own words on failure. */ |
| 48 |
export async function ok(args: string[], cwd?: string, input?: string): Promise<string> { |
| 49 |
const r = await run(args, cwd, input); |
| 50 |
if (r.code !== 0) throw new Error(why(r)); |
| 51 |
return r.stdout; |
| 52 |
} |
| 53 |
|
| 54 |
/** The error spectra printed, without its `error:` prefix. */ |
| 55 |
export function why(r: Ran): string { |
| 56 |
const text = (r.stderr.trim() || r.stdout.trim()).split("\n").filter((l) => l.trim()).pop() ?? `spectra exited ${r.code}`; |
| 57 |
return text.replace(/^error:\s*/, ""); |
| 58 |
} |
| 59 |
|
| 60 |
export interface Status { |
| 61 |
version: string; |
| 62 |
home: string; |
| 63 |
identity: { name: string; key: string } | null; |
| 64 |
server: { url: string; instance: string } | null; |
| 65 |
local: { data: string; initialised: boolean; serving: boolean }; |
| 66 |
reachable: boolean; |
| 67 |
} |
| 68 |
|
| 69 |
export function status(): Promise<Status> { |
| 70 |
return json<Status>(["status", "--json"]); |
| 71 |
} |