1import { describe, expect, it } from "bun:test";
2import { execFileSync, spawnSync } from "node:child_process";
3import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4import { tmpdir } from "node:os";
5import { dirname, join } from "node:path";
6import { cases, IS_WINDOWS } from "./fixtures/cases.ts";
7
8const gitAvailable = spawnSync("git", ["--version"]).status === 0;
9
10const withoutEndingSlash = (value: string) => value.replace(/\/$/, "");
11
12const containsAnotherPath = (
13 candidate: string,
14 index: number,
15 paths: string[],
16) => {
17 const path = withoutEndingSlash(candidate);
18 return paths.some(
19 (other, otherIndex) =>
20 otherIndex !== index &&
21 (other === path ||
22 (other.startsWith(path) && other[path.length] === "/")),
23 );
24};
25
26const nativeGitResult = (
27 patterns: Array<string | { pattern: string }> | string,
28 paths: string[],
29) => {
30 const root = mkdtempSync(join(tmpdir(), "alchemy-node-ignore-"));
31 try {
32 const contents =
33 typeof patterns === "string"
34 ? patterns
35 : patterns
36 .map((rule) => (typeof rule === "string" ? rule : rule.pattern))
37 .join("\n");
38 writeFileSync(join(root, ".gitignore"), contents);
39
40 paths.forEach((path, index) => {
41 if (path === ".gitignore" || containsAnotherPath(path, index, paths))
42 return;
43 const target = join(root, path);
44 if (path.endsWith("/")) {
45 mkdirSync(target, { recursive: true });
46 } else {
47 mkdirSync(dirname(target), { recursive: true });
48 writeFileSync(target, "");
49 }
50 });
51
52 execFileSync("git", ["init", "--quiet"], { cwd: root });
53 return paths.filter((path) => {
54 const result = spawnSync("git", ["check-ignore", "--no-index", path], {
55 cwd: root,
56 encoding: "utf8",
57 });
58 return result.status !== 0;
59 });
60 } finally {
61 rmSync(root, { recursive: true, force: true });
62 }
63};
64
65describe.skipIf(IS_WINDOWS || !gitAvailable)(
66 "parity with git check-ignore",
67 () => {
68 cases(({ description, patterns, skip_test_fixture, paths, expected }) => {
69 if (
70 skip_test_fixture ||
71 !paths.some(Boolean) ||
72 !expected.every((path: string) => !path.startsWith(".git/"))
73 ) {
74 return;
75 }
76
77 it(description, () => {
78 expect(nativeGitResult(patterns, paths).sort()).toEqual(
79 expected.sort(),
80 );
81 });
82 });
83 },
84);