Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/nushell/runtime.ts

Raw
import { spawn } from "node:child_process";
import { constants } from "node:fs";
import { access } from "node:fs/promises";
import { join } from "node:path";
import type { BashOperations } from "@earendil-works/pi-coding-agent";
import { span } from "./src/debug.ts";
import { findExecutable } from "./src/pi-ext-executable.ts";

const FRESH_ARGS = [
	"--no-config-file",
	"--no-history",
	"--error-style",
	"fancy",
	"--table-mode",
	"markdown",
	"-c",
];

export function findNu(): string | undefined {
	return findExecutable(process.platform === "win32" ? ["nu.exe"] : ["nu"]);
}

function stopProcess(pid: number | undefined): void {
	if (!pid) return;
	if (process.platform === "win32") {
		const root = process.env["SystemRoot"] ?? "C:\\Windows";
		spawn(
			join(root, "System32", "taskkill.exe"),
			["/F", "/T", "/PID", String(pid)],
			{
				stdio: "ignore",
				windowsHide: true,
			},
		).unref();
	} else {
		try {
			process.kill(-pid, "SIGKILL");
		} catch {
			try {
				process.kill(pid, "SIGKILL");
			} catch {
				/* already exited */
			}
		}
	}
}

export function freshOperations(binary: string): BashOperations {
	return {
		async exec(command, cwd, { onData, signal, timeout, env }) {
			const finish = span?.(
				"fresh",
				timeout === undefined ? undefined : { timeoutMs: timeout * 1_000 },
			);
			let exitCode: number | null = null;
			let timedOut = false;
			let outputBytes = 0;
			try {
				if (signal?.aborted) throw new Error("aborted");
				if (
					timeout !== undefined &&
					(!Number.isFinite(timeout) ||
						timeout <= 0 ||
						timeout * 1000 > 2_147_483_647)
				)
					throw new Error(
						"Invalid timeout: must be a positive number of seconds within the timer range",
					);
				await access(cwd, constants.F_OK);
				const child = spawn(binary, [...FRESH_ARGS, command], {
					cwd,
					env,
					detached: process.platform !== "win32",
					stdio: ["ignore", "pipe", "pipe"],
					windowsHide: true,
				});
				const onAbort = () => stopProcess(child.pid);
				const timer =
					timeout === undefined
						? undefined
						: setTimeout(() => {
								timedOut = true;
								onAbort();
							}, timeout * 1000);
				signal?.addEventListener("abort", onAbort, { once: true });
				if (signal?.aborted) onAbort();
				if (finish) {
					const forward = (chunk: Buffer) => {
						outputBytes += chunk.length;
						onData(chunk);
					};
					child.stdout.on("data", forward);
					child.stderr.on("data", forward);
				} else {
					child.stdout.on("data", onData);
					child.stderr.on("data", onData);
				}
				try {
					exitCode = await new Promise<number | null>((resolve, reject) => {
						child.once("error", reject);
						child.once("close", resolve);
					});
					if (signal?.aborted) throw new Error("aborted");
					if (timedOut) throw new Error(`timeout:${timeout}`);
					return { exitCode: exitCode ?? (child.signalCode ? 1 : null) };
				} finally {
					if (timer) clearTimeout(timer);
					signal?.removeEventListener("abort", onAbort);
				}
			} finally {
				const status = signal?.aborted
					? "aborted"
					: timedOut
						? "timeout"
						: exitCode === 0
							? "success"
							: exitCode === null
								? "failed"
								: "nonzero";
				finish?.(
					status === "success" || status === "nonzero" ? "finish" : "error",
					{
						status,
						count: outputBytes,
					},
				);
			}
		},
	};
}