1import * as Effect from "effect/Effect";
2import * as FileSystem from "effect/FileSystem";
3
4/**
5 * An "isolated project": a throwaway consumer project OUTSIDE the repository
6 * tree, laid out the way bun's isolated linker (or pnpm) lays out a real
7 * consumer — `alchemy` present in the project's `node_modules` (a symlink to
8 * this package), and NONE of alchemy's own dependencies hoisted beside it —
9 * whose `main.ts` re-exports a test fixture by absolute path.
10 *
11 * Every `main`-bundled platform (ECS, App Runner, Batch, EC2, Lambda,
12 * Cloudflare Containers, Docker, Fly, Hetzner, Prisma, MicroVM, …) wraps the
13 * user's program in a generated entry — a *virtual* rolldown module. A
14 * virtual module has no directory, so rolldown resolves its bare imports
15 * from the build `cwd`, i.e. the nearest `package.json` above `main`. Inside
16 * this monorepo that is `packages/alchemy`, from which everything resolves —
17 * so a fixture checked in next to its test can never reproduce the
18 * consumer-side failure: when the generated entry imported alchemy's own
19 * dependencies (`@distilled.cloud/*`, `@effect/platform-*`) directly, an
20 * isolated install left them `[UNRESOLVED_IMPORT]`, external, and the
21 * deployed process died at boot with `Cannot find module`. The entry is now
22 * a shim importing only `alchemy/Runtime/Bootstrap/<Platform>` (resolvable
23 * from any consumer) plus `main`; this harness pins that contract.
24 *
25 * Pointing a fixture's `main` at {@link IsolatedProject.main} makes the bundle
26 * `cwd` this project. The fixture itself (and the user code it represents)
27 * is reached through the absolute-path re-export, so its imports still
28 * resolve from the repo as usual — only the generated entry's own imports
29 * are exercised, exactly as for a real consumer.
30 *
31 * The location is deterministic (no nonce) and computed at module scope
32 * because `main` is a prop of the fixture's declaration: it has to be known
33 * when the fixture module is evaluated, at test collection, before any hook
34 * runs. A plain `/tmp` prefix (not `os.tmpdir()`) keeps fixture modules free
35 * of Node builtins — Worker/Durable Object bundles import the container
36 * fixture for its class and run on workerd.
37 */
38export interface IsolatedProject {
39 /** Unique project name; also the directory name under the shared root. */
40 readonly name: string;
41 /** Project directory (outside the repository). */
42 readonly dir: string;
43 /** The `main` to hand to the platform resource. */
44 readonly main: string;
45 /** Absolute path of the fixture module `main.ts` re-exports. */
46 readonly fixture: string;
47}
48
49const ROOT = "/tmp/alchemy-test-isolated-projects";
50
51/** `packages/alchemy` — this helper lives at `packages/alchemy/test/`. */
52const ALCHEMY_PACKAGE_DIR = new URL("..", import.meta.url).pathname.replace(
53 /\/$/,
54 "",
55);
56
57/**
58 * Declare an isolated project for `fixture` (pass `import.meta.filename`).
59 * Pure: nothing is written until {@link materializeIsolatedProject}.
60 */
61export const isolatedProject = (
62 name: string,
63 fixture: string,
64): IsolatedProject => {
65 const dir = `${ROOT}/${name}`;
66 return { name, dir, main: `${dir}/main.ts`, fixture };
67};
68
69/**
70 * Write the project to disk: a `package.json` (so the bundle `cwd` stops
71 * here), `node_modules/alchemy` linked to this package (the consumer's one
72 * direct dependency, as an isolated install lays it out — alchemy's own
73 * dependencies are deliberately NOT reachable from here), and a `main.ts`
74 * re-exporting the fixture's default export. Idempotent — safe to call at
75 * the top of every test that deploys it.
76 */
77export const materializeIsolatedProject = (project: IsolatedProject) =>
78 Effect.gen(function* () {
79 const fs = yield* FileSystem.FileSystem;
80 yield* fs.makeDirectory(`${project.dir}/node_modules`, {
81 recursive: true,
82 });
83 const alchemyLink = `${project.dir}/node_modules/alchemy`;
84 yield* fs.remove(alchemyLink, { recursive: true }).pipe(Effect.ignore);
85 yield* fs.symlink(ALCHEMY_PACKAGE_DIR, alchemyLink);
86 yield* fs.writeFileString(
87 `${project.dir}/package.json`,
88 JSON.stringify({
89 name: `alchemy-test-isolated-${project.name}`,
90 private: true,
91 type: "module",
92 }),
93 );
94 yield* fs.writeFileString(
95 project.main,
96 `export { default } from ${JSON.stringify(project.fixture)};\n`,
97 );
98 });
99
100/** Remove the project directory (ignores a missing directory). */
101export const removeIsolatedProject = (project: IsolatedProject) =>
102 FileSystem.FileSystem.pipe(
103 Effect.flatMap((fs) => fs.remove(project.dir, { recursive: true })),
104 Effect.ignore,
105 );