1import { exec } from "@/Util/exec.ts";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { describe, expect, it } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import { ChildProcess } from "effect/process";
6import { fileURLToPath } from "node:url";
7
8/**
9 * Regression tests for #562: importing alchemy's CLI or provider modules
10 * must never load rolldown's native binding (`@rolldown/binding-*`). A
11 * user whose stack bundles nothing (e.g. a non-Cloudflare stack running
12 * `alchemy deploy`) must not require the native bundler to be loadable.
13 *
14 * Each entry is imported in a fresh subprocess — the single-process test
15 * runner has long since loaded rolldown for other suites, so the check
16 * can only be made in an isolated module registry. `process.dlopen` is
17 * the choke point every native-addon load goes through, so intercepting
18 * it detects the binding no matter which package (`rolldown`,
19 * `rolldown/parseAst`, `@alchemy.run/cloudflare-runtime/rolldown`)
20 * pulled it in.
21 */
22
23const entries = [
24 "src/Bundle/index.ts",
25 "src/Cli/main.ts",
26 "src/Cloudflare/index.ts",
27] as const;
28
29const importInSubprocess = (entry: string) => {
30 const entryPath = fileURLToPath(new URL(`../../${entry}`, import.meta.url));
31 const script = `
32 const orig = process.dlopen;
33 const loaded = [];
34 process.dlopen = function (mod, path, ...rest) {
35 loaded.push(path);
36 return orig.call(process, mod, path, ...rest);
37 };
38 await import(${JSON.stringify(entryPath)});
39 const hits = loaded.filter((p) => p.includes("rolldown"));
40 if (hits.length > 0) {
41 console.error("rolldown native binding loaded by:\\n" + hits.join("\\n"));
42 process.exit(1);
43 }
44 console.log("no rolldown native binding loaded");
45 `;
46 return exec(
47 ChildProcess.make(process.execPath, ["-e", script], { shell: false }),
48 ).pipe(Effect.scoped);
49};
50
51describe("lazy rolldown (#562)", { tags: ["unit", "local"] }, () => {
52 for (const entry of entries) {
53 it.effect(
54 `importing ${entry} does not load rolldown's native binding`,
55 () =>
56 Effect.gen(function* () {
57 const { exitCode, stdout, stderr } = yield* importInSubprocess(entry);
58 expect(stderr).toBe("");
59 expect(stdout).toContain("no rolldown native binding loaded");
60 expect(exitCode).toBe(0);
61 }).pipe(Effect.provide(NodeServices.layer)),
62 );
63 }
64});