import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { mkdtemp, rename, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; import { test } from "node:test"; import { WorkspaceInspector } from "../src/workspace.ts"; const run = promisify(execFile); const exec = async (command: string, args: string[], options?: { timeout?: number; signal?: AbortSignal }) => { try { const value = await run(command, args, { encoding: "utf8", ...options }); return { stdout: value.stdout, stderr: value.stderr, code: 0 }; } catch (error) { const row = error as { stdout?: string; stderr?: string; code?: number }; return { stdout: row.stdout ?? "", stderr: row.stderr ?? "", code: row.code ?? 1 }; } }; async function git(root: string, ...args: string[]) { const result = await exec("git", ["-C", root, ...args]); assert.equal(result.code, 0, result.stderr); } test("git snapshots detect second edits, staging, untracked, rename, deletion, and symlinks", async () => { const root = await mkdtemp(join(tmpdir(), "native-hooks-git-")); await git(root, "init", "-q"); await git(root, "config", "user.email", "test@example.invalid"); await git(root, "config", "user.name", "Test"); await writeFile(join(root, "a.ts"), "export const a = 1;\n"); await git(root, "add", "a.ts"); await git(root, "commit", "-qm", "initial"); const inspector = new WorkspaceInspector(exec); const clean = await inspector.capture(root); await writeFile(join(root, "a.ts"), "export const a = 2;\n"); const dirty = await inspector.capture(root); assert.deepEqual(inspector.diff(clean, dirty).changedPaths, ["a.ts"]); await writeFile(join(root, "a.ts"), "export const a = 3;\n"); const second = await inspector.capture(root); assert.notEqual(second.fingerprint, dirty.fingerprint); assert.deepEqual(inspector.diff(dirty, second).changedPaths, ["a.ts"]); await git(root, "add", "a.ts"); const staged = await inspector.capture(root); assert.deepEqual(inspector.diff(second, staged).changedPaths, ["a.ts"]); await writeFile(join(root, "new.ts"), "new\n"); const untracked = await inspector.capture(root); assert.ok(inspector.diff(staged, untracked).changedPaths.includes("new.ts")); await symlink("a.ts", join(root, "link.ts")); const linked = await inspector.capture(root); assert.ok(inspector.diff(untracked, linked).changedPaths.includes("link.ts")); await rename(join(root, "a.ts"), join(root, "moved.ts")); const renamed = await inspector.capture(root); assert.ok(inspector.diff(linked, renamed).changedPaths.includes("a.ts")); assert.ok(inspector.diff(linked, renamed).changedPaths.includes("moved.ts")); await rm(join(root, "moved.ts")); const deleted = await inspector.capture(root); assert.ok(inspector.diff(renamed, deleted).changedPaths.includes("moved.ts")); });