mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
37312edd0a
* Default source maps to dev-on / prod-off Inline source maps are embedded in the step bundle and the intermediate workflow VM bundle, which bloats production function bundles (a problem for the Vercel 250MB limit) even though maps only help when reading a stack trace. Make the default environment-aware in @workflow/builders: inline in development (next dev / nitro dev / Vite-based dev servers, detected via config.watch or NODE_ENV=development) and off in production. The `sourcemap` config option and `WORKFLOW_SOURCEMAP` env var still override in either environment. A production build with no override also drops the source-map-support shim from the Vercel step function. Keep runtime stack remapping graceful and fast when maps are absent (@workflow/core): short-circuit when no frame references the workflow file and memoize the parsed map (or its absence) per bundle, so production failures don't rescan the bundle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): make source-map expectations match dev-on/prod-off default The e2e error-stack tests gate source-map assertions on hasWorkflowSourceMaps() and hasStepSourceMaps(). Now that source maps default to off in production builds, update those helpers: - hasWorkflowSourceMaps(): false for all production builds (local prod, postgres, Vercel — keyed off DEV_TEST_CONFIG), and exclude nest in dev (the Nest integration builds with watch:false / no NODE_ENV=development, so its bundles have no maps). - hasStepSourceMaps(): nest now resolves to a production build (maps off) in both dev and prod. Add unit cases for the dev-vs-prod and nest behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
231 lines
7.1 KiB
TypeScript
231 lines
7.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { BaseBuilder } from './base-builder.js';
|
|
import type { SourcemapMode, StandaloneConfig } from './types.js';
|
|
|
|
/**
|
|
* Minimal subclass that exposes the protected `resolveSourcemap()` and
|
|
* `sourcemapsEnabled` members for testing.
|
|
*/
|
|
class TestBuilder extends BaseBuilder {
|
|
async build(): Promise<void> {
|
|
// no-op
|
|
}
|
|
|
|
public callResolveSourcemap(defaultMode: SourcemapMode): SourcemapMode {
|
|
return this.resolveSourcemap(defaultMode);
|
|
}
|
|
|
|
public get publicSourcemapsEnabled(): boolean {
|
|
return this.sourcemapsEnabled;
|
|
}
|
|
|
|
public get publicDefaultSourcemapMode(): SourcemapMode {
|
|
return this.defaultSourcemapMode;
|
|
}
|
|
|
|
public get publicIsDevelopmentBuild(): boolean {
|
|
return this.isDevelopmentBuild;
|
|
}
|
|
}
|
|
|
|
function createBuilder(
|
|
sourcemap?: SourcemapMode,
|
|
watch?: boolean
|
|
): TestBuilder {
|
|
const config: StandaloneConfig = {
|
|
buildTarget: 'standalone',
|
|
workingDir: '/tmp/workflow-test',
|
|
dirs: ['.'],
|
|
stepsBundlePath: '',
|
|
workflowsBundlePath: '',
|
|
webhookBundlePath: '',
|
|
sourcemap,
|
|
watch,
|
|
};
|
|
return new TestBuilder(config);
|
|
}
|
|
|
|
describe('resolveSourcemap', () => {
|
|
const originalEnv = process.env.WORKFLOW_SOURCEMAP;
|
|
|
|
beforeEach(() => {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalEnv === undefined) {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
} else {
|
|
process.env.WORKFLOW_SOURCEMAP = originalEnv;
|
|
}
|
|
});
|
|
|
|
it('returns the default when no config or env var is set', () => {
|
|
const builder = createBuilder();
|
|
expect(builder.callResolveSourcemap('inline')).toBe('inline');
|
|
expect(builder.callResolveSourcemap(false)).toBe(false);
|
|
expect(builder.callResolveSourcemap(true)).toBe(true);
|
|
});
|
|
|
|
it('prefers explicit config over the default', () => {
|
|
expect(createBuilder(false).callResolveSourcemap('inline')).toBe(false);
|
|
expect(createBuilder('external').callResolveSourcemap('inline')).toBe(
|
|
'external'
|
|
);
|
|
expect(createBuilder('linked').callResolveSourcemap(false)).toBe('linked');
|
|
expect(createBuilder(true).callResolveSourcemap('inline')).toBe(true);
|
|
});
|
|
|
|
it('prefers explicit config over environment variable', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = 'inline';
|
|
expect(createBuilder(false).callResolveSourcemap('inline')).toBe(false);
|
|
expect(createBuilder('external').callResolveSourcemap('inline')).toBe(
|
|
'external'
|
|
);
|
|
});
|
|
|
|
it('uses environment variable when config is not set', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = 'false';
|
|
expect(createBuilder().callResolveSourcemap('inline')).toBe(false);
|
|
|
|
process.env.WORKFLOW_SOURCEMAP = 'true';
|
|
expect(createBuilder().callResolveSourcemap(false)).toBe(true);
|
|
|
|
for (const mode of ['inline', 'linked', 'external', 'both'] as const) {
|
|
process.env.WORKFLOW_SOURCEMAP = mode;
|
|
expect(createBuilder().callResolveSourcemap('inline')).toBe(mode);
|
|
}
|
|
});
|
|
|
|
it('accepts "0" / "1" as environment variable aliases for false / true', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = '0';
|
|
expect(createBuilder().callResolveSourcemap('inline')).toBe(false);
|
|
|
|
process.env.WORKFLOW_SOURCEMAP = '1';
|
|
expect(createBuilder().callResolveSourcemap(false)).toBe(true);
|
|
});
|
|
|
|
it('falls back to default when env var is empty or unrecognized', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = '';
|
|
expect(createBuilder().callResolveSourcemap('inline')).toBe('inline');
|
|
|
|
// Suppress the expected warning
|
|
const originalWarn = console.warn;
|
|
console.warn = () => {};
|
|
try {
|
|
process.env.WORKFLOW_SOURCEMAP = 'nonsense';
|
|
expect(createBuilder().callResolveSourcemap('inline')).toBe('inline');
|
|
} finally {
|
|
console.warn = originalWarn;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('defaultSourcemapMode / isDevelopmentBuild', () => {
|
|
const originalNodeEnv = process.env.NODE_ENV;
|
|
const originalSourcemapEnv = process.env.WORKFLOW_SOURCEMAP;
|
|
|
|
beforeEach(() => {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalNodeEnv === undefined) {
|
|
delete process.env.NODE_ENV;
|
|
} else {
|
|
process.env.NODE_ENV = originalNodeEnv;
|
|
}
|
|
if (originalSourcemapEnv === undefined) {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
} else {
|
|
process.env.WORKFLOW_SOURCEMAP = originalSourcemapEnv;
|
|
}
|
|
});
|
|
|
|
it('defaults to off in production (no watch, NODE_ENV not development)', () => {
|
|
process.env.NODE_ENV = 'production';
|
|
const builder = createBuilder();
|
|
expect(builder.publicIsDevelopmentBuild).toBe(false);
|
|
expect(builder.publicDefaultSourcemapMode).toBe(false);
|
|
});
|
|
|
|
it('defaults to inline when config.watch is true', () => {
|
|
// Even with a production NODE_ENV, an active watch/dev server opts in.
|
|
process.env.NODE_ENV = 'production';
|
|
const builder = createBuilder(undefined, true);
|
|
expect(builder.publicIsDevelopmentBuild).toBe(true);
|
|
expect(builder.publicDefaultSourcemapMode).toBe('inline');
|
|
});
|
|
|
|
it('defaults to inline when NODE_ENV is development', () => {
|
|
process.env.NODE_ENV = 'development';
|
|
const builder = createBuilder();
|
|
expect(builder.publicIsDevelopmentBuild).toBe(true);
|
|
expect(builder.publicDefaultSourcemapMode).toBe('inline');
|
|
});
|
|
});
|
|
|
|
describe('sourcemapsEnabled', () => {
|
|
const originalNodeEnv = process.env.NODE_ENV;
|
|
const originalEnv = process.env.WORKFLOW_SOURCEMAP;
|
|
|
|
beforeEach(() => {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
// Pin to production so the environment-aware default is deterministic;
|
|
// individual tests opt into dev via watch/NODE_ENV as needed.
|
|
process.env.NODE_ENV = 'production';
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalNodeEnv === undefined) {
|
|
delete process.env.NODE_ENV;
|
|
} else {
|
|
process.env.NODE_ENV = originalNodeEnv;
|
|
}
|
|
if (originalEnv === undefined) {
|
|
delete process.env.WORKFLOW_SOURCEMAP;
|
|
} else {
|
|
process.env.WORKFLOW_SOURCEMAP = originalEnv;
|
|
}
|
|
});
|
|
|
|
it('is false by default in production', () => {
|
|
expect(createBuilder().publicSourcemapsEnabled).toBe(false);
|
|
});
|
|
|
|
it('is true by default in development (watch)', () => {
|
|
expect(createBuilder(undefined, true).publicSourcemapsEnabled).toBe(true);
|
|
});
|
|
|
|
it('is true by default in development (NODE_ENV)', () => {
|
|
process.env.NODE_ENV = 'development';
|
|
expect(createBuilder().publicSourcemapsEnabled).toBe(true);
|
|
});
|
|
|
|
it('is false when config sourcemap is false', () => {
|
|
expect(createBuilder(false).publicSourcemapsEnabled).toBe(false);
|
|
});
|
|
|
|
it('is true for any non-false config value', () => {
|
|
for (const mode of [
|
|
true,
|
|
'inline',
|
|
'linked',
|
|
'external',
|
|
'both',
|
|
] as const) {
|
|
expect(createBuilder(mode).publicSourcemapsEnabled).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('is true in production when WORKFLOW_SOURCEMAP env opts in', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = 'inline';
|
|
expect(createBuilder().publicSourcemapsEnabled).toBe(true);
|
|
});
|
|
|
|
it('is false when WORKFLOW_SOURCEMAP env is false', () => {
|
|
process.env.WORKFLOW_SOURCEMAP = 'false';
|
|
expect(createBuilder().publicSourcemapsEnabled).toBe(false);
|
|
});
|
|
});
|