diff --git a/packages/cli/src/bun.ts b/packages/cli/src/bun.ts index 0721d2af..4b1e4a3b 100644 --- a/packages/cli/src/bun.ts +++ b/packages/cli/src/bun.ts @@ -9,7 +9,7 @@ import { main } from "effection"; import { fileURLToPath } from "node:url"; import process from "node:process"; import { API, useHostFiles } from "@executablemd/runtime"; -import { compileDataUri } from "@executablemd/core"; +import { compileTempFile } from "@executablemd/core"; import { runXmd } from "./cli.ts"; import { unassembledMachineSessions } from "./session-coordinator.ts"; import { unsupportedWorkflowHost } from "./workflow.ts"; @@ -27,7 +27,7 @@ await main(function* (args) { }, *compile([source, options]) { - return yield* compileDataUri(source, options); + return yield* compileTempFile(source, options); }, }, { at: "min" }, diff --git a/packages/cli/tests/value-root.test.ts b/packages/cli/tests/value-root.test.ts index 5e03ab1b..fc2a586e 100644 --- a/packages/cli/tests/value-root.test.ts +++ b/packages/cli/tests/value-root.test.ts @@ -7,14 +7,18 @@ */ import { describe, it } from "@executablemd/test-support/bdd"; import { expect } from "@executablemd/test-support/expect"; -import { ensure, scoped } from "effection"; +import { ensure, scoped, until } from "effection"; import type { Operation } from "effection"; import { ensureDir, readTextFile, rm, writeTextFile } from "@effectionx/fs"; import { randomUUID } from "node:crypto"; +import { symlink } from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { fileURLToPath } from "node:url"; import { runCli } from "@executablemd/test-support/launch"; +const ROOT = fileURLToPath(new URL("../../../", import.meta.url)); + function* useFixture( files: Record, body: (dir: string) => Operation, @@ -23,6 +27,10 @@ function* useFixture( yield* ensureDir(dir); return yield* scoped(function* () { yield* ensure(() => rm(dir, { recursive: true, force: true })); + yield* writeTextFile(path.join(dir, "package.json"), JSON.stringify({ type: "module" })); + // The child compiles beside the document, so its authored imports resolve + // through the fixture's explicit project dependencies. + yield* until(symlink(path.join(ROOT, "node_modules"), path.join(dir, "node_modules"), "dir")); for (const [name, content] of Object.entries(files)) { yield* writeTextFile(path.join(dir, name), content); } diff --git a/packages/core/src/data-uri-compiler.ts b/packages/core/src/data-uri-compiler.ts index 41797a7b..f7c9d599 100644 --- a/packages/core/src/data-uri-compiler.ts +++ b/packages/core/src/data-uri-compiler.ts @@ -2,8 +2,10 @@ * data: URI eval block compiler middleware. * * Compiles eval block source into generator functions by importing a data: - * URI, which leaves nothing on disk. Deno and Bun load one; Node's tsx loader - * rejects it, so the Node entrypoint installs the temp-file compiler instead. + * URI, which leaves nothing on disk. Deno and the compiled binary load one. + * Node's tsx loader rejects it, and Bun does not preserve the generated + * module's exports when it imports a filesystem-backed dependency, so those + * entrypoints install the temp-file compiler instead. * Standard imports (Effection, executable.md APIs) are captured in the middleware * closure — they are not part of the `API.Env.compile` interface. * diff --git a/specs/executable-mdx-spec.md b/specs/executable-mdx-spec.md index 9b2bb047..b778a8c3 100644 --- a/specs/executable-mdx-spec.md +++ b/specs/executable-mdx-spec.md @@ -1456,13 +1456,14 @@ suggested: | Middleware | Mechanism | Hosts | | --- | --- | --- | -| `useDataUriCompiler()` | imports a `data:` URI; touches no disk | Deno, Bun, the compiled binary | -| `useTempFileCompiler()` | writes `.xmd-eval/.ts` and imports `file://` | any host, and the only one Node's tsx loader accepts | +| `useDataUriCompiler()` | imports a `data:` URI; touches no disk | Deno and the compiled binary | +| `useTempFileCompiler()` | writes `.xmd-eval/.ts` and imports `file://` | any host; the Bun and Node entrypoints use it | An entrypoint installs whichever its host can load, with `{ at: "min" }`, so it sits at the base of the middleware chain. Where the two disagree — Node's -tsx loader rejects `data:` URI imports — that is a property of the loader, not -of the eval block. +tsx loader rejects `data:` URI imports, while Bun loses the generated module's +exports when it imports a filesystem-backed dependency — that is a property of +the loader, not of the eval block. Because the entrypoint's compiler is a base provider, ordinary middleware wraps it rather than racing it. The behavior-document policy in @@ -8427,7 +8428,7 @@ Argument placement belongs to the adapter, because it differs per host: | --- | --- | --- | | `deno.ts` | `[execPath, "run", "--allow-all", entry, ...args]` | `compileDataUri` | | `node.ts` | `[execPath, ...execArgv-minus-inspect, entry, ...args]` | `compileTempFile` | -| `bun.ts` | `[execPath, entry, ...args]` | `compileDataUri` | +| `bun.ts` | `[execPath, entry, ...args]` | `compileTempFile` | | `compiled.ts` | `[execPath, ...args]` | `compileDataUri` | **There is no inferred default.** With no adapter installed the operation