1import * as Bundle from "@/Bundle/Bundle";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { expect, layer } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7
8layer(NodeServices.layer)("Bundle.build default text module types", (it) => {
9 it.effect(
10 "inlines a bare .sql import as a string default export",
11 () =>
12 Effect.gen(function* () {
13 const fs = yield* FileSystem.FileSystem;
14 const path = yield* Path.Path;
15 const root = yield* fs.makeTempDirectory({
16 prefix: "alchemy-sql-module-",
17 });
18 yield* fs.writeFileString(
19 path.join(root, "0000_init.sql"),
20 "CREATE TABLE sql_module_marker (id integer primary key);",
21 );
22 const entry = path.join(root, "entry.ts");
23 yield* fs.writeFileString(
24 entry,
25 `import m0000 from "./0000_init.sql";\nconsole.log(m0000);\n`,
26 );
27
28 const result = yield* Bundle.build({
29 input: entry,
30 cwd: root,
31 });
32
33 const code = result.files
34 .filter((f) => typeof f.content === "string")
35 .map((f) => f.content as string)
36 .join("\n");
37 expect(code).toContain("CREATE TABLE sql_module_marker");
38 // Inlined into the chunk, not emitted as a separate asset.
39 expect(result.files.every((f) => !f.path.endsWith(".sql"))).toBe(true);
40
41 yield* fs.remove(root, { recursive: true });
42 }),
43 { tags: ["unit", "local"] },
44 );
45
46 it.effect(
47 "bundles a drizzle-kit durable-sqlite migrations layout",
48 () =>
49 Effect.gen(function* () {
50 const fs = yield* FileSystem.FileSystem;
51 const path = yield* Path.Path;
52 const root = yield* fs.makeTempDirectory({
53 prefix: "alchemy-drizzle-migrations-",
54 });
55 // Mirror `drizzle-kit generate` output for `driver: "durable-sqlite"`:
56 // migrations.js imports each .sql migration and the meta journal.
57 yield* fs.makeDirectory(path.join(root, "drizzle", "meta"), {
58 recursive: true,
59 });
60 yield* fs.writeFileString(
61 path.join(root, "drizzle", "0000_cool_migration.sql"),
62 "CREATE TABLE drizzle_do_marker (id integer primary key, name text);",
63 );
64 yield* fs.writeFileString(
65 path.join(root, "drizzle", "meta", "_journal.json"),
66 JSON.stringify({
67 version: "7",
68 dialect: "sqlite",
69 entries: [
70 {
71 idx: 0,
72 version: "6",
73 when: 1,
74 tag: "0000_cool_migration",
75 breakpoints: true,
76 },
77 ],
78 }),
79 );
80 yield* fs.writeFileString(
81 path.join(root, "drizzle", "migrations.js"),
82 [
83 `import journal from './meta/_journal.json';`,
84 `import m0000 from './0000_cool_migration.sql';`,
85 `export default { journal, migrations: { m0000 } };`,
86 ].join("\n"),
87 );
88 const entry = path.join(root, "entry.ts");
89 yield* fs.writeFileString(
90 entry,
91 `import migrations from "./drizzle/migrations.js";\nconsole.log(migrations);\n`,
92 );
93
94 const result = yield* Bundle.build({
95 input: entry,
96 cwd: root,
97 });
98
99 const code = result.files
100 .filter((f) => typeof f.content === "string")
101 .map((f) => f.content as string)
102 .join("\n");
103 expect(code).toContain("CREATE TABLE drizzle_do_marker");
104 expect(code).toContain("0000_cool_migration");
105
106 yield* fs.remove(root, { recursive: true });
107 }),
108 { tags: ["unit", "local"] },
109 );
110
111 it.effect(
112 "inlines bare .txt and .html imports as strings",
113 () =>
114 Effect.gen(function* () {
115 const fs = yield* FileSystem.FileSystem;
116 const path = yield* Path.Path;
117 const root = yield* fs.makeTempDirectory({
118 prefix: "alchemy-text-module-",
119 });
120 yield* fs.writeFileString(
121 path.join(root, "note.txt"),
122 "TXT_MODULE_MARKER",
123 );
124 yield* fs.writeFileString(
125 path.join(root, "page.html"),
126 "<h1>HTML_MODULE_MARKER</h1>",
127 );
128 const entry = path.join(root, "entry.ts");
129 yield* fs.writeFileString(
130 entry,
131 [
132 `import note from "./note.txt";`,
133 `import page from "./page.html";`,
134 `console.log(note, page);`,
135 ].join("\n"),
136 );
137
138 const result = yield* Bundle.build({
139 input: entry,
140 cwd: root,
141 });
142
143 const code = result.files
144 .filter((f) => typeof f.content === "string")
145 .map((f) => f.content as string)
146 .join("\n");
147 expect(code).toContain("TXT_MODULE_MARKER");
148 expect(code).toContain("HTML_MODULE_MARKER");
149
150 yield* fs.remove(root, { recursive: true });
151 }),
152 { tags: ["unit", "local"] },
153 );
154
155 it.effect(
156 "caller-provided moduleTypes override the defaults",
157 () =>
158 Effect.gen(function* () {
159 const fs = yield* FileSystem.FileSystem;
160 const path = yield* Path.Path;
161 const root = yield* fs.makeTempDirectory({
162 prefix: "alchemy-module-override-",
163 });
164 yield* fs.writeFileString(
165 path.join(root, "data.sql"),
166 "OVERRIDE_MARKER",
167 );
168 const entry = path.join(root, "entry.ts");
169 yield* fs.writeFileString(
170 entry,
171 `import data from "./data.sql";\nconsole.log(data);\n`,
172 );
173
174 const result = yield* Bundle.build({
175 input: entry,
176 cwd: root,
177 moduleTypes: { ".sql": "base64" },
178 });
179
180 const code = result.files
181 .filter((f) => typeof f.content === "string")
182 .map((f) => f.content as string)
183 .join("\n");
184 expect(code).not.toContain("OVERRIDE_MARKER");
185 expect(code).toContain(
186 Buffer.from("OVERRIDE_MARKER").toString("base64"),
187 );
188
189 yield* fs.remove(root, { recursive: true });
190 }),
191 { tags: ["unit", "local"] },
192 );
193});