/** * Pure utility functions — no pi API, no Julia processes, no I/O. * Extracted here so they can be unit-tested independently. */ import * as fs from "node:fs"; import * as path from "node:path"; // ─── Path resolution ────────────────────────────────────────────────────────── /** Walk upward from startDir until a Project.toml is found. */ export function findProjectToml(startDir: string): string | undefined { let dir = startDir; while (true) { if (fs.existsSync(path.join(dir, "Project.toml"))) return dir; const parent = path.dirname(dir); if (parent === dir) return undefined; dir = parent; } } /** * Resolve a raw path argument to an absolute project directory. * Mirrors `julia --project=` semantics: * "" / "." → search upward from cwd for nearest Project.toml * relative / absolute → must contain Project.toml * path/to/Project.toml → use its directory */ export function resolveProjectPath(arg: string, cwd: string): string | undefined { const t = arg.trim(); if (!t || t === ".") return findProjectToml(cwd); const resolved = path.resolve(cwd, t); if (path.basename(resolved) === "Project.toml") return path.dirname(resolved); if (fs.existsSync(path.join(resolved, "Project.toml"))) return resolved; return undefined; } // ─── Port allocation ────────────────────────────────────────────────────────── /** Stable port in 3001-3999 derived from the project path string. */ export function projectPort(projectPath: string): number { let hash = 0; for (const c of projectPath) hash = ((hash * 31) + c.charCodeAt(0)) & 0xffff; return 3001 + (hash % 999); } // ─── Output formatting ──────────────────────────────────────────────────────── const MAX_OUTPUT_BYTES = 50 * 1024; export function formatOutput(result: { stdout: string; stderr: string; code: number; }): string { const parts: string[] = []; if (result.stdout.trim()) { let out = result.stdout; if (Buffer.byteLength(out) > MAX_OUTPUT_BYTES) out = `[...truncated — last ${MAX_OUTPUT_BYTES / 1024} KB...]\n` + out.slice(-MAX_OUTPUT_BYTES); parts.push(out.trimEnd()); } if (result.stderr.trim()) parts.push(`--- stderr ---\n${result.stderr.trimEnd()}`); if (result.code !== 0) parts.push(`--- exit code: ${result.code} ---`); return parts.length ? parts.join("\n\n") : "(no output)"; }