- Add ia_dev submodule (projects/smart_ide on forge 4nk) - Document APIs, orchestrator, gateway, local-office, rollout - Add systemd/scripts layout; relocate setup scripts - Remove obsolete nginx/enso-only docs from this repo scope
186 lines
4.0 KiB
TypeScript
186 lines
4.0 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
|
|
const MAX_PATTERN_LEN = 8192;
|
|
|
|
export interface RgMatchRow {
|
|
path: string;
|
|
lineNumber: number;
|
|
line: string;
|
|
}
|
|
|
|
export interface RgResult {
|
|
matches: RgMatchRow[];
|
|
truncated: boolean;
|
|
exitCode: number;
|
|
stderr: string;
|
|
}
|
|
|
|
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
|
typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
|
const readPathText = (v: unknown): string => {
|
|
if (!isRecord(v)) {
|
|
return "";
|
|
}
|
|
const t = v.text;
|
|
return typeof t === "string" ? t : "";
|
|
};
|
|
|
|
const readLinesText = (v: unknown): string => {
|
|
if (!isRecord(v)) {
|
|
return "";
|
|
}
|
|
const t = v.lines;
|
|
if (!isRecord(t)) {
|
|
return "";
|
|
}
|
|
const text = t.text;
|
|
return typeof text === "string" ? text : "";
|
|
};
|
|
|
|
export const runRipgrepJson = (
|
|
pattern: string,
|
|
searchPath: string,
|
|
maxMatches: number,
|
|
timeoutMs: number,
|
|
): Promise<RgResult> => {
|
|
if (pattern.length === 0) {
|
|
return Promise.resolve({
|
|
matches: [],
|
|
truncated: false,
|
|
exitCode: 2,
|
|
stderr: "Empty pattern",
|
|
});
|
|
}
|
|
if (pattern.length > MAX_PATTERN_LEN) {
|
|
return Promise.resolve({
|
|
matches: [],
|
|
truncated: false,
|
|
exitCode: 2,
|
|
stderr: "Pattern too long",
|
|
});
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const matches: RgMatchRow[] = [];
|
|
let truncated = false;
|
|
let stderr = "";
|
|
let settled = false;
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
const finish = (r: RgResult): void => {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
settled = true;
|
|
if (timer !== undefined) {
|
|
clearTimeout(timer);
|
|
}
|
|
resolve(r);
|
|
};
|
|
|
|
const child = spawn(
|
|
"rg",
|
|
[
|
|
"--json",
|
|
"--line-number",
|
|
"--regexp",
|
|
pattern,
|
|
"--",
|
|
searchPath,
|
|
],
|
|
{
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
windowsHide: true,
|
|
},
|
|
);
|
|
|
|
timer = setTimeout(() => {
|
|
child.kill("SIGKILL");
|
|
finish({
|
|
matches,
|
|
truncated: true,
|
|
exitCode: 124,
|
|
stderr: `${stderr}\nTimed out`.trim(),
|
|
});
|
|
}, timeoutMs);
|
|
|
|
const flushErr = (): void => {
|
|
child.stderr?.on("data", (chunk: Buffer) => {
|
|
stderr += chunk.toString("utf8");
|
|
});
|
|
};
|
|
flushErr();
|
|
|
|
let buf = "";
|
|
child.stdout?.setEncoding("utf8");
|
|
child.stdout?.on("data", (chunk: string) => {
|
|
buf += chunk;
|
|
let idx: number;
|
|
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
const line = buf.slice(0, idx);
|
|
buf = buf.slice(idx + 1);
|
|
if (line.length === 0) {
|
|
continue;
|
|
}
|
|
let obj: unknown;
|
|
try {
|
|
obj = JSON.parse(line) as unknown;
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (!isRecord(obj) || obj.type !== "match") {
|
|
continue;
|
|
}
|
|
const data = obj.data;
|
|
if (!isRecord(data)) {
|
|
continue;
|
|
}
|
|
const pathText = readPathText(data.path);
|
|
const lineNum = data.line_number;
|
|
const lineText = readLinesText(data).replace(/\n$/, "");
|
|
if (typeof lineNum !== "number" || pathText.length === 0) {
|
|
continue;
|
|
}
|
|
matches.push({
|
|
path: pathText,
|
|
lineNumber: lineNum,
|
|
line: lineText,
|
|
});
|
|
if (matches.length >= maxMatches) {
|
|
truncated = true;
|
|
child.kill("SIGTERM");
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
child.on("error", (err: NodeJS.ErrnoException) => {
|
|
if (err.code === "ENOENT") {
|
|
finish({
|
|
matches: [],
|
|
truncated: false,
|
|
exitCode: 127,
|
|
stderr: "ripgrep (rg) not found in PATH",
|
|
});
|
|
return;
|
|
}
|
|
finish({
|
|
matches,
|
|
truncated,
|
|
exitCode: 1,
|
|
stderr: `${stderr}\n${err.message}`.trim(),
|
|
});
|
|
});
|
|
|
|
child.on("close", (code) => {
|
|
finish({
|
|
matches,
|
|
truncated,
|
|
exitCode: code ?? 0,
|
|
stderr: stderr.trim(),
|
|
});
|
|
});
|
|
});
|
|
};
|