Problem
voyant workflows build --platform node can produce a bundle successfully, then fail while loading that bundle to emit the workflow manifest.
Observed deployment log:
Detected workflow entry src/workflows.ts
Workflow manifest generated with 6 workflow(s)
Building node workflow bundle
loading bundle failed: Dynamic require of "stream" is not supported
Command failed with exit code 1: pnpm exec voyant workflows build --file 'src/workflows.ts' --out '.voyant/build/node' --platform node --no-sourcemap
Expected behavior: a node-platform workflow bundle that depends on packages using Node built-ins should be loadable by the CLI for manifest extraction, and should produce bundle.mjs plus manifest.json without requiring app developers to debug bundler internals.
Reproduction shape:
- Create a workflow entry with
defaultRuntime: "node".
- Import code that pulls in a CommonJS dependency path that dynamically requires a Node built-in such as
stream.
- Run
voyant workflows build --platform node.
- The generated ESM bundle can contain a dynamic CommonJS require helper, and the CLI load step fails because the bundle is imported as ESM without an ambient CommonJS
require.
Root Cause Analysis
The node workflow build currently uses esbuild to produce a single ESM bundle, then immediately imports that bundle in the CLI process to discover registered workflows and emit the manifest.
When a dependency in the workflow graph is CommonJS and dynamically requires a Node built-in, esbuild can preserve that access through a dynamic require helper. Because the generated artifact is ESM, Node does not provide require at import time, so bundle loading fails even though the target runtime is Node and the dependency is valid in Node.
A tempting fix is to inject createRequire(import.meta.url) into the generated bundle, but that should be considered carefully: platform runners may import workflow bundles from non-file module URLs. The generated node bundle contract should be explicit about whether it requires a file URL loader, or should avoid ambient CommonJS require in the artifact altogether.
TDD Fix Plan
-
RED: Add a workflow build test where a node-runtime workflow imports a dependency fixture that dynamically requires a Node built-in such as stream. Assert that voyant workflows build --platform node completes and writes both bundle and manifest.
GREEN: Adjust the node bundle/load strategy so the generated node bundle can be imported successfully during CLI manifest extraction.
-
RED: Add a test that imports the generated node bundle through the same supported loader contract used by the runtime and verifies workflows register successfully.
GREEN: Make the runtime loader contract explicit and compatible with the generated node bundle, either by avoiding dynamic require in the bundle or by requiring/importing the bundle through a file-backed Node loader that can support CommonJS built-ins.
-
RED: Add a regression test ensuring browser/neutral workflow builds do not accidentally gain Node-only require behavior.
GREEN: Scope any CommonJS/Node built-in handling to node-platform builds only.
REFACTOR: Document the node bundle import contract so the CLI and managed node runner use the same assumptions.
Acceptance Criteria
Problem
voyant workflows build --platform nodecan produce a bundle successfully, then fail while loading that bundle to emit the workflow manifest.Observed deployment log:
Expected behavior: a node-platform workflow bundle that depends on packages using Node built-ins should be loadable by the CLI for manifest extraction, and should produce
bundle.mjsplusmanifest.jsonwithout requiring app developers to debug bundler internals.Reproduction shape:
defaultRuntime: "node".stream.voyant workflows build --platform node.require.Root Cause Analysis
The node workflow build currently uses esbuild to produce a single ESM bundle, then immediately imports that bundle in the CLI process to discover registered workflows and emit the manifest.
When a dependency in the workflow graph is CommonJS and dynamically requires a Node built-in, esbuild can preserve that access through a dynamic require helper. Because the generated artifact is ESM, Node does not provide
requireat import time, so bundle loading fails even though the target runtime is Node and the dependency is valid in Node.A tempting fix is to inject
createRequire(import.meta.url)into the generated bundle, but that should be considered carefully: platform runners may import workflow bundles from non-file module URLs. The generated node bundle contract should be explicit about whether it requires a file URL loader, or should avoid ambient CommonJS require in the artifact altogether.TDD Fix Plan
RED: Add a workflow build test where a node-runtime workflow imports a dependency fixture that dynamically requires a Node built-in such as
stream. Assert thatvoyant workflows build --platform nodecompletes and writes both bundle and manifest.GREEN: Adjust the node bundle/load strategy so the generated node bundle can be imported successfully during CLI manifest extraction.
RED: Add a test that imports the generated node bundle through the same supported loader contract used by the runtime and verifies workflows register successfully.
GREEN: Make the runtime loader contract explicit and compatible with the generated node bundle, either by avoiding dynamic require in the bundle or by requiring/importing the bundle through a file-backed Node loader that can support CommonJS built-ins.
RED: Add a regression test ensuring browser/neutral workflow builds do not accidentally gain Node-only require behavior.
GREEN: Scope any CommonJS/Node built-in handling to node-platform builds only.
REFACTOR: Document the node bundle import contract so the CLI and managed node runner use the same assumptions.
Acceptance Criteria
voyant workflows build --platform nodesucceeds for workflow graphs that include valid Node dependencies using dynamic require of Node built-ins.streamrequire case.