Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -24,7 +24,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 @@ -33,6 +35,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 @@ -232,6 +235,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 @@ -18,14 +18,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 @@ -568,7 +609,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 @@ -772,7 +813,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 @@ -944,7 +985,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 @@ -1213,7 +1254,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 @@ -1339,6 +1380,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