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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ jobs:
- name: Run E2E Tests (Next.js)
run: |
cd workbench/nextjs-turbopack
$job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev }
$job = Start-Job -ScriptBlock { $env:NODE_OPTIONS = $using:env:NODE_OPTIONS; Set-Location $using:PWD; pnpm dev }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏🏼

Start-Sleep -Seconds 15
cd ../..
pnpm vitest run packages/core/e2e/dev.test.ts
Expand Down
8 changes: 4 additions & 4 deletions packages/builders/src/apply-swc-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export async function applySwcTransform(
},
},
},
// TODO: investigate proper source map support as they
// won't even be used in Node.js by default unless we
// intercept errors and apply them ourselves
sourceMaps: false,
// node_modules have invalid source maps often so ignore there
// but enable for first party code
sourceMaps: filename.includes('node_modules') ? false : 'inline',
inlineSourcesContent: true,
minify: false,
});

Expand Down
135 changes: 103 additions & 32 deletions packages/core/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getProtectionBypassHeaders,
hasStepSourceMaps,
hasWorkflowSourceMaps,
isLocalDeployment,
} from './utils';

const deploymentUrl = process.env.DEPLOYMENT_URL;
Expand Down Expand Up @@ -603,16 +604,45 @@ describe('e2e', () => {
// TODO: Known issue - workflow error stack traces are muddled when
// running sveltekit in dev mode
if (
!process.env.DEV_TEST_CONFIG ||
process.env.APP_NAME !== 'sveltekit'
!(
process.env.DEV_TEST_CONFIG &&
process.env.APP_NAME === 'sveltekit'
)
) {
// Stack shows call chain: errorNested1 -> errorNested2 -> errorNested3
expect(result.cause.stack).toContain('errorNested1');
expect(result.cause.stack).toContain('errorNested2');
expect(result.cause.stack).toContain('errorNested3');
expect(result.cause.stack).toContain('errorWorkflowNested');
expect(result.cause.stack).toContain('99_e2e.ts');
expect(result.cause.stack).not.toContain('evalmachine');
// Stack shows call chain: errorNested3 (threw) <- errorNested2 <- errorNested1 <- errorWorkflowNested
const stack = result.cause.stack;
expect(stack).toContain('errorNested3');
expect(stack).toContain('errorNested2');
expect(stack).toContain('errorNested1');
expect(stack).toContain('errorWorkflowNested');
expect(stack).toContain('99_e2e.ts');
expect(stack).not.toContain('evalmachine');

// Verify the call chain order in stack trace (errorNested3 should appear first since it threw)
const errorNested3Idx = stack.indexOf('errorNested3');
const errorNested2Idx = stack.indexOf('errorNested2');
const errorNested1Idx = stack.indexOf('errorNested1');
const errorWorkflowNestedIdx = stack.indexOf(
'errorWorkflowNested'
);

expect(errorNested3Idx).toBeLessThan(errorNested2Idx);
expect(errorNested2Idx).toBeLessThan(errorNested1Idx);
expect(errorNested1Idx).toBeLessThan(errorWorkflowNestedIdx);

// Workflow source maps are not properly supported everywhere. Check the definition
// of hasWorkflowSourceMaps() to see where they are supported
if (hasWorkflowSourceMaps()) {
expect(stack).toContain('99_e2e.ts:568:8');
expect(stack).toContain('99_e2e.ts:572:2');
expect(stack).toContain('99_e2e.ts:576:2');
expect(stack).toContain('99_e2e.ts:582:2');
} else {
expect(stack).not.toContain('99_e2e.ts:568:8');
expect(stack).not.toContain('99_e2e.ts:572:2');
expect(stack).not.toContain('99_e2e.ts:576:2');
expect(stack).not.toContain('99_e2e.ts:582:2');
}
}

const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
Expand All @@ -635,20 +665,26 @@ describe('e2e', () => {
// TODO: Known issue - workflow error stack traces are muddled when
// running sveltekit in dev mode
if (
!process.env.DEV_TEST_CONFIG ||
process.env.APP_NAME !== 'sveltekit'
!(
process.env.DEV_TEST_CONFIG &&
process.env.APP_NAME === 'sveltekit'
)
) {
expect(result.cause.stack).toContain('throwError');
expect(result.cause.stack).toContain('callThrower');
expect(result.cause.stack).toContain('errorWorkflowCrossFile');
expect(result.cause.stack).not.toContain('evalmachine');

// Workflow source maps are not properly supported everyhwere. Check the definition
// Workflow source maps are not properly supported everywhere. Check the definition
// of hasWorkflowSourceMaps() to see where they are supported
if (hasWorkflowSourceMaps()) {
expect(result.cause.stack).toContain('helpers.ts');
expect(result.cause.stack).toContain('helpers.ts:10:8');
expect(result.cause.stack).toContain('helpers.ts:15:2');
expect(result.cause.stack).toContain('99_e2e.ts:589:2');
} else {
expect(result.cause.stack).not.toContain('helpers.ts');
expect(result.cause.stack).not.toContain('helpers.ts:10:8');
expect(result.cause.stack).not.toContain('helpers.ts:15:2');
expect(result.cause.stack).not.toContain('99_e2e.ts:589:2');
}
}

Expand All @@ -669,16 +705,31 @@ describe('e2e', () => {
// Workflow catches the error and returns it
expect(result.caught).toBe(true);
expect(result.message).toContain('Step error message');
// Stack trace contains function name and source file
expect(result.stack).toContain('errorStepFn');
expect(result.stack).not.toContain('evalmachine');

// Source maps are not supported everyhwere. Check the definition
// TODO: Known issue - step error stack traces are muddled when
// running sveltekit in dev mode
if (
!(
process.env.DEV_TEST_CONFIG &&
process.env.APP_NAME === 'sveltekit'
) &&
!(
// stacks do not work in Vercel production
!isLocalDeployment()
)
) {
// Stack trace contains function name and source file
expect(result.stack).toContain('errorStepFn');
expect(result.stack).not.toContain('evalmachine');
}

// Source maps are not supported everywhere. Check the definition
// of hasStepSourceMaps() to see where they are supported
if (hasStepSourceMaps()) {
expect(result.stack).toContain('99_e2e.ts');
expect(result.stack).toContain('99_e2e.ts:597:9');
} else {
expect(result.stack).not.toContain('99_e2e.ts');
// test negated in case we fix it
expect(result.stack).not.toContain('99_e2e.ts:597:9');
}

// Verify step failed via CLI (--withData needed to resolve errorRef)
Expand All @@ -695,12 +746,13 @@ describe('e2e', () => {
expect(failedStep.error.stack).toContain('errorStepFn');
expect(failedStep.error.stack).not.toContain('evalmachine');

// Source maps are not supported everyhwere. Check the definition
// Source maps are not supported everywhere. Check the definition
// of hasStepSourceMaps() to see where they are supported
if (hasStepSourceMaps()) {
expect(failedStep.error.stack).toContain('99_e2e.ts');
expect(failedStep.error.stack).toContain('99_e2e.ts:597:9');
} else {
expect(failedStep.error.stack).not.toContain('99_e2e.ts');
// test negated in case we fix it
expect(failedStep.error.stack).not.toContain('99_e2e.ts:597:9');
}

// Workflow completed (error was caught)
Expand All @@ -722,16 +774,32 @@ describe('e2e', () => {
'Step error from imported helper module'
);
// Stack trace propagates to caught error with function names and source file
expect(result.stack).toContain('throwErrorFromStep');
expect(result.stack).toContain('stepThatThrowsFromHelper');
expect(result.stack).not.toContain('evalmachine');
// TODO: Known issue - step error stack traces are muddled when
// running sveltekit in dev mode
if (
!(
process.env.DEV_TEST_CONFIG &&
process.env.APP_NAME === 'sveltekit'
) &&
!(
// stacks do not work in Vercel production
!isLocalDeployment()
)
) {
expect(result.stack).toContain('throwErrorFromStep');
expect(result.stack).toContain('stepThatThrowsFromHelper');
expect(result.stack).not.toContain('evalmachine');
}

// Source maps are not supported everyhwere. Check the definition
// Source maps are not supported everywhere. Check the definition
// of hasStepSourceMaps() to see where they are supported
if (hasStepSourceMaps()) {
expect(result.stack).toContain('helpers.ts');
expect(result.stack).toContain('helpers.ts:21:9');
expect(result.stack).toContain('helpers.ts:27:3');
} else {
expect(result.stack).not.toContain('helpers.ts');
// test negated in case we fix it
expect(result.stack).not.toContain('helpers.ts:21:9');
expect(result.stack).not.toContain('helpers.ts:27:3');
}

// Verify step failed via CLI - same stack info available there too (--withData needed to resolve errorRef)
Expand All @@ -747,12 +815,15 @@ describe('e2e', () => {
'stepThatThrowsFromHelper'
);
expect(failedStep.error.stack).not.toContain('evalmachine');
// Source maps are not supported everyhwere. Check the definition
// Source maps are not supported everywhere. Check the definition
// of hasStepSourceMaps() to see where they are supported
if (hasStepSourceMaps()) {
expect(failedStep.error.stack).toContain('helpers.ts');
expect(failedStep.error.stack).toContain('helpers.ts:21:9');
expect(failedStep.error.stack).toContain('helpers.ts:27:3');
} else {
expect(failedStep.error.stack).not.toContain('helpers.ts');
// test negated in case we fix it
expect(failedStep.error.stack).not.toContain('helpers.ts:21:9');
expect(failedStep.error.stack).not.toContain('helpers.ts:27:3');
}

// Workflow completed (error was caught)
Expand Down
32 changes: 9 additions & 23 deletions packages/core/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,28 @@ export function isLocalDeployment(): boolean {
* get rid of this strange matrix
*/
export function hasStepSourceMaps(): boolean {
// Next.js does not consume inline sourcemaps AT ALL for step bundles
// TODO: we need to fix this
const appName = process.env.APP_NAME as string;
if (['nextjs-webpack', 'nextjs-turbopack'].includes(appName)) {
return false;
}

// Vercel production builds don't support step source maps
if (process.env.WORKFLOW_VERCEL_ENV === 'production') {
if (!isLocalDeployment()) {
return false;
}

// Vercel preview builds have proper source maps for all other frameworks, EXCEPT sveltekit
if (!isLocalDeployment()) {
return appName !== 'sveltekit';
// Local prod builds don't have source maps
if (isLocalDeployment() && !process.env.DEV_TEST_CONFIG) {
return false;
}

// Vite only works in vercel, not on local prod or dev
if (appName === 'vite') {
// Windows doesn't support step source maps in local dev
if (isLocalDeployment() && process.platform === 'win32') {
return false;
}

// Prod buils for frameworks typically don't consume source maps. So let's disable testing
// in local prod and local postgres tests
if (!process.env.DEV_TEST_CONFIG) {
// vite does not have step source maps in local dev
if (process.env.DEV_TEST_CONFIG && ['vite'].includes(appName)) {
return false;
}

// Works everywhere else (i.e. other frameworks in dev mode)
return true;
}

Expand All @@ -66,18 +59,11 @@ export function hasStepSourceMaps(): boolean {
export function hasWorkflowSourceMaps(): boolean {
const appName = process.env.APP_NAME as string;

// Vercel deployments have proper source map support for workflow errors
if (!isLocalDeployment()) {
return true;
}

// These frameworks currently don't handle sourcemaps correctly in local dev
// TODO: figure out how to get sourcemaps working in these frameworks too
// vite and astro don't have workflow source maps in local dev
if (process.env.DEV_TEST_CONFIG && ['vite', 'astro'].includes(appName)) {
return false;
}

// Works everywhere else
return true;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { types } from 'node:util';
import { inspect, types } from 'node:util';

export function getErrorName(v: unknown): string {
if (types.isNativeError(v)) {
Expand All @@ -9,7 +9,9 @@ export function getErrorName(v: unknown): string {

export function getErrorStack(v: unknown): string {
if (types.isNativeError(v)) {
return v.stack ?? '';
// Use util.inspect to get the formatted error with source maps applied.
// Accessing err.stack directly returns the raw stack without source map resolution.
return inspect(v);
}
return '';
}
24 changes: 17 additions & 7 deletions packages/next/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { transform } from '@swc/core';

// This loader applies the "use workflow"/"use step"
// client transformation
export default async function workflowLoader(
export default function workflowLoader(
this: {
resourcePath: string;
async: () => (err: Error | null, content?: string, sourceMap?: any) => void;
},
source: string | Buffer,
sourceMap: any
): Promise<string> {
) {
const callback = this.async();
const filename = this.resourcePath;
const normalizedSource = source.toString();

// only apply the transform if file needs it
if (!normalizedSource.match(/(use step|use workflow)/)) {
return normalizedSource;
callback(null, normalizedSource, sourceMap);
return;
}

const isTypeScript =
Expand Down Expand Up @@ -64,7 +67,7 @@ export default async function workflowLoader(
}

// Transform with SWC
const result = await transform(normalizedSource, {
transform(normalizedSource, {
filename: relativeFilename,
jsc: {
parser: {
Expand Down Expand Up @@ -94,7 +97,14 @@ export default async function workflowLoader(
inputSourceMap: sourceMap,
sourceMaps: true,
inlineSourcesContent: true,
});

return result.code;
}).then(
(result) => {
// Parse and pass the source map to webpack for proper source map chaining
const map = result.map ? JSON.parse(result.map) : undefined;
callback(null, result.code, map);
},
(err) => {
callback(err);
}
);
}
15 changes: 15 additions & 0 deletions packages/rollup/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ export function workflowTransformPlugin(): Plugin {
return null;
}

// Get the combined source map from previous transforms (if any)
// This is the Rollup way to get input source maps for chaining
let inputSourceMap: string | undefined;
try {
const combinedMap = this.getCombinedSourcemap();
if (combinedMap) {
inputSourceMap = JSON.stringify(combinedMap);
}
} catch {
// getCombinedSourcemap() throws if no previous transforms produced maps
// This is expected for the first transform in the chain
}

const isTypeScript =
id.endsWith('.ts') ||
id.endsWith('.tsx') ||
Expand Down Expand Up @@ -91,6 +104,8 @@ export function workflowTransformPlugin(): Plugin {
minify: false,
sourceMaps: true,
inlineSourcesContent: true,
// Pass input source map from previous transforms for proper source map chaining
inputSourceMap,
});

return {
Expand Down
Loading
Loading