1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it, describe, expect } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Path from "effect/Path";
8
9import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";
10
11const layerTest = Layer.empty.pipe(
12 Layer.provideMerge(T3ProjectFileLoader.layer),
13 Layer.provideMerge(NodeServices.layer),
14);
15
16const makeTempDir = Effect.gen(function* () {
17 const fileSystem = yield* FileSystem.FileSystem;
18 return yield* fileSystem.makeTempDirectoryScoped({
19 prefix: "t3code-project-file-",
20 });
21});
22
23const writeProjectFile = Effect.fn("writeProjectFile")(function* (cwd: string, contents: string) {
24 const fileSystem = yield* FileSystem.FileSystem;
25 const path = yield* Path.Path;
26 yield* fileSystem.writeFileString(path.join(cwd, "t3.json"), contents).pipe(Effect.orDie);
27});
28
29it.layer(layerTest)("T3ProjectFileLoader", (it) => {
30 describe("load", () => {
31 it.effect("loads and decodes a valid t3.json", () =>
32 Effect.gen(function* () {
33 const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
34 const cwd = yield* makeTempDir;
35 yield* writeProjectFile(
36 cwd,
37 `{
38 // JSONC is tolerated
39 "iconPath": "assets/logo.svg",
40 "scripts": [{ "name": "Dev", "command": "pnpm dev" }],
41 }`,
42 );
43
44 const loaded = yield* loader.load(cwd);
45
46 expect(Option.isSome(loaded)).toBe(true);
47 if (Option.isSome(loaded)) {
48 expect(loaded.value.iconPath).toBe("assets/logo.svg");
49 expect(loaded.value.scripts).toEqual([{ name: "Dev", command: "pnpm dev" }]);
50 }
51 }),
52 );
53
54 it.effect("returns none when t3.json is missing", () =>
55 Effect.gen(function* () {
56 const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
57 const cwd = yield* makeTempDir;
58
59 const loaded = yield* loader.load(cwd);
60
61 expect(Option.isNone(loaded)).toBe(true);
62 }),
63 );
64
65 it.effect("returns none for malformed JSON without failing", () =>
66 Effect.gen(function* () {
67 const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
68 const cwd = yield* makeTempDir;
69 yield* writeProjectFile(cwd, "{ not json");
70
71 const loaded = yield* loader.load(cwd);
72
73 expect(Option.isNone(loaded)).toBe(true);
74 }),
75 );
76
77 it.effect("returns none for schema-invalid files without failing", () =>
78 Effect.gen(function* () {
79 const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
80 const cwd = yield* makeTempDir;
81 yield* writeProjectFile(cwd, '{ "scripts": [{ "name": "Dev" }] }');
82
83 const loaded = yield* loader.load(cwd);
84
85 expect(Option.isNone(loaded)).toBe(true);
86 }),
87 );
88 });
89});