export type InstallCommandBlock = { label: string; commands: string[]; }; export type NonExecutableBinaryMessageOptions = { binPath: string; trustPackages: string; bunInstallBlocks: InstallCommandBlock[]; genericInstallCommands?: string[]; binaryName?: string; }; export type FsSubset = { accessSync: (path: string, mode?: number) => void; chmodSync: (path: string, mode: number) => void; constants: { X_OK: number }; }; export function isBunRuntime(): boolean { if (typeof process?.versions?.bun === "string") return true; const userAgent = process?.env?.npm_config_user_agent || ""; return userAgent.includes("bun/"); } const PERMISSION_ERRORS = new Set(["EACCES", "EPERM", "ENOEXEC"]); function isPermissionError(error: unknown): boolean { if (!error || typeof error !== "object") return false; const code = (error as { code?: unknown }).code; return typeof code === "string" && PERMISSION_ERRORS.has(code); } /** * Checks if a binary is executable and attempts to make it executable if not. * Returns true if the binary is (or was made) executable, false if it couldn't * be made executable due to permission errors. Throws for other errors. * * Requires fs to be passed in to avoid static imports that break browser builds. */ export function assertExecutable(binPath: string, fs: FsSubset): boolean { if (process.platform === "win32") { return true; } try { fs.accessSync(binPath, fs.constants.X_OK); return true; } catch { // Not executable, try to fix } try { fs.chmodSync(binPath, 0o755); return true; } catch (error) { if (isPermissionError(error)) { return false; } throw error; } } export function formatNonExecutableBinaryMessage( options: NonExecutableBinaryMessageOptions, ): string { const { binPath, trustPackages, bunInstallBlocks, genericInstallCommands, binaryName, } = options; const label = binaryName ?? "sandbox-agent"; const lines = [`${label} binary is not executable: ${binPath}`]; if (isBunRuntime()) { lines.push( "Allow Bun to run postinstall scripts for native binaries and reinstall:", ); for (const block of bunInstallBlocks) { lines.push(`${block.label}:`); for (const command of block.commands) { lines.push(` ${command}`); } } lines.push(`Or run: chmod +x "${binPath}"`); return lines.join("\n"); } lines.push( "Postinstall scripts for native packages did not run, so the binary was left non-executable.", ); if (genericInstallCommands && genericInstallCommands.length > 0) { lines.push("Reinstall with scripts enabled:"); for (const command of genericInstallCommands) { lines.push(` ${command}`); } } else { lines.push("Reinstall with scripts enabled for:"); lines.push(` ${trustPackages}`); } lines.push(`Or run: chmod +x "${binPath}"`); return lines.join("\n"); }