Skip to content
16 changes: 16 additions & 0 deletions .changeset/sourcemap-config-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@workflow/builders": minor
"@workflow/nitro": minor
"@workflow/nest": minor
"@workflow/next": minor
"@workflow/sveltekit": minor
"@workflow/astro": minor
---

Add `sourcemap` option to builders for disabling or customising source map emission on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option: `true`, `false`, `'inline'`, `'linked'`, `'external'`, `'both'`. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable.

Setting `sourcemap: false` drops inline source maps from the step, workflow and webhook bundles, and skips the source-map-support runtime shim on the Vercel step function — helpful for staying under the Vercel 250MB function size limit.

Exposed per framework: `nitro.options.workflow.sourcemap`, `NestBuilderOptions.sourcemap`, `withWorkflow({ workflows: { sourcemap } })`, and the `sourcemap` option on `workflowPlugin()` for SvelteKit and Astro.

Minor semantics change: when the `sourcemap` option (or `WORKFLOW_SOURCEMAP`) is set explicitly, it now applies to **all** generated bundles. Previously, the final workflow wrapper and webhook bundles could only be toggled via the legacy `WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING=1` env var, which continues to work but is narrower in scope.
22 changes: 22 additions & 0 deletions docs/content/docs/api-reference/workflow-next/with-workflow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default withWorkflow(nextConfig, {
local: {
port: 4000,
},
sourcemap: false,
},
});
```
Expand All @@ -71,6 +72,27 @@ export default withWorkflow(nextConfig, {
| --- | --- | --- | --- |
| `workflows.lazyDiscovery` | `boolean` | `false` | When `true`, defers workflow discovery until files are requested instead of scanning eagerly at startup. Useful for large projects where startup time matters. |
| `workflows.local.port` | `number` | — | Overrides the `PORT` environment variable for local development. Has no effect when deployed to Vercel. |
| `workflows.sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. See [Source maps](#source-maps) below. |

### Source maps

The step bundle and intermediate workflow bundle default to `'inline'` source maps so that stack traces from step errors and workflow VM errors point at your source files. The `sourcemap` option lets you change that:

| Value | Behavior |
| --- | --- |
| `true` / `'inline'` | Base64-encode the source map and append it to the bundle (default). |
| `'linked'` | Write a separate `.map` file and add a `sourceMappingURL` comment. |
| `'external'` | Write a separate `.map` file without the comment. |
| `'both'` | Emit both inline and external source maps. |
| `false` | Omit source maps entirely. |

Setting `sourcemap: false` is the main escape hatch for users hitting the Vercel 250MB function size limit — it drops the inline source map from every bundle and also skips the source-map-support runtime shim on the Vercel step function. The tradeoff is that workflow VM stack traces will reference generated code (e.g. `evalmachine.<anonymous>`) rather than your source files.

<Callout type="info">
Setting `sourcemap` explicitly affects **all** generated bundles (steps, workflows, webhook). The legacy `WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING=1` environment variable is narrower — it only toggles source maps on the final workflow wrapper and webhook bundle (which default to off). It continues to work, but new code should use the `sourcemap` option or the `WORKFLOW_SOURCEMAP` environment variable instead.
</Callout>

The option can also be set via the `WORKFLOW_SOURCEMAP` environment variable, which accepts the same values plus `'0'` / `'1'` as aliases for `false` / `true`. Precedence is: explicit config > `WORKFLOW_SOURCEMAP` > per-bundle default.

<Callout type="info">
The `workflows.local` options only affect local development. When deployed to Vercel, the runtime ignores `local` settings and uses the Vercel world automatically.
Expand Down
6 changes: 6 additions & 0 deletions docs/content/docs/getting-started/astro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export default defineConfig({
});
```

`workflow()` accepts an options object:

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |

<Accordion type="single" collapsible>
<AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
<AccordionTrigger className="text-sm">
Expand Down
8 changes: 8 additions & 0 deletions docs/content/docs/getting-started/nestjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,14 @@ WorkflowModule.forRoot({
// Only used when moduleType is 'commonjs'
// Should match the outDir in your tsconfig.json
distDir: 'dist',

// Source maps on generated workflow bundles (default: 'inline').
// Accepts the same values as esbuild's sourcemap option: true, false,
// 'inline', 'linked', 'external', 'both'. Set to false for smaller
// function bundles (useful for staying under the Vercel 250MB function
// size limit) at the cost of stack traces pointing at generated code.
// Can also be set via the WORKFLOW_SOURCEMAP environment variable.
sourcemap: 'inline',
});
```

Expand Down
22 changes: 22 additions & 0 deletions docs/content/docs/getting-started/nitro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ export default defineConfig({

```

### Module options

The `workflow/nitro` module reads its options from `workflow` on your Nitro config.

```typescript title="nitro.config.ts" lineNumbers
import { defineConfig } from "nitro";

export default defineConfig({
modules: ["workflow/nitro"],
workflow: {
runtime: "nodejs22.x",
sourcemap: "inline",
},
});
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `dirs` | `string[]` | — | Directories to scan for workflows and steps. By default, `workflows/` is scanned from the project root and all layer source directories. |
| `runtime` | `string` | `'nodejs22.x'` | Node.js runtime version for Vercel Functions (e.g. `'nodejs22.x'`, `'nodejs24.x'`). |
| `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |

<Accordion type="single" collapsible>
<AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
<AccordionTrigger className="[&_p]:my-0 text-lg [&_p]:text-foreground">
Expand Down
6 changes: 6 additions & 0 deletions docs/content/docs/getting-started/sveltekit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export default defineConfig({
});
```

`workflowPlugin()` accepts an options object:

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |

<Accordion type="single" collapsible>
<AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
<AccordionTrigger className="text-sm">
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const WORKFLOW_ROUTES = [
];

export class LocalBuilder extends BaseBuilder {
constructor() {
constructor(options?: {
sourcemap?: boolean | 'inline' | 'linked' | 'external' | 'both';
}) {
super({
dirs: ['src/pages', 'src/workflows'],
buildTarget: 'astro' as const,
Expand All @@ -29,6 +31,7 @@ export class LocalBuilder extends BaseBuilder {
webhookBundlePath: '', // unused in base
workingDir: process.cwd(),
debugFilePrefix: '_', // Prefix with underscore so Astro ignores debug files
sourcemap: options?.sourcemap,
});
}

Expand Down Expand Up @@ -166,6 +169,7 @@ export class VercelBuilder extends VercelBuildOutputAPIBuilder {
workingDir,
dirs: ['src/pages', 'src/workflows'],
runtime: config?.runtime,
sourcemap: config?.sourcemap,
}),
buildTarget: 'vercel-build-output-api',
debugFilePrefix: '_',
Expand Down
20 changes: 17 additions & 3 deletions packages/astro/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@ import { workflowHotUpdatePlugin } from '@workflow/vite';
import type { AstroIntegration, HookParameters } from 'astro';
import { LocalBuilder, VercelBuilder } from './builder.js';

export function workflowPlugin(): AstroIntegration {
const builder = new LocalBuilder();
export interface WorkflowPluginOptions {
/**
* Controls how source maps are emitted for workflow bundles. Accepts the
* same values as esbuild's `sourcemap` option: `true`/`'inline'` (default),
* `'linked'`, `'external'`, `'both'`, or `false` to omit source maps. Can
* also be set via the `WORKFLOW_SOURCEMAP` environment variable.
*/
sourcemap?: boolean | 'inline' | 'linked' | 'external' | 'both';
}

export function workflowPlugin(
options: WorkflowPluginOptions = {}
): AstroIntegration {
const builder = new LocalBuilder({ sourcemap: options.sourcemap });
const enqueue = createBuildQueue();

return {
Expand Down Expand Up @@ -40,7 +52,9 @@ export function workflowPlugin(): AstroIntegration {
},
'astro:build:done': async () => {
if (process.env.VERCEL_DEPLOYMENT_ID) {
const vercelBuilder = new VercelBuilder();
const vercelBuilder = new VercelBuilder({
sourcemap: options.sourcemap,
});
await vercelBuilder.build();
}
},
Expand Down
73 changes: 68 additions & 5 deletions packages/builders/src/base-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,55 @@ import { getImportPath } from './module-specifier.js';
import { createNodeModuleErrorPlugin } from './node-module-esbuild-plugin.js';
import { createPseudoPackagePlugin } from './pseudo-package-esbuild-plugin.js';
import { createSwcPlugin } from './swc-esbuild-plugin.js';
import type { WorkflowConfig } from './types.js';
import type { SourcemapMode, WorkflowConfig } from './types.js';
import { extractWorkflowGraphs } from './workflows-extractor.js';

const enhancedResolve = promisify(enhancedResolveOriginal);

/**
* Legacy opt-in for source maps on the final workflow wrapper + webhook
* bundles (which default to off, unlike the step/interim workflow bundles
* that default to inline). Superseded by the `sourcemap` config option and
* the `WORKFLOW_SOURCEMAP` environment variable; kept for back-compat.
*/
const EMIT_SOURCEMAPS_FOR_DEBUGGING =
process.env.WORKFLOW_EMIT_SOURCEMAPS_FOR_DEBUGGING === '1';

const VALID_SOURCEMAP_STRINGS = new Set([
'inline',
'linked',
'external',
'both',
]);

/**
* Parse the value of the `WORKFLOW_SOURCEMAP` environment variable into a
* `SourcemapMode`. Returns `undefined` if the env var is unset, empty, or
* unrecognized (a warning is emitted for unrecognized values).
*/
function parseSourcemapEnv(
value: string | undefined
): SourcemapMode | undefined {
if (value === undefined || value === '') return undefined;
switch (value) {
case '0':
case 'false':
return false;
case '1':
case 'true':
return true;
default:
if (VALID_SOURCEMAP_STRINGS.has(value)) {
return value as SourcemapMode;
}
console.warn(
`Ignoring unrecognized WORKFLOW_SOURCEMAP=${value}. ` +
`Expected one of: true, false, 0, 1, inline, linked, external, both.`
);
return undefined;
}
}

/**
* Normalize an array of file paths by appending the `realpath()` of each entry
* (to handle symlinks, e.g. pnpm/workspace layouts) and deduplicating.
Expand Down Expand Up @@ -612,7 +653,7 @@ export abstract class BaseBuilder {
// Steps execute in Node.js context and inline sourcemaps ensure we get
// meaningful stack traces with proper file names and line numbers when errors
// occur in deeply nested function calls across multiple files.
sourcemap: 'inline',
sourcemap: this.resolveSourcemap('inline'),
plugins: [
// Handle pseudo-packages like 'server-only' and 'client-only' by providing
// empty modules. Must run first to intercept these before other resolution.
Expand Down Expand Up @@ -818,7 +859,7 @@ export abstract class BaseBuilder {
// Inline source maps for better stack traces in workflow VM execution.
// This intermediate bundle is executed via runInContext() in a VM, so we need
// inline source maps to get meaningful stack traces instead of "evalmachine.<anonymous>".
sourcemap: 'inline',
sourcemap: this.resolveSourcemap('inline'),
// Use tsconfig for path alias resolution.
// For symlinked configs this uses tsconfigRaw to preserve cwd-relative aliases.
...esbuildTsconfigOptions,
Expand Down Expand Up @@ -992,7 +1033,7 @@ export const POST = workflowEntrypoint(workflowCode);`;
outfile,
// Source maps for the final workflow bundle wrapper (not important since this code
// doesn't run in the VM - only the intermediate bundle sourcemap is relevant)
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
sourcemap: this.resolveSourcemap(EMIT_SOURCEMAPS_FOR_DEBUGGING),
absWorkingDir: this.config.workingDir,
bundle: true,
format,
Expand Down Expand Up @@ -1449,7 +1490,7 @@ export const OPTIONS = handler;`;
'.mjs',
'.cjs',
],
sourcemap: EMIT_SOURCEMAPS_FOR_DEBUGGING,
sourcemap: this.resolveSourcemap(EMIT_SOURCEMAPS_FOR_DEBUGGING),
mainFields: ['module', 'main'],
// Don't externalize anything - bundle everything including workflow packages
external: [],
Expand Down Expand Up @@ -1575,6 +1616,28 @@ export const OPTIONS = handler;`;
}
}

/**
* Resolve the effective source map mode for a given call site. Precedence:
* explicit `sourcemap` config > `WORKFLOW_SOURCEMAP` env var > the call
* site's default. Returned value is passed directly to esbuild's
* `sourcemap` option.
*/
protected resolveSourcemap(defaultMode: SourcemapMode): SourcemapMode {
if (this.config.sourcemap !== undefined) return this.config.sourcemap;
const envMode = parseSourcemapEnv(process.env.WORKFLOW_SOURCEMAP);
if (envMode !== undefined) return envMode;
return defaultMode;
}

/**
* Whether the resolved source map mode emits any source maps at all.
* Used by consumers like the Vercel builder to decide whether to include
* the source-map-support runtime shim in generated functions.
*/
protected get sourcemapsEnabled(): boolean {
return this.resolveSourcemap(true) !== false;
}

/**
* Creates a manifest JSON file containing step/workflow/class metadata
* and graph data for visualization.
Expand Down
4 changes: 3 additions & 1 deletion packages/builders/src/config-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';
import { findUp } from 'find-up';
import JSON5 from 'json5';
import type { WorkflowConfig } from './types.js';
import type { SourcemapMode, WorkflowConfig } from './types.js';

export interface DecoratorOptions {
decorators: boolean;
Expand Down Expand Up @@ -96,6 +96,7 @@ export function createBaseBuilderConfig(options: {
watch?: boolean;
externalPackages?: string[];
runtime?: string;
sourcemap?: SourcemapMode;
}): Omit<WorkflowConfig, 'buildTarget'> {
return {
dirs: options.dirs ?? ['workflows'],
Expand All @@ -107,5 +108,6 @@ export function createBaseBuilderConfig(options: {
webhookBundlePath: '', // Not used by base builder methods
externalPackages: options.externalPackages,
runtime: options.runtime,
sourcemap: options.sourcemap,
};
}
Loading
Loading