1import { readPrebuiltWorkerBundle } from "@/Cloudflare/Workers/Sources/Prebuilt";
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
8const decode = (content: string | Uint8Array<ArrayBufferLike>) =>
9 typeof content === "string"
10 ? content
11 : new TextDecoder().decode(content as Uint8Array);
12
13/**
14 * Write `files` (paths relative to a fresh temp directory) and return
15 * the temp directory's absolute path.
16 */
17const writeFixture = Effect.fn(function* (
18 files: Record<string, string | Uint8Array>,
19) {
20 const fs = yield* FileSystem.FileSystem;
21 const path = yield* Path.Path;
22 const root = yield* fs.makeTempDirectory({ prefix: "alchemy-prebuilt-" });
23 for (const [name, content] of Object.entries(files)) {
24 const file = path.join(root, name);
25 yield* fs.makeDirectory(path.dirname(file), { recursive: true });
26 if (typeof content === "string") {
27 yield* fs.writeFileString(file, content);
28 } else {
29 yield* fs.writeFile(file, content);
30 }
31 }
32 return root;
33});
34
35layer(NodeServices.layer)("readPrebuiltWorkerBundle", (it) => {
36 it.effect(
37 "collects modules matching the default rules, entry first, names POSIX-relative to the entry's directory",
38 () =>
39 Effect.gen(function* () {
40 const fs = yield* FileSystem.FileSystem;
41 const path = yield* Path.Path;
42 const root = yield* writeFixture({
43 "index.mjs": `import { greeting } from "./lib/greeting.mjs";\nexport default { fetch: () => new Response(greeting) };\n`,
44 "lib/greeting.mjs": `export const greeting = "hello";\n`,
45 "lib/deep/notice.txt": "NOTICE",
46 "add.wasm": new Uint8Array([0x00, 0x61, 0x73, 0x6d, 1, 0, 0, 0]),
47 "schema.sql": "select 1;",
48 "page.html": "<html></html>",
49 "blob.bin": new Uint8Array([1, 2, 3]),
50 // hidden directories are walked (wrangler glob parity)
51 ".chunks/extra.mjs": `export const extra = 1;\n`,
52 // not matched by any default rule
53 "README.md": "# not a module",
54 // source maps are never uploaded as modules
55 "index.mjs.map": "{}",
56 "lib/greeting.mjs.map": "{}",
57 });
58
59 const bundle = yield* readPrebuiltWorkerBundle({
60 main: path.join(root, "index.mjs"),
61 });
62
63 expect(bundle.files[0].path).toEqual("index.mjs");
64 expect(bundle.files.slice(1).map((file) => file.path)).toEqual([
65 ".chunks/extra.mjs",
66 "add.wasm",
67 "blob.bin",
68 "lib/deep/notice.txt",
69 "lib/greeting.mjs",
70 "page.html",
71 "schema.sql",
72 ]);
73
74 yield* fs.remove(root, { recursive: true });
75 }),
76 {
77 tags: [
78 "unit",
79 "provider:cloudflare",
80 "provider:cloudflare:worker",
81 "local",
82 ],
83 },
84 );
85
86 it.effect(
87 "uploads file contents byte-for-byte",
88 () =>
89 Effect.gen(function* () {
90 const fs = yield* FileSystem.FileSystem;
91 const path = yield* Path.Path;
92 // Comments and formatting that any bundler/minifier would strip.
93 const entrySource = `// SENTINEL: must survive byte-for-byte 7f1c\nconst kSentinel = "sentinel/7f1c";\nexport default { fetch: () => new Response(kSentinel) };\n`;
94 const wasmBytes = new Uint8Array([
95 0x00, 0x61, 0x73, 0x6d, 1, 0, 0, 0, 42,
96 ]);
97 const root = yield* writeFixture({
98 "index.mjs": entrySource,
99 "add.wasm": wasmBytes,
100 });
101
102 const bundle = yield* readPrebuiltWorkerBundle({
103 main: path.join(root, "index.mjs"),
104 });
105
106 expect(decode(bundle.files[0].content)).toEqual(entrySource);
107 const wasm = bundle.files.find((file) => file.path === "add.wasm")!;
108 expect(new Uint8Array(wasm.content as Uint8Array)).toEqual(wasmBytes);
109
110 yield* fs.remove(root, { recursive: true });
111 }),
112 {
113 tags: [
114 "unit",
115 "provider:cloudflare",
116 "provider:cloudflare:worker",
117 "local",
118 ],
119 },
120 );
121
122 it.effect(
123 "does not walk outside the entry's directory",
124 () =>
125 Effect.gen(function* () {
126 const fs = yield* FileSystem.FileSystem;
127 const path = yield* Path.Path;
128 const root = yield* writeFixture({
129 "outside.mjs": `export const outside = true;\n`,
130 "dist/index.mjs": `export default { fetch: () => new Response("ok") };\n`,
131 "dist/chunk.mjs": `export const chunk = 1;\n`,
132 });
133
134 const bundle = yield* readPrebuiltWorkerBundle({
135 main: path.join(root, "dist/index.mjs"),
136 });
137
138 expect(bundle.files.map((file) => file.path)).toEqual([
139 "index.mjs",
140 "chunk.mjs",
141 ]);
142
143 yield* fs.remove(root, { recursive: true });
144 }),
145 {
146 tags: [
147 "unit",
148 "provider:cloudflare",
149 "provider:cloudflare:worker",
150 "local",
151 ],
152 },
153 );
154
155 it.effect(
156 "custom rules replace the defaults",
157 () =>
158 Effect.gen(function* () {
159 const fs = yield* FileSystem.FileSystem;
160 const path = yield* Path.Path;
161 const root = yield* writeFixture({
162 "index.mjs": `export default { fetch: () => new Response("ok") };\n`,
163 "data/weights.dat": "0101",
164 "data/skipped.txt": "skipped",
165 "skipped.mjs": `export const skipped = true;\n`,
166 });
167
168 const bundle = yield* readPrebuiltWorkerBundle({
169 main: path.join(root, "index.mjs"),
170 rules: [{ globs: ["**/*.dat"] }],
171 });
172
173 // The entry is always included, even when it matches no rule.
174 expect(bundle.files.map((file) => file.path)).toEqual([
175 "index.mjs",
176 "data/weights.dat",
177 ]);
178
179 yield* fs.remove(root, { recursive: true });
180 }),
181 {
182 tags: [
183 "unit",
184 "provider:cloudflare",
185 "provider:cloudflare:worker",
186 "local",
187 ],
188 },
189 );
190
191 it.effect(
192 "hash is stable across reads and sensitive to module changes",
193 () =>
194 Effect.gen(function* () {
195 const fs = yield* FileSystem.FileSystem;
196 const path = yield* Path.Path;
197 const root = yield* writeFixture({
198 "index.mjs": `import { x } from "./lib/x.mjs";\nexport default { fetch: () => new Response(String(x)) };\n`,
199 "lib/x.mjs": `export const x = 1;\n`,
200 });
201 const main = path.join(root, "index.mjs");
202
203 const first = yield* readPrebuiltWorkerBundle({ main });
204 const second = yield* readPrebuiltWorkerBundle({ main });
205 expect(second.hash).toEqual(first.hash);
206
207 yield* fs.writeFileString(
208 path.join(root, "lib/x.mjs"),
209 `export const x = 2;\n`,
210 );
211 const changed = yield* readPrebuiltWorkerBundle({ main });
212 expect(changed.hash).not.toEqual(first.hash);
213
214 yield* fs.remove(root, { recursive: true });
215 }),
216 {
217 tags: [
218 "unit",
219 "provider:cloudflare",
220 "provider:cloudflare:worker",
221 "local",
222 ],
223 },
224 );
225
226 it.effect(
227 "fails with BundleError when the entry does not exist",
228 () =>
229 Effect.gen(function* () {
230 const fs = yield* FileSystem.FileSystem;
231 const path = yield* Path.Path;
232 const root = yield* fs.makeTempDirectory({
233 prefix: "alchemy-prebuilt-",
234 });
235
236 const error = yield* readPrebuiltWorkerBundle({
237 main: path.join(root, "missing.mjs"),
238 }).pipe(Effect.flip);
239
240 expect(error._tag).toEqual("BundleError");
241 expect(error.message).toContain("missing.mjs");
242
243 yield* fs.remove(root, { recursive: true });
244 }),
245 {
246 tags: [
247 "unit",
248 "provider:cloudflare",
249 "provider:cloudflare:worker",
250 "local",
251 ],
252 },
253 );
254});