Files
PaperClipAI/scripts/dev-runner-native-binary.mjs
T

78 lines
2.6 KiB
JavaScript

import { readdirSync, statSync } from "node:fs";
import path from "node:path";
export function resolveNativeRunnerRequirement({ exitCode, stdout }) {
if (exitCode !== 0) {
return { nativeRunnerRequired: true, valid: false };
}
const jsonLines = stdout
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.startsWith("{"));
for (let index = jsonLines.length - 1; index >= 0; index -= 1) {
try {
const payload = JSON.parse(jsonLines[index]);
if (typeof payload?.nativeRunnerRequired === "boolean") {
return { nativeRunnerRequired: payload.nativeRunnerRequired, valid: true };
}
} catch {
// Keep scanning earlier JSON-looking output from pnpm and runtime logging.
}
}
// An unknown state must not strand a persisted native run without runnerd.
return { nativeRunnerRequired: true, valid: false };
}
function newestMtimeMs(target) {
const stat = statSync(target, { throwIfNoEntry: false });
if (!stat) return 0;
if (!stat.isDirectory()) return stat.mtimeMs;
let newest = stat.mtimeMs;
for (const entry of readdirSync(target)) {
const childNewest = newestMtimeMs(path.join(target, entry));
if (childNewest > newest) newest = childNewest;
}
return newest;
}
export function paperclipRunnerBinaryNeedsBuild({
repoRoot,
nativeRunnerRequired,
configuredBinary,
platform = process.platform,
architecture = process.arch,
}) {
if (!nativeRunnerRequired) return false;
if (configuredBinary?.trim()) return false;
const executable = platform === "win32" ? "paperclip-runnerd.exe" : "paperclip-runnerd";
const packageRoot = path.join(repoRoot, "packages", "paperclip-runner");
// Match production resolution: a staged release target takes precedence over
// the flat source-build output. Remote Mac builds must not trigger Cargo just
// because only the normal darwin-<arch> release executable is present.
let binaryStat;
for (const binary of [
path.join(packageRoot, "dist", "bin", `${platform}-${architecture}`, executable),
path.join(packageRoot, "dist", "bin", executable),
]) {
const candidate = statSync(binary, { throwIfNoEntry: false });
if (candidate?.isFile()) { binaryStat = candidate; break; }
}
if (!binaryStat) return true;
const runnerRoot = path.join(packageRoot, "runner");
const buildInputs = [
path.join(runnerRoot, "Cargo.toml"),
path.join(runnerRoot, "Cargo.lock"),
path.join(runnerRoot, ".cargo"),
path.join(runnerRoot, "rust-toolchain"),
path.join(runnerRoot, "rust-toolchain.toml"),
path.join(runnerRoot, "crates"),
];
return buildInputs.some((input) => newestMtimeMs(input) > binaryStat.mtimeMs);
}