Files
upstream-atomic-crm/.claude/hooks/lib/workspace-folders.mjs
upstream-mirror 93ecc1a6f8
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

89 lines
3.3 KiB
JavaScript

// Show a harness worktree in the editor by adding it as a workspace folder, so
// its files AND its Source Control diff appear live during a run. Two mechanisms,
// auto-detected:
//
// - A `.code-workspace` file beside the repo (multi-root workspace): edit its
// `folders` array. VS Code live-reloads the file, so add AND remove are clean.
// - A mono-folder window (no `.code-workspace`): `code --add <path>` asks the
// running VS Code window to add the folder (which turns it into an untitled
// multi-root workspace). There is no `code --remove` in the VS Code CLI, so
// removal is a no-op here; the untitled workspace is not persisted, so the
// entry is gone on the next fresh open anyway.
//
// Every function is best-effort and never throws: a convenience view must not
// break a run. A parse failure leaves the file untouched (never corrupt it).
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { exec } from "./process.mjs";
// The `.code-workspace` file beside the repo (the multi-root workspace root is
// dirname(repo)). Returns its absolute path, or null (mono-folder / none).
export function workspaceFile(repo) {
const root = dirname(repo);
try {
const name = readdirSync(root).find((n) => n.endsWith(".code-workspace"));
return name ? join(root, name) : null;
} catch {
return null;
}
}
function readWorkspace(file) {
try {
const ws = JSON.parse(readFileSync(file, "utf8"));
return ws && Array.isArray(ws.folders) ? ws : null;
} catch {
return null; // JSONC/comments or unreadable — do not touch it
}
}
function writeWorkspace(file, ws) {
try {
writeFileSync(file, JSON.stringify(ws, null, 2) + "\n");
return true;
} catch {
return false;
}
}
// A VS Code window is reachable from a hook subprocess (its IPC env is present).
const canDriveWindow = () =>
Boolean(process.env.VSCODE_IPC_HOOK_CLI || process.env.REMOTE_CONTAINERS_IPC);
/**
* Add `path` (an absolute worktree path) as a workspace folder named `name`.
* @returns {boolean} whether the folder was added (or already present).
*/
export function addWorktreeFolder(repo, path, name) {
if (!existsSync(path)) return false;
const file = workspaceFile(repo);
if (file) {
const ws = readWorkspace(file);
if (!ws) return false;
if (ws.folders.some((f) => f.path === path)) return true; // idempotent
ws.folders.push({ path, name });
return writeWorkspace(file, ws);
}
// Mono-folder window: ask VS Code to add it (add-only, self-heals on reopen).
if (canDriveWindow()) return exec("code", ["--add", path]).status === 0;
return false;
}
/**
* Remove every workspace folder whose absolute path satisfies `matches(path)`
* (e.g. a prefix match on the session's worktree base, or an exact path). Only
* the `.code-workspace` mechanism supports removal; mono-folder is a no-op.
* @returns {boolean} whether anything was removed.
*/
export function removeWorktreeFolders(repo, matches) {
const file = workspaceFile(repo);
if (!file) return false;
const ws = readWorkspace(file);
if (!ws) return false;
const kept = ws.folders.filter((f) => !(f.path && matches(f.path)));
if (kept.length === ws.folders.length) return false;
ws.folders = kept;
return writeWorkspace(file, ws);
}