Files
aetherbound-guild/tools/browser_temp.js
T

86 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const RUNNER_ID = "aetherbound-browser-temp-v1";
const OWNER_FILE = ".abg-browser-owner.json";
function safeLabel(value) {
const normalized = String(value || "browser").replace(/[^A-Za-z0-9_.-]+/g, "-");
return normalized.replace(/^-+|-+$/g, "") || "browser";
}
function processAlive(pid) {
if (!Number.isInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch (error) {
return error && error.code === "EPERM";
}
}
function cleanupStaleOwnedRoots(parent) {
for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const candidate = path.join(parent, entry.name);
const ownerPath = path.join(candidate, OWNER_FILE);
let owner;
try {
owner = JSON.parse(fs.readFileSync(ownerPath, "utf8"));
} catch (_error) {
continue;
}
if (owner.createdBy !== RUNNER_ID || processAlive(Number(owner.pid))) continue;
fs.rmSync(candidate, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
}
}
function installBrowserTemp(label) {
const configuredParent = process.env.ABG_BROWSER_TEMP_PARENT;
const parent = path.resolve(configuredParent || path.join(os.tmpdir(), "aetherbound-browser-runs"));
fs.mkdirSync(parent, { recursive: true });
cleanupStaleOwnedRoots(parent);
const root = fs.mkdtempSync(path.join(parent, `${safeLabel(label)}-`));
fs.writeFileSync(
path.join(root, OWNER_FILE),
`${JSON.stringify({ createdBy: RUNNER_ID, pid: process.pid, label: safeLabel(label) })}\n`,
);
// Playwright and Chrome consult these variables when creating profiles,
// artifacts and macOS code-signing copies. Configure them before Playwright
// is required by the calling script.
process.env.TMPDIR = root;
process.env.TMP = root;
process.env.TEMP = root;
let cleaned = false;
const cleanup = () => {
if (cleaned) return;
cleaned = true;
fs.rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
if (!configuredParent) {
try {
fs.rmdirSync(parent);
} catch (_error) {
// Another active browser attempt may still own a sibling directory.
}
}
};
process.once("exit", cleanup);
for (const [signal, exitCode] of [["SIGINT", 130], ["SIGTERM", 143], ["SIGHUP", 129]]) {
process.once(signal, () => {
cleanup();
process.exit(exitCode);
});
}
return { root, cleanup };
}
module.exports = { installBrowserTemp };