From 2a14260fa02003226be22df33d088180b1d9f6ea Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 29 Jun 2026 13:16:46 -0700 Subject: [PATCH 1/3] Fix Workflow loader source map warnings (#2693) --- .changeset/quiet-sourcemap-warnings.md | 5 ++ .github/workflows/e2e-community-world.yml | 3 +- .github/workflows/tests.yml | 10 ++- packages/core/e2e/dev.test.ts | 104 ++++++++++++++++++++++ packages/core/e2e/local-build.test.ts | 99 +++++++++++++++++++- packages/next/src/loader.test.ts | 52 +++++++++++ packages/next/src/loader.ts | 17 +++- 7 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 .changeset/quiet-sourcemap-warnings.md create mode 100644 packages/next/src/loader.test.ts diff --git a/.changeset/quiet-sourcemap-warnings.md b/.changeset/quiet-sourcemap-warnings.md new file mode 100644 index 0000000000..e0d88a92aa --- /dev/null +++ b/.changeset/quiet-sourcemap-warnings.md @@ -0,0 +1,5 @@ +--- +"@workflow/next": patch +--- + +Disable Workflow loader source-map emission for node_modules files to avoid noisy SWC input source-map warnings. diff --git a/.github/workflows/e2e-community-world.yml b/.github/workflows/e2e-community-world.yml index 157afe86e3..708fc3ddb8 100644 --- a/.github/workflows/e2e-community-world.yml +++ b/.github/workflows/e2e-community-world.yml @@ -125,7 +125,8 @@ jobs: - name: Run E2E Tests run: | - cd workbench/${{ inputs.app-name }} && pnpm dev & + export DEV_SERVER_LOG_PATH="$GITHUB_WORKSPACE/dev-server-$APP_NAME-$WORLD_ID.log" + (cd "workbench/$APP_NAME" && pnpm dev 2>&1 | tee "$DEV_SERVER_LOG_PATH") & cd "$GITHUB_WORKSPACE" echo "Waiting for dev server to start..." && sleep 15 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 64bf662a87..2b1977518b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -398,7 +398,8 @@ jobs: - name: Run E2E Tests run: | - cd "${{ steps.prepare-workbench.outputs.workbench_app_path }}" && pnpm dev & + export DEV_SERVER_LOG_PATH="$GITHUB_WORKSPACE/dev-server-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.log" + (cd "$WORKBENCH_APP_PATH" && pnpm dev 2>&1 | tee "$DEV_SERVER_LOG_PATH") & echo "starting tests in 10 seconds" && sleep 10 pnpm vitest run packages/core/e2e/dev.test.ts; sleep 10 pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-dev-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.json @@ -477,7 +478,8 @@ jobs: - name: Run E2E Tests run: | - cd "${{ steps.prepare-workbench.outputs.workbench_app_path }}" && pnpm start & + export PROD_SERVER_LOG_PATH="$GITHUB_WORKSPACE/prod-server-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.log" + (cd "$WORKBENCH_APP_PATH" && pnpm start 2>&1 | tee "$PROD_SERVER_LOG_PATH") & echo "starting tests in 10 seconds" && sleep 10 pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-prod-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.json env: @@ -574,7 +576,8 @@ jobs: - name: Run E2E Tests run: | - cd "${{ steps.prepare-workbench.outputs.workbench_app_path }}" && pnpm start & + export PROD_SERVER_LOG_PATH="$GITHUB_WORKSPACE/prod-server-postgres-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.log" + (cd "$WORKBENCH_APP_PATH" && pnpm start 2>&1 | tee "$PROD_SERVER_LOG_PATH") & echo "starting tests in 10 seconds" && sleep 10 pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-postgres-${{ matrix.app.name }}-${{ matrix.app.canary && 'canary' || 'stable' }}.json env: @@ -638,6 +641,7 @@ jobs: run: | cd workbench/nextjs-turbopack $logFile = "$env:GITHUB_WORKSPACE/nextjs-server.log" + $env:DEV_SERVER_LOG_PATH = $logFile $job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev *>&1 | Tee-Object -FilePath $using:logFile } Start-Sleep -Seconds 15 cd ../.. diff --git a/packages/core/e2e/dev.test.ts b/packages/core/e2e/dev.test.ts index 2910d9b925..760011a503 100644 --- a/packages/core/e2e/dev.test.ts +++ b/packages/core/e2e/dev.test.ts @@ -18,6 +18,10 @@ export interface DevTestConfig { workflowsDir?: string; } +const SOURCE_MAP_WARNING = 'failed to read input source map'; +const SOURCE_MAP_FIXTURE_PACKAGE = 'workflow-sourcemap-warning-fixture'; +const SOURCE_MAP_COMMENT = '//# sourceMapping' + 'URL=index.js.map'; + function getConfigFromEnv(): DevTestConfig | null { const envConfig = process.env.DEV_TEST_CONFIG; if (envConfig) { @@ -64,6 +68,7 @@ export function createDevTests(config?: DevTestConfig) { path.join('.well-known', 'workflow', 'v1', 'step', 'route.js') ); const restoreFiles: Array<{ path: string; content: string }> = []; + const restoreDirectories: string[] = []; const fetchWithTimeout = (pathname: string) => { if (!deploymentUrl) { @@ -176,7 +181,14 @@ export function createDevTests(config?: DevTestConfig) { await prewarm(); } } + await Promise.all( + restoreDirectories.map((dir) => + fs.rm(dir, { recursive: true, force: true }) + ) + ); + await prewarm(); restoreFiles.length = 0; + restoreDirectories.length = 0; }, CLEANUP_HOOK_TIMEOUT_MS); test('should rebuild on workflow change', { timeout: 30_000 }, async () => { @@ -519,6 +531,98 @@ ${apiFileContent}` }); } ); + + test.runIf(process.env.APP_NAME === 'nextjs-turbopack')( + 'should not log source map warnings for workflow node_modules imports', + { timeout: 70_000 }, + async () => { + const packageDir = path.join( + appPath, + 'node_modules', + SOURCE_MAP_FIXTURE_PACKAGE + ); + const workflowFile = path.join( + appPath, + workflowsDir, + 'source-map-warning-fixture.ts' + ); + const apiFile = path.join(appPath, finalConfig.apiFilePath); + const apiFileContent = await fs.readFile(apiFile, 'utf8'); + + await fs.mkdir(packageDir, { recursive: true }); + restoreDirectories.push(packageDir); + await fs.writeFile( + path.join(packageDir, 'package.json'), + JSON.stringify( + { + name: SOURCE_MAP_FIXTURE_PACKAGE, + version: '0.0.0', + type: 'module', + main: './index.js', + types: './index.d.ts', + }, + null, + 2 + ) + ); + await fs.writeFile( + path.join(packageDir, 'index.js'), + `export const sourceMapWarningFixtureValue = Symbol.for('workflow-serialize').description ?? 'workflow-serialize'; +${SOURCE_MAP_COMMENT} +` + ); + await fs.writeFile( + path.join(packageDir, 'index.d.ts'), + `export declare const sourceMapWarningFixtureValue: string; +` + ); + await fs.writeFile( + workflowFile, + `import { sourceMapWarningFixtureValue } from '${SOURCE_MAP_FIXTURE_PACKAGE}'; + +async function readSourceMapWarningFixture() { + 'use step'; + return sourceMapWarningFixtureValue; +} + +export async function sourceMapWarningFixtureWorkflow() { + 'use workflow'; + return readSourceMapWarningFixture(); +} +` + ); + restoreFiles.push({ path: workflowFile, content: '' }); + restoreFiles.push({ path: apiFile, content: apiFileContent }); + + await fs.writeFile( + apiFile, + `import '${finalConfig.apiFileImportPath}/${workflowsDir}/source-map-warning-fixture'; +${apiFileContent}` + ); + + await pollUntil({ + description: + 'generated workflow to include sourceMapWarningFixtureWorkflow', + timeoutMs: 50_000, + check: async () => { + await fetchWithTimeout('/api/chat'); + const workflowContent = await fs.readFile( + generatedWorkflow, + 'utf8' + ); + expect(workflowContent).toContain( + 'sourceMapWarningFixtureWorkflow' + ); + }, + }); + + const devServerLogPath = process.env.DEV_SERVER_LOG_PATH; + if (devServerLogPath) { + const log = await fs.readFile(devServerLogPath, 'utf8'); + expect(log).not.toContain(SOURCE_MAP_WARNING); + } + } + ); }); } diff --git a/packages/core/e2e/local-build.test.ts b/packages/core/e2e/local-build.test.ts index 594771715f..28b3f4b87e 100644 --- a/packages/core/e2e/local-build.test.ts +++ b/packages/core/e2e/local-build.test.ts @@ -106,6 +106,81 @@ const CJS_STEP_BUNDLE_PROJECTS: Record = { '.vercel/output/functions/.well-known/workflow/v1/step.func/index.js', }; +const SOURCE_MAP_WARNING = 'failed to read input source map'; +const SOURCE_MAP_FIXTURE_PACKAGE = 'workflow-sourcemap-warning-fixture'; +const SOURCE_MAP_COMMENT = '//# sourceMapping' + 'URL=index.js.map'; + +async function writeFileWithParents( + filePath: string, + content: string +): Promise { + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, content); +} + +async function setupNextSourceMapWarningFixture( + appPath: string +): Promise<() => Promise> { + const packageDir = path.join( + appPath, + 'node_modules', + SOURCE_MAP_FIXTURE_PACKAGE + ); + const workflowPath = path.join( + appPath, + 'workflows', + 'source-map-warning-fixture.ts' + ); + + await writeFileWithParents( + path.join(packageDir, 'package.json'), + JSON.stringify( + { + name: SOURCE_MAP_FIXTURE_PACKAGE, + version: '0.0.0', + type: 'module', + main: './index.js', + types: './index.d.ts', + }, + null, + 2 + ) + ); + await writeFileWithParents( + path.join(packageDir, 'index.js'), + `export const sourceMapWarningFixtureValue = Symbol.for('workflow-serialize').description ?? 'workflow-serialize'; +${SOURCE_MAP_COMMENT} +` + ); + await writeFileWithParents( + path.join(packageDir, 'index.d.ts'), + `export declare const sourceMapWarningFixtureValue: string; +` + ); + await writeFileWithParents( + workflowPath, + `import { sourceMapWarningFixtureValue } from '${SOURCE_MAP_FIXTURE_PACKAGE}'; + +async function readSourceMapWarningFixture() { + 'use step'; + return sourceMapWarningFixtureValue; +} + +export async function sourceMapWarningFixtureWorkflow() { + 'use workflow'; + return readSourceMapWarningFixture(); +} +` + ); + + return async () => { + await Promise.all([ + fs.rm(packageDir, { recursive: true, force: true }), + fs.rm(workflowPath, { force: true }), + ]); + }; +} + describe.each([ 'example', 'nextjs-webpack', @@ -142,13 +217,31 @@ describe.each([ expect(importResult.output).toContain('workflow/sveltekit import ok'); } - const result = await runBuildWithRetry(appPath); + const cleanup = + project === 'nextjs-turbopack' + ? await setupNextSourceMapWarningFixture(appPath) + : async () => {}; + const preserveFixtureForBuiltOutput = + project === 'nextjs-turbopack' && process.env.CI === 'true'; + + let result: CommandResult; + try { + result = await runBuildWithRetry(appPath); + } finally { + // CI starts the just-built app in the same prepared workbench path after + // this test. Turbopack production bundles can retain references to the + // fixture package/source, so keep them available until the job ends. + if (!preserveFixtureForBuiltOutput) { + await cleanup(); + } + } expect(result.output).not.toContain('Error:'); + expect(result.output).not.toContain(SOURCE_MAP_WARNING); if (usesVercelWorld()) { const diagnosticsManifestPath = path.join( - getWorkbenchAppPath(project), + appPath, '.vercel/output/diagnostics/workflows-manifest.json' ); await fs.access(diagnosticsManifestPath); @@ -158,7 +251,7 @@ describe.each([ const cjsBundlePath = CJS_STEP_BUNDLE_PROJECTS[project]; if (cjsBundlePath) { const bundleContent = await readFileIfExists( - path.join(getWorkbenchAppPath(project), cjsBundlePath) + path.join(appPath, cjsBundlePath) ); expect(bundleContent).not.toBeNull(); expect(bundleContent).toContain('var __import_meta_url'); diff --git a/packages/next/src/loader.test.ts b/packages/next/src/loader.test.ts new file mode 100644 index 0000000000..5f0d31faa5 --- /dev/null +++ b/packages/next/src/loader.test.ts @@ -0,0 +1,52 @@ +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { getLoaderSourceMapOptions } from './loader.js'; + +describe('getLoaderSourceMapOptions', () => { + it('emits source maps for app files and uses the upstream source map', () => { + const upstreamMap = { version: 3, sources: ['input.ts'], mappings: '' }; + + expect( + getLoaderSourceMapOptions( + join(process.cwd(), 'app', 'workflow.ts'), + upstreamMap + ) + ).toEqual({ + inputSourceMap: upstreamMap, + sourceMaps: true, + inlineSourcesContent: true, + }); + }); + + it('disables implicit input source map loading when app files have no upstream map', () => { + expect( + getLoaderSourceMapOptions(join(process.cwd(), 'app', 'workflow.ts'), null) + ).toEqual({ + inputSourceMap: false, + sourceMaps: true, + inlineSourcesContent: true, + }); + }); + + it('does not emit source maps for node_modules files', () => { + expect( + getLoaderSourceMapOptions( + join( + process.cwd(), + 'node_modules', + '.pnpm', + 'pkg@1.0.0', + 'node_modules', + 'pkg', + 'dist', + 'index.js' + ), + { version: 3, sources: ['index.js'], mappings: '' } + ) + ).toEqual({ + inputSourceMap: false, + sourceMaps: false, + inlineSourcesContent: false, + }); + }); +}); diff --git a/packages/next/src/loader.ts b/packages/next/src/loader.ts index 69162385e8..3e221ccdd5 100644 --- a/packages/next/src/loader.ts +++ b/packages/next/src/loader.ts @@ -201,6 +201,19 @@ async function getRelativeFilenameForSwc( return relativeFilename; } +function isNodeModulesPath(filename: string): boolean { + return /(?:^|[/\\])node_modules(?:[/\\]|$)/.test(filename); +} + +export function getLoaderSourceMapOptions(filename: string, sourceMap: any) { + const shouldEmitSourceMaps = !isNodeModulesPath(filename); + return { + inputSourceMap: shouldEmitSourceMaps ? (sourceMap ?? false) : false, + sourceMaps: shouldEmitSourceMaps, + inlineSourcesContent: shouldEmitSourceMaps, + }; +} + // This loader applies the "use workflow"/"use step" transform. // All matching files are transformed in client mode; the SWC plugin decides // per-function whether to emit workflow or step bindings based on the @@ -305,9 +318,7 @@ export default function workflowLoader( }, }, minify: false, - inputSourceMap: sourceMap, - sourceMaps: true, - inlineSourcesContent: true, + ...getLoaderSourceMapOptions(filename, sourceMap), }); let transformedMap = sourceMap; From 7661b8d7cad675e90856fbc75d0533b5ce1b85bb Mon Sep 17 00:00:00 2001 From: Nathan Colosimo <110621881+NathanColosimo@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:14:09 -0700 Subject: [PATCH 2/3] Keep source map fixture alive for dev E2E --- packages/core/e2e/dev.test.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/core/e2e/dev.test.ts b/packages/core/e2e/dev.test.ts index 760011a503..032edb3fa7 100644 --- a/packages/core/e2e/dev.test.ts +++ b/packages/core/e2e/dev.test.ts @@ -68,7 +68,6 @@ export function createDevTests(config?: DevTestConfig) { path.join('.well-known', 'workflow', 'v1', 'step', 'route.js') ); const restoreFiles: Array<{ path: string; content: string }> = []; - const restoreDirectories: string[] = []; const fetchWithTimeout = (pathname: string) => { if (!deploymentUrl) { @@ -181,14 +180,7 @@ export function createDevTests(config?: DevTestConfig) { await prewarm(); } } - await Promise.all( - restoreDirectories.map((dir) => - fs.rm(dir, { recursive: true, force: true }) - ) - ); - await prewarm(); restoreFiles.length = 0; - restoreDirectories.length = 0; }, CLEANUP_HOOK_TIMEOUT_MS); test('should rebuild on workflow change', { timeout: 30_000 }, async () => { @@ -550,7 +542,9 @@ ${apiFileContent}` const apiFileContent = await fs.readFile(apiFile, 'utf8'); await fs.mkdir(packageDir, { recursive: true }); - restoreDirectories.push(packageDir); + // The generated dev output can retain this import until the server + // shuts down, including while the full E2E suite runs after this file. + // Keep the ignored node_modules fixture available for that lifetime. await fs.writeFile( path.join(packageDir, 'package.json'), JSON.stringify( From a18c23eafbbf0b13da643005bc33c0204a3b6110 Mon Sep 17 00:00:00 2001 From: Nathan Colosimo <110621881+NathanColosimo@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:19:38 -0700 Subject: [PATCH 3/3] Avoid loader test stub race on Windows --- packages/next/src/loader.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/loader.test.ts b/packages/next/src/loader.test.ts index 5f0d31faa5..4570a0b6cf 100644 --- a/packages/next/src/loader.test.ts +++ b/packages/next/src/loader.test.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { getLoaderSourceMapOptions } from './loader.js'; +import { getLoaderSourceMapOptions } from './loader.ts'; describe('getLoaderSourceMapOptions', () => { it('emits source maps for app files and uses the upstream source map', () => {