86 lines
2.6 KiB
JavaScript
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 };
|