#!/usr/bin/env bun // Normalize Bun-generated .bin entries to stable symlinks or byte-stable wrappers. import { chmod, lstat, readdir, readFile, readlink, symlink, unlink, writeFile, } from "node:fs/promises"; import { basename, dirname, join, relative, resolve } from "node:path"; const nodeModulesRoot = resolve(process.cwd(), process.argv[2] ?? "node_modules"); async function pathExists(path: string): Promise { try { await lstat(path); return true; } catch { return false; } } function lexicalSort(values: Iterable): string[] { return [...values].sort((left, right) => left.localeCompare(right, "en")); } function commandNameFromPackage(name: string): string { const segments = name.split("/"); return segments[segments.length - 1]!; } function normalizeRelativeTarget(fromDir: string, targetPath: string): string { const target = relative(fromDir, targetPath) || "."; return target.replaceAll(/\/+/g, "/"); } function isBinDirectory(path: string): boolean { return basename(path) === ".bin" && dirname(path).endsWith("node_modules"); } async function readSortedDir(path: string): Promise { return lexicalSort(await readdir(path)); } async function collectPackageDirectories(nodeModulesDir: string): Promise { const packageDirs: string[] = []; for (const entry of await readSortedDir(nodeModulesDir)) { if (entry === ".bin") { continue; } const entryPath = join(nodeModulesDir, entry); const stats = await lstat(entryPath); if (!stats.isDirectory() && !stats.isSymbolicLink()) { continue; } if (entry.startsWith("@")) { for (const scopedEntry of await readSortedDir(entryPath).catch(() => [])) { packageDirs.push(join(entryPath, scopedEntry)); } continue; } packageDirs.push(entryPath); } return packageDirs; } async function readPackageJson(path: string): Promise { try { return JSON.parse(await readFile(path, "utf8")); } catch { return null; } } async function collectBinCandidates(nodeModulesDir: string): Promise> { const bins = new Map(); for (const packageDir of await collectPackageDirectories(nodeModulesDir)) { const packageJson = await readPackageJson(join(packageDir, "package.json")); if (packageJson === null || packageJson.bin === undefined) { continue; } const register = (command: string, relativeTarget: string) => { const targetPath = resolve(packageDir, relativeTarget); const candidates = bins.get(command) ?? []; candidates.push(targetPath); bins.set(command, candidates); }; if (typeof packageJson.bin === "string" && typeof packageJson.name === "string") { register(commandNameFromPackage(packageJson.name), packageJson.bin); continue; } if (packageJson.bin && typeof packageJson.bin === "object") { for (const command of lexicalSort(Object.keys(packageJson.bin))) { const relativeTarget = packageJson.bin[command]; if (typeof relativeTarget === "string") { register(command, relativeTarget); } } } } for (const [command, targets] of bins) { bins.set(command, lexicalSort(new Set(targets))); } return bins; } async function resolveCurrentTarget(binPath: string): Promise { const stats = await lstat(binPath); if (stats.isSymbolicLink()) { const currentTarget = await readlink(binPath); return resolve(dirname(binPath), currentTarget); } if (!stats.isFile()) { return null; } const content = await readFile(binPath, "utf8"); const launchTargetMatch = content.match(/(?:exec|run)\s+["']?([^"'$\s][^"'$\n]*)["']?/) ?? content.match(/(?:^|\s)(\.\.\/[^\s"'`]+|\.\/[^\s"'`]+)(?:\s|$)/m); if (launchTargetMatch) { return resolve(dirname(binPath), launchTargetMatch[1]!); } return null; } async function rewriteSymlink(binPath: string, targetPath: string): Promise { const nextTarget = normalizeRelativeTarget(dirname(binPath), targetPath); const currentTarget = await readlink(binPath).catch(() => null); if (currentTarget === nextTarget) { return; } await unlink(binPath).catch(() => undefined); await symlink(nextTarget, binPath); } async function writeWrapper(binPath: string, targetPath: string): Promise { const launcherTarget = normalizeRelativeTarget(dirname(binPath), targetPath); const wrapper = `#!/usr/bin/env sh set -eu script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) exec "$script_dir/${launcherTarget}" "$@" `; await writeFile(binPath, wrapper, "utf8"); await chmod(binPath, 0o755); } async function normalizeBinEntry(binDir: string, entry: string, candidates: Map): Promise { const binPath = join(binDir, entry); const stats = await lstat(binPath); const candidateTargets = candidates.get(entry) ?? []; const currentTarget = await resolveCurrentTarget(binPath); let selectedTarget: string | null = null; if (candidateTargets.length === 1) { selectedTarget = candidateTargets[0]!; } else if (currentTarget !== null) { selectedTarget = candidateTargets.find((candidate) => resolve(candidate) === resolve(currentTarget)) ?? null; } if (stats.isSymbolicLink()) { if (selectedTarget !== null && (await pathExists(selectedTarget))) { await rewriteSymlink(binPath, selectedTarget); } return; } if (!stats.isFile()) { return; } if (selectedTarget !== null && (await pathExists(selectedTarget))) { await unlink(binPath); await symlink(normalizeRelativeTarget(dirname(binPath), selectedTarget), binPath); return; } const content = await readFile(binPath, "utf8"); const homePath = process.env.HOME ?? ""; const hasMachinePath = content.includes("/build/") || content.includes("/tmp/") || (homePath.length > 0 && content.includes(homePath)); if (hasMachinePath) { if (currentTarget !== null && (await pathExists(currentTarget))) { await writeWrapper(binPath, currentTarget); } } await chmod(binPath, 0o755); } async function walk(path: string): Promise { const stats = await lstat(path); if (!stats.isDirectory()) { return; } if (isBinDirectory(path)) { const candidates = await collectBinCandidates(dirname(path)); for (const entry of await readSortedDir(path)) { await normalizeBinEntry(path, entry, candidates); } return; } for (const entry of await readSortedDir(path)) { if (entry === "." || entry === "..") { continue; } await walk(join(path, entry)); } } if (await pathExists(nodeModulesRoot)) { await walk(nodeModulesRoot); }