1// @effect-diagnostics nodeBuiltinImport:off - builds real worktree layouts on disk.
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, describe, it } from "@effect/vitest";
4import * as NodeFS from "node:fs";
5import * as NodeOS from "node:os";
6import * as NodePath from "node:path";
7import * as Effect from "effect/Effect";
8
9import { resolveGitWorktreePath, resolveWorktreeT3Home } from "./devHome.ts";
10
11const makeRepo = (
12 kind:
13 | "worktree"
14 | "checkout"
15 | "bare"
16 | "submodule"
17 | "unreadable-git-file"
18 | "bare-repo-worktree"
19 | "custom-common-dir-worktree",
20) =>
21 Effect.acquireRelease(
22 Effect.sync(() => {
23 const root = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-devhome-"));
24 if (kind === "worktree") {
25 NodeFS.writeFileSync(NodePath.join(root, ".git"), "gitdir: /elsewhere/.git/worktrees/x\n");
26 } else if (kind === "bare-repo-worktree") {
27 // `git worktree add` from a bare repo: the common dir is `<name>.git`.
28 NodeFS.writeFileSync(NodePath.join(root, ".git"), "gitdir: /srv/myrepo.git/worktrees/x\n");
29 } else if (kind === "custom-common-dir-worktree") {
30 // $GIT_COMMON_DIR need not be named `.git` at all.
31 NodeFS.writeFileSync(NodePath.join(root, ".git"), "gitdir: /srv/store/worktrees/x\n");
32 } else if (kind === "submodule") {
33 NodeFS.writeFileSync(NodePath.join(root, ".git"), "gitdir: ../.git/modules/sub\n");
34 } else if (kind === "unreadable-git-file") {
35 NodeFS.writeFileSync(NodePath.join(root, ".git"), "not a gitdir pointer\n");
36 } else if (kind === "checkout") {
37 NodeFS.mkdirSync(NodePath.join(root, ".git"));
38 }
39 const nested = NodePath.join(root, "apps", "web", "src");
40 NodeFS.mkdirSync(nested, { recursive: true });
41 return { root, nested };
42 }),
43 ({ root }) => Effect.sync(() => NodeFS.rmSync(root, { recursive: true, force: true })),
44 );
45
46describe("resolveGitWorktreePath", () => {
47 it.effect("finds a worktree root from a nested directory", () =>
48 Effect.gen(function* () {
49 const { root, nested } = yield* makeRepo("worktree");
50 assert.equal(yield* resolveGitWorktreePath(nested), NodePath.resolve(root));
51 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
52 );
53
54 it.effect("reports a main checkout as not a linked worktree", () =>
55 Effect.gen(function* () {
56 const { nested } = yield* makeRepo("checkout");
57 assert.equal(yield* resolveGitWorktreePath(nested), undefined);
58 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
59 );
60
61 it.effect("reports a directory outside a repository", () =>
62 Effect.gen(function* () {
63 const { nested } = yield* makeRepo("bare");
64 assert.equal(yield* resolveGitWorktreePath(nested), undefined);
65 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
66 );
67
68 it.effect("reports a submodule as not a linked worktree", () =>
69 Effect.gen(function* () {
70 const { nested } = yield* makeRepo("submodule");
71 assert.equal(yield* resolveGitWorktreePath(nested), undefined);
72 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
73 );
74
75 it.effect("reports a .git file without a usable gitdir pointer", () =>
76 Effect.gen(function* () {
77 const { nested } = yield* makeRepo("unreadable-git-file");
78 assert.equal(yield* resolveGitWorktreePath(nested), undefined);
79 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
80 );
81
82 it.effect("finds a worktree of a bare repository", () =>
83 Effect.gen(function* () {
84 const { root, nested } = yield* makeRepo("bare-repo-worktree");
85 assert.equal(yield* resolveGitWorktreePath(nested), NodePath.resolve(root));
86 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
87 );
88
89 it.effect("finds a worktree whose common dir is not named .git", () =>
90 Effect.gen(function* () {
91 const { root, nested } = yield* makeRepo("custom-common-dir-worktree");
92 assert.equal(yield* resolveGitWorktreePath(nested), NodePath.resolve(root));
93 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
94 );
95});
96
97describe("resolveWorktreeT3Home", () => {
98 it.effect("answers with .t3 before the dev runner creates it", () =>
99 Effect.gen(function* () {
100 const { root, nested } = yield* makeRepo("worktree");
101 const home = yield* resolveWorktreeT3Home(nested);
102 assert.equal(home, NodePath.join(NodePath.resolve(root), ".t3"));
103 assert.isFalse(NodeFS.existsSync(home ?? ""));
104 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
105 );
106});