import * as fs from "node:fs/promises"; import * as path from "node:path"; const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$/; export const assertSafeRepoName = (name: string): string => { const trimmed = name.trim(); if (!SAFE_NAME.test(trimmed)) { throw new Error("Invalid repo name: use letters, digits, . _ - only; no path segments."); } if (trimmed.includes("..")) { throw new Error("Invalid repo name: path traversal rejected."); } return trimmed; }; export const getCodeRoot = (): string => { const raw = process.env.REPOS_DEVTOOLS_ROOT ?? "/home/ncantu/code"; return path.resolve(raw); }; export const repoDirForName = (codeRoot: string, name: string): string => { const safe = assertSafeRepoName(name); return path.join(codeRoot, safe); }; export const isGitRepo = async (dir: string): Promise => { try { const stat = await fs.stat(path.join(dir, ".git")); return stat.isDirectory() || stat.isFile(); } catch { return false; } }; export const repoNameFromGitUrl = (rawUrl: string): string => { const trimmed = rawUrl.trim(); const noQuery = trimmed.split("?")[0] ?? trimmed; const base = noQuery.replace(/[/]+$/, ""); const segment = base.split("/").filter((s) => s.length > 0).pop() ?? ""; const withoutGit = segment.replace(/\.git$/i, ""); if (withoutGit.length === 0) { throw new Error("Could not derive repository name from URL."); } return assertSafeRepoName(withoutGit); };