singularity-forge/src/tool-bootstrap.ts
Mikael Hugo b24f426f2b batch: snapshot of in-flight v2 work
This commit captures uncommitted modifications that accumulated in the
working tree across multiple in-progress workstreams. It is a snapshot
to clear the deck before sf v3 work begins; individual workstreams
should land separately on top of this.

Notable additions:
- trace-collector.ts, traces.ts, src/tests/trace-export.test.ts —
  trace export plumbing
- biome.json — Biome linter configuration
- .gitignore — exclude native/npm/**/*.node compiled binaries

The bulk of the diff is across src/resources/extensions/sf/ (301 files)
and src/resources/extensions/sf/tests/ (277 files), reflecting the
ongoing sf extension work. Specific feature commits should follow this
snapshot rather than being archaeology'd out of it.

The 76MB native/npm/linux-x64-gnu/forge_engine.node compiled binary
was left out of the commit — it's now gitignored and built locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 12:42:31 +02:00

168 lines
3.6 KiB
TypeScript

import {
chmodSync,
copyFileSync,
existsSync,
lstatSync,
mkdirSync,
rmSync,
statSync,
symlinkSync,
unlinkSync,
} from "node:fs";
import { delimiter, join } from "node:path";
type ManagedTool = "fd" | "rg";
interface ToolSpec {
targetName: string;
candidates: string[];
}
const TOOL_SPECS: Record<ManagedTool, ToolSpec> = {
fd: {
targetName: process.platform === "win32" ? "fd.exe" : "fd",
candidates:
process.platform === "win32"
? ["fd.exe", "fd", "fdfind.exe", "fdfind"]
: ["fd", "fdfind"],
},
rg: {
targetName: process.platform === "win32" ? "rg.exe" : "rg",
candidates: process.platform === "win32" ? ["rg.exe", "rg"] : ["rg"],
},
};
function splitPath(pathValue: string | undefined): string[] {
if (!pathValue) return [];
return pathValue
.split(delimiter)
.map((segment) => segment.trim())
.filter(Boolean);
}
function getCandidateNames(name: string): string[] {
if (process.platform !== "win32") return [name];
const lower = name.toLowerCase();
if (
lower.endsWith(".exe") ||
lower.endsWith(".cmd") ||
lower.endsWith(".bat")
)
return [name];
return [name, `${name}.exe`, `${name}.cmd`, `${name}.bat`];
}
function isRegularFile(path: string): boolean {
try {
const stat = lstatSync(path);
return stat.isFile() || stat.isSymbolicLink();
} catch {
return false;
}
}
function pathExistsIncludingBrokenSymlink(path: string): boolean {
try {
lstatSync(path);
return true;
} catch {
return false;
}
}
function isBrokenSymlink(path: string): boolean {
try {
const stat = lstatSync(path);
if (!stat.isSymbolicLink()) return false;
try {
statSync(path);
return false;
} catch {
return true;
}
} catch {
return false;
}
}
function removeTargetPath(path: string): void {
try {
const stat = lstatSync(path);
if (stat.isSymbolicLink()) {
unlinkSync(path);
return;
}
rmSync(path, { force: true });
} catch {
// Path already absent.
}
}
export function resolveToolFromPath(
tool: ManagedTool,
pathValue: string | undefined = process.env.PATH,
): string | null {
const spec = TOOL_SPECS[tool];
for (const dir of splitPath(pathValue)) {
for (const candidate of spec.candidates) {
for (const name of getCandidateNames(candidate)) {
const fullPath = join(dir, name);
if (existsSync(fullPath) && isRegularFile(fullPath)) {
return fullPath;
}
}
}
}
return null;
}
function provisionTool(
targetDir: string,
tool: ManagedTool,
sourcePath: string,
): string {
const targetPath = join(targetDir, TOOL_SPECS[tool].targetName);
const brokenTarget = isBrokenSymlink(targetPath);
if (pathExistsIncludingBrokenSymlink(targetPath)) {
if (!brokenTarget) return targetPath;
removeTargetPath(targetPath);
}
mkdirSync(targetDir, { recursive: true });
if (!brokenTarget) {
try {
symlinkSync(sourcePath, targetPath);
return targetPath;
} catch {
// Fall back to copying below.
}
}
removeTargetPath(targetPath);
copyFileSync(sourcePath, targetPath);
chmodSync(targetPath, 0o755);
return targetPath;
}
export function ensureManagedTools(
targetDir: string,
pathValue: string | undefined = process.env.PATH,
): string[] {
const provisioned: string[] = [];
for (const tool of Object.keys(TOOL_SPECS) as ManagedTool[]) {
const targetPath = join(targetDir, TOOL_SPECS[tool].targetName);
if (
pathExistsIncludingBrokenSymlink(targetPath) &&
!isBrokenSymlink(targetPath)
)
continue;
const sourcePath = resolveToolFromPath(tool, pathValue);
if (!sourcePath) continue;
provisioned.push(provisionTool(targetDir, tool, sourcePath));
}
return provisioned;
}