Files
rules_bun/internal/runtime_launcher.js
2026-03-15 13:54:18 +01:00

1260 lines
37 KiB
JavaScript

import { spawn } from "node:child_process";
import {
copyFileSync,
cpSync,
existsSync,
lstatSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
readlinkSync,
realpathSync,
rmSync,
statSync,
symlinkSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { delimiter, dirname, join, posix, relative, resolve } from "node:path";
const IS_WINDOWS = process.platform === "win32";
function fail(message) {
throw new Error(message);
}
function normalizeRel(value) {
if (!value || value === ".") {
return ".";
}
const normalized = posix.normalize(String(value).replace(/\\/g, "/")).replace(/^\.\/+/, "");
if (!normalized || normalized === ".") {
return ".";
}
if (normalized.startsWith("../")) {
fail(`rules_bun: invalid relative path ${value}`);
}
return normalized;
}
function resolveNodeModulesRootRunfilesPath(value, repoRoot) {
const normalized = posix.normalize(String(value || ".").replace(/\\/g, "/")).replace(/^\.\/+/, "");
if (!normalized || normalized === ".") {
return repoRoot || ".";
}
if (repoRoot) {
if (normalized === repoRoot || normalized.startsWith(`${repoRoot}/`)) {
return normalized;
}
const externalRepoPrefix = `../${repoRoot}`;
if (normalized === externalRepoPrefix) {
return repoRoot;
}
if (normalized.startsWith(`${externalRepoPrefix}/`)) {
return normalized.slice(3);
}
}
const repoRelative = normalizeRel(normalized);
return repoRoot ? (repoRelative === "." ? repoRoot : `${repoRoot}/${repoRelative}`) : repoRelative;
}
function dirnameRel(value) {
const normalized = normalizeRel(value);
if (normalized === ".") {
return ".";
}
return normalizeRel(posix.dirname(normalized));
}
function firstPathComponent(value) {
const normalized = normalizeRel(value);
if (normalized === ".") {
return "";
}
return normalized.split("/")[0];
}
function stripRelPrefix(child, parent) {
const normalizedChild = normalizeRel(child);
const normalizedParent = normalizeRel(parent);
if (normalizedParent === ".") {
return normalizedChild;
}
if (normalizedChild === normalizedParent) {
return ".";
}
if (normalizedChild.startsWith(`${normalizedParent}/`)) {
return normalizeRel(normalizedChild.slice(normalizedParent.length + 1));
}
return normalizedChild;
}
function splitManifestLine(line) {
const delimiterIndex = line.indexOf(" ");
if (delimiterIndex < 0) {
return null;
}
return [line.slice(0, delimiterIndex), line.slice(delimiterIndex + 1)];
}
function detectRunfiles() {
const launcherPath = process.env.RULES_BUN_LAUNCHER_PATH || "";
let runfilesDir = process.env.RULES_BUN_RUNFILES_DIR || process.env.RUNFILES_DIR || "";
let manifestFile =
process.env.RULES_BUN_RUNFILES_MANIFEST || process.env.RUNFILES_MANIFEST_FILE || "";
if (!runfilesDir && launcherPath) {
const adjacentDir = `${launcherPath}.runfiles`;
if (existsSync(adjacentDir)) {
runfilesDir = adjacentDir;
}
}
if (!manifestFile && launcherPath) {
const candidates = [`${launcherPath}.runfiles_manifest`, `${launcherPath}.exe.runfiles_manifest`];
for (const candidate of candidates) {
if (existsSync(candidate)) {
manifestFile = candidate;
break;
}
}
}
if (!runfilesDir && !manifestFile) {
fail("rules_bun: unable to locate runfiles");
}
let manifestMap = null;
let manifestDirs = null;
let manifestChildren = null;
if (manifestFile) {
manifestMap = new Map();
manifestDirs = new Set();
manifestChildren = new Map();
const lines = readFileSync(manifestFile, "utf8").split(/\r?\n/);
for (const line of lines) {
if (!line) {
continue;
}
const entry = splitManifestLine(line);
if (!entry) {
continue;
}
const [runfilesPath, actualPath] = entry;
manifestMap.set(runfilesPath, actualPath);
const parts = runfilesPath.split("/");
let parent = ".";
for (let index = 0; index < parts.length; index += 1) {
const part = parts[index];
let children = manifestChildren.get(parent);
if (!children) {
children = new Set();
manifestChildren.set(parent, children);
}
children.add(part);
const current = parent === "." ? part : `${parent}/${part}`;
if (index < parts.length - 1) {
manifestDirs.add(current);
}
parent = current;
}
}
}
function rlocation(runfilesPath) {
const normalized = String(runfilesPath).replace(/\\/g, "/");
if (runfilesDir) {
return resolve(runfilesDir, normalized);
}
const resolved = manifestMap.get(normalized);
if (!resolved) {
fail(`rules_bun: missing runfile ${normalized}`);
}
return resolved;
}
function exists(runfilesPath) {
const normalized = String(runfilesPath).replace(/\\/g, "/");
if (runfilesDir) {
return existsSync(resolve(runfilesDir, normalized));
}
return manifestMap.has(normalized) || manifestDirs.has(normalized);
}
function isDir(runfilesPath) {
const normalized = String(runfilesPath).replace(/\\/g, "/");
if (runfilesDir) {
const target = resolve(runfilesDir, normalized);
if (!existsSync(target)) {
return false;
}
return statSync(target).isDirectory();
}
return manifestDirs.has(normalized);
}
function listChildren(runfilesPath) {
const normalized = runfilesPath ? String(runfilesPath).replace(/\\/g, "/") : ".";
if (runfilesDir) {
const target = normalized === "." ? runfilesDir : resolve(runfilesDir, normalized);
if (!existsSync(target) || !statSync(target).isDirectory()) {
return [];
}
return readdirSync(target).sort();
}
return Array.from(manifestChildren.get(normalized) || []).sort();
}
function directoryIdentity(runfilesPath) {
if (!runfilesDir) {
return String(runfilesPath).replace(/\\/g, "/");
}
return realpathSync(rlocation(runfilesPath));
}
return {
manifestMap,
manifestFile,
runfilesDir,
exists,
isDir,
listChildren,
directoryIdentity,
rlocation,
};
}
function workspaceRunfilesPath(relPath) {
const normalized = normalizeRel(relPath);
if (normalized === ".") {
return "_main";
}
return `_main/${normalized}`;
}
function stripWorkspacePrefix(runfilesPath) {
if (!runfilesPath) {
return "";
}
if (runfilesPath === "_main") {
return ".";
}
if (runfilesPath.startsWith("_main/")) {
return normalizeRel(runfilesPath.slice("_main/".length));
}
return "";
}
function createWorkspaceSource(runfiles) {
if (runfiles.runfilesDir && runfiles.isDir("_main")) {
const workspaceRoot = runfiles.rlocation("_main");
return {
type: "dir",
workspaceRoot,
exists(relPath) {
return existsSync(resolve(workspaceRoot, normalizeRel(relPath)));
},
isFile(relPath) {
const target = resolve(workspaceRoot, normalizeRel(relPath));
return existsSync(target) && statSync(target).isFile();
},
isDir(relPath) {
const target = resolve(workspaceRoot, normalizeRel(relPath));
return existsSync(target) && statSync(target).isDirectory();
},
listChildren(relPath) {
const normalized = normalizeRel(relPath);
const target = normalized === "." ? workspaceRoot : resolve(workspaceRoot, normalized);
if (!existsSync(target) || !statSync(target).isDirectory()) {
return [];
}
return readdirSync(target).sort();
},
actualFile(relPath) {
const normalized = normalizeRel(relPath);
return normalized === "." ? workspaceRoot : resolve(workspaceRoot, normalized);
},
};
}
const fileMap = new Map();
const dirSet = new Set(["."]);
const children = new Map();
for (const [runfilesPath, actualPath] of runfiles.manifestMap.entries()) {
if (!runfilesPath.startsWith("_main/")) {
continue;
}
const relPath = normalizeRel(runfilesPath.slice("_main/".length));
fileMap.set(relPath, actualPath);
const parts = relPath.split("/");
let parent = ".";
for (let index = 0; index < parts.length; index += 1) {
const part = parts[index];
let childrenForParent = children.get(parent);
if (!childrenForParent) {
childrenForParent = new Set();
children.set(parent, childrenForParent);
}
childrenForParent.add(part);
const current = parent === "." ? part : `${parent}/${part}`;
if (index < parts.length - 1) {
dirSet.add(current);
}
parent = current;
}
}
return {
type: "manifest",
exists(relPath) {
const normalized = normalizeRel(relPath);
return fileMap.has(normalized) || dirSet.has(normalized);
},
isFile(relPath) {
return fileMap.has(normalizeRel(relPath));
},
isDir(relPath) {
return dirSet.has(normalizeRel(relPath));
},
listChildren(relPath) {
return Array.from(children.get(normalizeRel(relPath)) || []).sort();
},
actualFile(relPath) {
return fileMap.get(normalizeRel(relPath)) || "";
},
fileMap,
};
}
function findPackageRelDirForPath(source, relPath) {
let current = normalizeRel(relPath);
if (source.isFile(current)) {
current = dirnameRel(current);
}
while (true) {
if (source.isFile(current === "." ? "package.json" : `${current}/package.json`)) {
return current;
}
if (current === ".") {
break;
}
current = dirnameRel(current);
}
return dirnameRel(relPath);
}
function findWorkingRelDirForPath(source, relPath) {
let current = normalizeRel(relPath);
if (source.isFile(current)) {
current = dirnameRel(current);
}
while (true) {
const envPath = current === "." ? ".env" : `${current}/.env`;
const manifestPath = current === "." ? "package.json" : `${current}/package.json`;
if (source.isFile(envPath) || source.isFile(manifestPath)) {
return current;
}
if (current === ".") {
break;
}
current = dirnameRel(current);
}
return dirnameRel(relPath);
}
function resolvePackageRelDir(spec, source) {
if (spec.package_dir_hint && spec.package_dir_hint !== ".") {
return normalizeRel(spec.package_dir_hint);
}
const packageJsonRel = stripWorkspacePrefix(spec.package_json_short_path || "");
if (packageJsonRel) {
return findPackageRelDirForPath(source, packageJsonRel);
}
const primaryRel = stripWorkspacePrefix(spec.primary_source_short_path || "");
if (primaryRel) {
return findPackageRelDirForPath(source, primaryRel);
}
return ".";
}
function resolveExecutionRelDir(spec, packageRelDir, source) {
switch (spec.working_dir_mode) {
case "workspace":
return ".";
case "package":
return packageRelDir;
case "entry_point": {
const primaryRel = stripWorkspacePrefix(spec.primary_source_short_path || "");
if (primaryRel) {
return findWorkingRelDirForPath(source, primaryRel);
}
return packageRelDir;
}
default:
return packageRelDir;
}
}
function resolveInstallRootRelDir(spec, packageRelDir, runfiles) {
const installMetadataPath =
spec.install_metadata_short_path && runfiles.exists(spec.install_metadata_short_path)
? runfiles.rlocation(spec.install_metadata_short_path)
: "";
if (installMetadataPath && existsSync(installMetadataPath)) {
try {
const metadata = JSON.parse(readFileSync(installMetadataPath, "utf8"));
const normalizedPackageRelDir = packageRelDir === "." ? "." : packageRelDir.replace(/^\.\/+/, "");
const matches = [];
for (const workspacePackageDir of metadata.workspace_package_dirs || []) {
const normalizedWorkspaceDir = normalizeRel(workspacePackageDir);
if (normalizedWorkspaceDir === ".") {
continue;
}
if (normalizedPackageRelDir === normalizedWorkspaceDir) {
matches.push([normalizedWorkspaceDir.length, "."]);
continue;
}
const suffix = `/${normalizedWorkspaceDir}`;
if (normalizedPackageRelDir.endsWith(suffix)) {
const prefix = normalizedPackageRelDir.slice(0, -suffix.length).replace(/^\/+|\/+$/g, "");
matches.push([normalizedWorkspaceDir.length, prefix || "."]);
}
}
if (matches.length > 0) {
matches.sort((left, right) => right[0] - left[0]);
return normalizeRel(matches[0][1]);
}
} catch {}
}
const packageJsonRel = stripWorkspacePrefix(spec.package_json_short_path || "");
if (packageJsonRel) {
return findPackageRelDirForPath(createWorkspaceSource(runfiles), packageJsonRel);
}
const primaryRel = stripWorkspacePrefix(spec.primary_source_short_path || "");
if (primaryRel) {
return findPackageRelDirForPath(createWorkspaceSource(runfiles), primaryRel);
}
return ".";
}
function removePath(targetPath) {
rmSync(targetPath, { force: true, recursive: true });
}
function ensureDir(targetPath) {
mkdirSync(targetPath, { recursive: true });
}
function linkPath(sourcePath, destinationPath) {
removePath(destinationPath);
ensureDir(dirname(destinationPath));
const sourceStats = lstatSync(sourcePath);
if (sourceStats.isSymbolicLink()) {
symlinkSync(readlinkSync(sourcePath), destinationPath);
return;
}
symlinkSync(sourcePath, destinationPath);
}
function copyPath(sourcePath, destinationPath) {
removePath(destinationPath);
ensureDir(dirname(destinationPath));
const resolvedStats = statSync(sourcePath);
if (resolvedStats.isDirectory()) {
cpSync(sourcePath, destinationPath, { dereference: true, force: true, recursive: true });
} else {
copyFileSync(sourcePath, destinationPath);
}
}
function materializePath(sourcePath, destinationPath, preferLinks) {
if (preferLinks && !IS_WINDOWS) {
linkPath(sourcePath, destinationPath);
} else {
copyPath(sourcePath, destinationPath);
}
}
function materializeTreeContents(sourceRoot, destinationRoot) {
removePath(destinationRoot);
ensureDir(destinationRoot);
for (const entry of readdirSync(sourceRoot)) {
const sourcePath = join(sourceRoot, entry);
const destinationPath = join(destinationRoot, entry);
copyPath(sourcePath, destinationPath);
}
}
function stageRuntimeToolAlias(sourcePath, destinationPath, preferLinks) {
removePath(destinationPath);
ensureDir(dirname(destinationPath));
if (preferLinks && !IS_WINDOWS) {
symlinkSync(sourcePath, destinationPath);
return;
}
copyPath(sourcePath, destinationPath);
}
function stageRuntimeToolBin(runtimeWorkspace, bunPath, preferLinks) {
const runtimeToolBin = join(runtimeWorkspace, ".rules_bun", "bin");
ensureDir(runtimeToolBin);
const bunName = IS_WINDOWS ? "bun.exe" : "bun";
const stagedBunPath = join(runtimeToolBin, bunName);
materializePath(realpathSync(bunPath), stagedBunPath, preferLinks);
for (const aliasName of IS_WINDOWS ? ["bunx.exe", "node.exe"] : ["bunx", "node"]) {
stageRuntimeToolAlias(stagedBunPath, join(runtimeToolBin, aliasName), preferLinks);
}
return runtimeToolBin;
}
function stageWorkspaceView(sourceRoot, destinationRoot, packageRelDir) {
ensureDir(destinationRoot);
const skippedEntry = firstPathComponent(packageRelDir);
for (const entry of readdirSync(sourceRoot)) {
if (entry === skippedEntry) {
continue;
}
materializePath(join(sourceRoot, entry), join(destinationRoot, entry), true);
}
if (packageRelDir === ".") {
return;
}
const parts = packageRelDir.split("/");
let sourceCursor = sourceRoot;
let destinationCursor = destinationRoot;
for (let index = 0; index < parts.length - 1; index += 1) {
const part = parts[index];
const nextPart = parts[index + 1];
sourceCursor = join(sourceCursor, part);
destinationCursor = join(destinationCursor, part);
ensureDir(destinationCursor);
for (const sibling of readdirSync(sourceCursor)) {
if (sibling === nextPart) {
continue;
}
const siblingPath = join(destinationCursor, sibling);
if (existsSync(siblingPath)) {
continue;
}
materializePath(join(sourceCursor, sibling), siblingPath, true);
}
}
const packageDestination = join(destinationRoot, packageRelDir);
ensureDir(packageDestination);
for (const entry of readdirSync(join(sourceRoot, packageRelDir))) {
materializePath(join(sourceRoot, packageRelDir, entry), join(packageDestination, entry), true);
}
}
function materializeWorkspaceFromManifest(source, destinationRoot) {
ensureDir(destinationRoot);
for (const [relPath, actualPath] of source.fileMap.entries()) {
copyPath(actualPath, join(destinationRoot, relPath));
}
}
function buildWorkspacePackageMap(workspaceRoot) {
const packageMap = new Map();
function walk(currentDir, relDir) {
const entries = readdirSync(currentDir, { withFileTypes: true });
const hasPackageJson = entries.some((entry) => entry.isFile() && entry.name === "package.json");
if (hasPackageJson) {
const manifestPath = join(currentDir, "package.json");
try {
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
if (typeof manifest.name === "string" && manifest.name) {
packageMap.set(manifest.name, relDir || ".");
}
} catch {}
}
for (const entry of entries) {
if (!entry.isDirectory() || entry.name === "node_modules") {
continue;
}
walk(join(currentDir, entry.name), relDir ? `${relDir}/${entry.name}` : entry.name);
}
}
walk(workspaceRoot, "");
return packageMap;
}
function readInstallMetadata(spec, runfiles) {
if (!spec.install_metadata_short_path || !runfiles.exists(spec.install_metadata_short_path)) {
return null;
}
try {
return JSON.parse(readFileSync(runfiles.rlocation(spec.install_metadata_short_path), "utf8"));
} catch {
return null;
}
}
function workspacePackageMapFromInstallMetadata(installMetadata, installRootRelDir) {
const packageMap = new Map();
const packagesByDir = installMetadata?.workspace_package_names_by_dir || {};
for (const [workspaceDir, packageName] of Object.entries(packagesByDir)) {
if (typeof packageName !== "string" || !packageName) {
continue;
}
const normalizedWorkspaceDir = normalizeRel(workspaceDir);
const rootedWorkspaceDir =
installRootRelDir && installRootRelDir !== "."
? normalizeRel(join(installRootRelDir, normalizedWorkspaceDir).replace(/\\/g, "/"))
: normalizedWorkspaceDir;
packageMap.set(packageName, rootedWorkspaceDir);
}
return packageMap;
}
function findNodeModulesRoots(spec, runfiles) {
const repoRoot = spec.install_repo_runfiles_path || "";
const results = [];
for (const relativeRoot of spec.node_modules_roots || []) {
const runfilesPath = resolveNodeModulesRootRunfilesPath(relativeRoot, repoRoot);
if (runfiles.isDir(runfilesPath)) {
results.push(runfilesPath);
}
}
return results.sort((left, right) => left.length - right.length || left.localeCompare(right));
}
function selectPrimaryNodeModules(roots) {
return roots[0] || "";
}
function readPackageNameFromRunfilesEntry(runfiles, entryRunfilesPath) {
const packageJsonPath = `${entryRunfilesPath}/package.json`;
if (!runfiles.exists(packageJsonPath)) {
return "";
}
try {
const manifest = JSON.parse(readFileSync(runfiles.rlocation(packageJsonPath), "utf8"));
return typeof manifest.name === "string" ? manifest.name : "";
} catch {
return "";
}
}
function materializeRunfilesSubtree(runfiles, prefix, destinationPath) {
if (runfiles.runfilesDir && existsSync(runfiles.rlocation(prefix))) {
copyPath(runfiles.rlocation(prefix), destinationPath);
return;
}
const normalizedPrefix = prefix.replace(/\\/g, "/");
let copied = false;
for (const [runfilesPath, actualPath] of runfiles.manifestMap.entries()) {
if (runfilesPath === normalizedPrefix) {
copyPath(actualPath, destinationPath);
copied = true;
break;
}
if (!runfilesPath.startsWith(`${normalizedPrefix}/`)) {
continue;
}
const relPath = runfilesPath.slice(normalizedPrefix.length + 1);
copyPath(actualPath, join(destinationPath, relPath));
copied = true;
}
if (!copied) {
removePath(destinationPath);
}
}
function materializeNodeModulesEntry(
runfiles,
sourceRunfilesPath,
destinationPath,
workspacePackageMap,
runtimeWorkspace,
preferLinks,
) {
const packageName = readPackageNameFromRunfilesEntry(runfiles, sourceRunfilesPath);
const workspaceRelDir = packageName ? workspacePackageMap.get(packageName) || "" : "";
if (workspaceRelDir) {
const workspaceSourcePath = join(runtimeWorkspace, workspaceRelDir);
if (existsSync(workspaceSourcePath)) {
materializePath(workspaceSourcePath, destinationPath, preferLinks);
return;
}
}
if (runfiles.runfilesDir && existsSync(runfiles.rlocation(sourceRunfilesPath))) {
materializePath(runfiles.rlocation(sourceRunfilesPath), destinationPath, preferLinks);
return;
}
materializeRunfilesSubtree(runfiles, sourceRunfilesPath, destinationPath);
}
function mirrorNodeModulesDir(
runfiles,
sourceNodeModulesRunfilesPath,
destinationDir,
workspacePackageMap,
runtimeWorkspace,
preferLinks,
) {
removePath(destinationDir);
ensureDir(destinationDir);
for (const entry of runfiles.listChildren(sourceNodeModulesRunfilesPath)) {
if (entry === ".rules_bun") {
continue;
}
const entryRunfilesPath = `${sourceNodeModulesRunfilesPath}/${entry}`;
if (entry.startsWith("@") && runfiles.isDir(entryRunfilesPath)) {
ensureDir(join(destinationDir, entry));
for (const scopedEntry of runfiles.listChildren(entryRunfilesPath)) {
materializeNodeModulesEntry(
runfiles,
`${entryRunfilesPath}/${scopedEntry}`,
join(destinationDir, entry, scopedEntry),
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
continue;
}
materializeNodeModulesEntry(
runfiles,
entryRunfilesPath,
join(destinationDir, entry),
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
}
function findInstallRepoNodeModules(runfiles, repoRootRunfilesPath, packageRelDir) {
if (packageRelDir !== ".") {
let candidate = packageRelDir;
while (true) {
const candidateRunfilesPath = `${repoRootRunfilesPath}/${candidate}/node_modules`;
if (runfiles.isDir(candidateRunfilesPath)) {
return candidateRunfilesPath;
}
if (!candidate.includes("/")) {
break;
}
candidate = dirnameRel(candidate);
}
}
const rootNodeModules = `${repoRootRunfilesPath}/node_modules`;
if (runfiles.isDir(rootNodeModules)) {
return rootNodeModules;
}
return "";
}
function mirrorInstallRepoWorkspaceNodeModules(
runfiles,
nodeModulesRoots,
repoRootRunfilesPath,
destinationRoot,
workspacePackageMap,
runtimeWorkspace,
preferLinks,
) {
for (const nodeModulesRunfilesPath of nodeModulesRoots) {
if (nodeModulesRunfilesPath === `${repoRootRunfilesPath}/node_modules`) {
continue;
}
if (!nodeModulesRunfilesPath.startsWith(`${repoRootRunfilesPath}/`)) {
continue;
}
const relPath = nodeModulesRunfilesPath.slice(repoRootRunfilesPath.length + 1);
mirrorNodeModulesDir(
runfiles,
nodeModulesRunfilesPath,
join(destinationRoot, relPath),
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
}
function buildRuntimePath(
runtimeToolBin,
runtimeWorkspace,
runtimePackageDir,
runtimeInstallRoot,
inheritHostPath,
) {
const entries = [];
if (existsSync(runtimeToolBin) && statSync(runtimeToolBin).isDirectory()) {
entries.push(runtimeToolBin);
}
const installBin = join(runtimeInstallRoot, "node_modules", ".bin");
const packageBin = join(runtimePackageDir, "node_modules", ".bin");
const workspaceBin = join(runtimeWorkspace, "node_modules", ".bin");
if (existsSync(installBin) && statSync(installBin).isDirectory()) {
entries.push(installBin);
}
if (
existsSync(packageBin) &&
statSync(packageBin).isDirectory() &&
packageBin !== installBin
) {
entries.push(packageBin);
}
if (
existsSync(workspaceBin) &&
statSync(workspaceBin).isDirectory() &&
workspaceBin !== packageBin &&
workspaceBin !== installBin
) {
entries.push(workspaceBin);
}
if (inheritHostPath && process.env.PATH) {
entries.push(process.env.PATH);
}
return entries.join(delimiter);
}
function pathEnvKey(env) {
for (const key of Object.keys(env)) {
if (key.toUpperCase() === "PATH") {
return key;
}
}
return "PATH";
}
function runtimePathForRunfile(runfiles, runtimeWorkspace, runfilesPath) {
if (!runfilesPath) {
return "";
}
const workspaceRel = stripWorkspacePrefix(runfilesPath);
if (workspaceRel) {
return workspaceRel === "." ? runtimeWorkspace : join(runtimeWorkspace, workspaceRel);
}
return runfiles.rlocation(runfilesPath);
}
function createRuntime(spec, runfiles) {
const source = createWorkspaceSource(runfiles);
const packageRelDir = resolvePackageRelDir(spec, source);
const executionRelDir = resolveExecutionRelDir(spec, packageRelDir, source);
const installRootRelDir = resolveInstallRootRelDir(spec, packageRelDir, runfiles);
const packageRelDirInInstallRoot = stripRelPrefix(packageRelDir, installRootRelDir);
const installMetadata = readInstallMetadata(spec, runfiles);
const runtimeWorkspace = mkdtempSync(join(tmpdir(), "rules_bun_runtime-"));
const preferLinks = source.type === "dir" && !IS_WINDOWS;
if (source.type === "dir") {
stageWorkspaceView(source.workspaceRoot, runtimeWorkspace, packageRelDir);
} else {
materializeWorkspaceFromManifest(source, runtimeWorkspace);
}
const runtimePackageDir =
packageRelDir === "." ? runtimeWorkspace : join(runtimeWorkspace, packageRelDir);
const runtimeInstallRoot =
installRootRelDir === "." ? runtimeWorkspace : join(runtimeWorkspace, installRootRelDir);
const runtimeExecDir =
executionRelDir === "." ? runtimeWorkspace : join(runtimeWorkspace, executionRelDir);
ensureDir(runtimePackageDir);
ensureDir(runtimeInstallRoot);
ensureDir(runtimeExecDir);
const primaryRel = stripWorkspacePrefix(spec.primary_source_short_path || "");
const packageJsonRel = stripWorkspacePrefix(spec.package_json_short_path || "");
if (source.type === "dir" && primaryRel) {
materializeTreeContents(source.actualFile(packageRelDir), runtimePackageDir);
}
if (source.type === "dir" && packageJsonRel) {
materializeTreeContents(source.actualFile(installRootRelDir), runtimeInstallRoot);
}
const workspacePackageMap = installMetadata
? workspacePackageMapFromInstallMetadata(installMetadata, installRootRelDir)
: buildWorkspacePackageMap(runtimeWorkspace);
const nodeModulesRoots = findNodeModulesRoots(spec, runfiles);
const primaryNodeModules = selectPrimaryNodeModules(nodeModulesRoots);
let installRepoRoot = spec.install_repo_runfiles_path || "";
if (primaryNodeModules) {
if (!installRepoRoot) {
installRepoRoot = posix.dirname(primaryNodeModules);
}
ensureDir(runtimeInstallRoot);
mirrorNodeModulesDir(
runfiles,
primaryNodeModules,
join(runtimeInstallRoot, "node_modules"),
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
if (installRepoRoot) {
const resolvedInstallNodeModules = findInstallRepoNodeModules(
runfiles,
installRepoRoot,
packageRelDirInInstallRoot,
);
if (
resolvedInstallNodeModules &&
resolvedInstallNodeModules !== `${installRepoRoot}/node_modules`
) {
mirrorNodeModulesDir(
runfiles,
resolvedInstallNodeModules,
join(runtimePackageDir, "node_modules"),
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
mirrorInstallRepoWorkspaceNodeModules(
runfiles,
nodeModulesRoots,
installRepoRoot,
runtimeInstallRoot,
workspacePackageMap,
runtimeWorkspace,
preferLinks,
);
}
const runtimeInstallNodeModules = join(runtimeInstallRoot, "node_modules");
const runtimePackageNodeModules = join(runtimePackageDir, "node_modules");
if (
!existsSync(runtimePackageNodeModules) &&
existsSync(runtimeInstallNodeModules) &&
runtimeInstallNodeModules !== runtimePackageNodeModules
) {
materializePath(runtimeInstallNodeModules, runtimePackageNodeModules, preferLinks);
}
const runtimeToolBin = stageRuntimeToolBin(
runtimeWorkspace,
runfiles.rlocation(spec.bun_short_path),
preferLinks,
);
const env = { ...process.env };
const pathKey = pathEnvKey(env);
const runtimePath = buildRuntimePath(
runtimeToolBin,
runtimeWorkspace,
runtimePackageDir,
runtimeInstallRoot,
Boolean(spec.inherit_host_path),
);
if (runtimePath || !spec.inherit_host_path) {
env[pathKey] = runtimePath;
}
return {
env,
runtimeExecDir,
runtimeInstallRoot,
runtimePackageDir,
runtimeWorkspace,
cleanup() {
removePath(runtimeWorkspace);
},
};
}
function composeBunArgs(spec, runfiles, runtime) {
const args = [...(spec.argv || [])];
for (const preloadPath of spec.preload_short_paths || []) {
args.push("--preload", runtimePathForRunfile(runfiles, runtime.runtimeWorkspace, preloadPath));
}
for (const envFilePath of spec.env_file_short_paths || []) {
args.push("--env-file", runtimePathForRunfile(runfiles, runtime.runtimeWorkspace, envFilePath));
}
if (spec.kind === "bun_test") {
let coverageRequested = false;
let coverageDir = "";
if (process.env.COVERAGE_DIR) {
coverageRequested = true;
coverageDir = process.env.COVERAGE_DIR;
} else if (spec.coverage) {
coverageRequested = true;
coverageDir = process.env.TEST_UNDECLARED_OUTPUTS_DIR || join(runtime.runtimeWorkspace, "coverage");
}
if (coverageRequested) {
args.push("--coverage", "--coverage-dir", coverageDir);
if ((spec.coverage_reporters || []).length > 0) {
for (const reporter of spec.coverage_reporters) {
args.push("--coverage-reporter", reporter);
}
} else if (process.env.COVERAGE_DIR) {
args.push("--coverage-reporter", "lcov");
}
}
if (process.env.TESTBRIDGE_TEST_ONLY) {
args.push("--test-name-pattern", process.env.TESTBRIDGE_TEST_ONLY);
}
if (spec.reporter === "junit") {
const reporterOut = process.env.XML_OUTPUT_FILE || join(runtime.runtimeWorkspace, "junit.xml");
args.push("--reporter", "junit", "--reporter-outfile", reporterOut);
} else if (spec.reporter === "dots") {
args.push("--reporter", "dots");
}
for (const testPath of spec.test_short_paths || []) {
args.push(runtimePathForRunfile(runfiles, runtime.runtimeWorkspace, testPath));
}
} else if (spec.primary_source_short_path) {
args.push(runtimePathForRunfile(runfiles, runtime.runtimeWorkspace, spec.primary_source_short_path));
}
args.push(...(spec.args || []));
return args;
}
function isWindowsBatchFile(command) {
return IS_WINDOWS && /\.(cmd|bat)$/i.test(String(command || ""));
}
function quoteForWindowsShell(command) {
return `"${String(command).replace(/"/g, '""')}"`;
}
function spawnChild(command, args, options) {
const spawnOptions = {
...options,
stdio: "inherit",
};
if (isWindowsBatchFile(command)) {
return spawn(quoteForWindowsShell(command), args, {
...spawnOptions,
shell: true,
});
}
return spawn(command, args, spawnOptions);
}
function spawnProcess(command, args, options) {
return new Promise((resolvePromise, rejectPromise) => {
const child = spawnChild(command, args, options);
child.once("error", rejectPromise);
child.once("exit", (code, signal) => {
resolvePromise({ child, code, signal });
});
});
}
async function runBunOnce(spec, runfiles, extraBunArgs = []) {
const runtime = createRuntime(spec, runfiles);
try {
const bunPath = runfiles.rlocation(spec.bun_short_path);
const bunArgs = composeBunArgs(spec, runfiles, runtime);
const result = await spawnProcess(bunPath, [...bunArgs, ...extraBunArgs], {
cwd: runtime.runtimeExecDir,
env: runtime.env,
});
return typeof result.code === "number" ? result.code : 1;
} finally {
runtime.cleanup();
}
}
function fileMtime(filePath) {
if (!filePath || !existsSync(filePath)) {
return "missing";
}
return `${statSync(filePath).mtimeMs}`;
}
function sleep(ms) {
return new Promise((resolvePromise) => {
setTimeout(resolvePromise, ms);
});
}
async function terminateChild(child) {
if (!child || child.exitCode !== null) {
return;
}
await new Promise((resolvePromise) => {
let finished = false;
const finalize = () => {
if (finished) {
return;
}
finished = true;
resolvePromise();
};
child.once("exit", finalize);
child.kill();
setTimeout(() => {
if (child.exitCode === null) {
child.kill("SIGKILL");
}
finalize();
}, 2000);
});
}
async function runDevMode(spec, runfiles, userArgs) {
const watchFlag = spec.watch_mode === "hot" ? "--hot" : "--watch";
const restartPaths = (spec.restart_on || []).map((runfilesPath) => runfiles.rlocation(runfilesPath));
const passthroughArgs = spec.passthrough_args ? userArgs : [];
if (restartPaths.length === 0) {
return runBunOnce(spec, runfiles, [watchFlag, ...passthroughArgs]);
}
let currentRuntime = null;
let child = null;
let exitPromise = null;
let shuttingDown = false;
const launch = () => {
currentRuntime = createRuntime(spec, runfiles);
const bunPath = runfiles.rlocation(spec.bun_short_path);
const bunArgs = [...composeBunArgs(spec, runfiles, currentRuntime), watchFlag, ...passthroughArgs];
child = spawnChild(bunPath, bunArgs, {
cwd: currentRuntime.runtimeExecDir,
env: currentRuntime.env,
});
exitPromise = new Promise((resolvePromise, rejectPromise) => {
child.once("error", rejectPromise);
child.once("exit", (code, signal) => resolvePromise({ code, signal }));
});
};
const cleanup = async () => {
shuttingDown = true;
await terminateChild(child);
if (currentRuntime) {
currentRuntime.cleanup();
currentRuntime = null;
}
};
const signalHandler = async () => {
await cleanup();
process.exit(1);
};
process.once("SIGINT", signalHandler);
process.once("SIGTERM", signalHandler);
try {
launch();
const mtimes = new Map();
for (const restartPath of restartPaths) {
mtimes.set(restartPath, fileMtime(restartPath));
}
while (!shuttingDown) {
const tick = sleep(1000).then(() => ({ type: "tick" }));
const childResult = exitPromise.then((result) => ({ type: "exit", result }));
const outcome = await Promise.race([tick, childResult]);
if (outcome.type === "exit") {
return typeof outcome.result.code === "number" ? outcome.result.code : 1;
}
let changed = false;
for (const restartPath of restartPaths) {
const currentMtime = fileMtime(restartPath);
if (currentMtime !== mtimes.get(restartPath)) {
mtimes.set(restartPath, currentMtime);
changed = true;
}
}
if (!changed) {
continue;
}
await terminateChild(child);
currentRuntime.cleanup();
launch();
}
return 1;
} finally {
process.removeListener("SIGINT", signalHandler);
process.removeListener("SIGTERM", signalHandler);
await cleanup();
}
}
async function runToolExec(spec, runfiles, userArgs) {
const runtime = createRuntime(spec, runfiles);
try {
const toolPath = runfiles.rlocation(spec.tool_short_path);
const args = [...(spec.args || [])];
if (spec.passthrough_args) {
args.push(...userArgs);
}
const result = await spawnProcess(toolPath, args, {
cwd: runtime.runtimeExecDir,
env: runtime.env,
});
return typeof result.code === "number" ? result.code : 1;
} finally {
runtime.cleanup();
}
}
async function main() {
const specPath = process.argv[2];
if (!specPath) {
fail("rules_bun: expected launcher spec path");
}
const spec = JSON.parse(readFileSync(specPath, "utf8"));
if (spec.version !== 1) {
fail(`rules_bun: unsupported launcher spec version ${spec.version}`);
}
const runfiles = detectRunfiles();
const userArgs = process.argv.slice(3);
let exitCode = 1;
if (spec.kind === "tool_exec") {
exitCode = await runToolExec(spec, runfiles, userArgs);
} else if (spec.kind === "bun_test") {
exitCode = await runBunOnce(spec, runfiles, spec.passthrough_args ? userArgs : []);
} else if (spec.kind === "bun_run" && spec.watch_mode) {
exitCode = await runDevMode(spec, runfiles, userArgs);
} else if (spec.kind === "bun_run") {
exitCode = await runBunOnce(spec, runfiles, spec.passthrough_args ? userArgs : []);
} else {
fail(`rules_bun: unsupported launcher kind ${spec.kind}`);
}
process.exit(exitCode);
}
main().catch((error) => {
const message = error && error.stack ? error.stack : String(error);
process.stderr.write(`${message}\n`);
process.exit(1);
});