Skip to content

fix(builders): bundle aliased project-local helpers instead of externalizing#1885

Merged
TooTallNate merged 1 commit intomainfrom
fix/bundle-aliased-project-local-helpers
May 1, 2026
Merged

fix(builders): bundle aliased project-local helpers instead of externalizing#1885
TooTallNate merged 1 commit intomainfrom
fix/bundle-aliased-project-local-helpers

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate commented Apr 30, 2026

Summary

Fixes a regression where step files reaching project-local helpers via tsconfig paths / esbuild aliases / self-referencing package names crash at runtime in non-vite-node environments (Nitro, plain Node ESM, etc.) with:

  • Package subpath './lib/foo' is not defined by "exports"
  • ERR_MODULE_NOT_FOUND

Reported by Mux: muxinc/ai#193.

Root cause

#1613 added an esbuild fallback resolver so aliased imports resolve via tsconfig paths. When the resolved file is project-local, it gets externalized as a relative path. But the externalized file's source on disk can contain further alias imports (import "@my-pkg/lib/providers" etc.). Node's ESM loader doesn't know about build-time aliases and falls through to the package's exports map — where the subpath isn't defined — and throws.

#1644 fixed this for Node builtins (because esbuild marks builtins external). Genuinely project-local files aren't external, so the !esbuildResult.external guard doesn't help them.

Fix

When the esbuild fallback resolves a bare specifier to a project-local file, bundle it inline instead of externalizing it as a relative path. Bundling resolves all alias imports at build time, so the runtime never sees an unresolvable specifier. Relative-path imports are unaffected and continue to externalize as before — they don't have this problem because Node can resolve relative paths fine.

Relationship to #1613

For the aliased-project-local-helper case specifically, this fix produces the same end behavior as pre-#1613: such helpers get bundled inline. The mechanism is different (we still run the esbuild fallback resolver, but use its result to choose "let esbuild bundle this" rather than "externalize"), so this is not a literal revert:

#1613's stated vitest motivation (externalize aliased imports so vi.mock() can intercept them) appears to have been illusory — per workbench/vitest/MOCKING.md, vi.mock() cannot intercept step dependencies regardless of externalization, due to three independent architectural layers (bundle bypasses Vitest's module system, setup runs before mocks, etc.). So we lose nothing user-visible by reverting to inline bundling here.

Tests

  • Updated existing 'rewrites path-aliased imports to relative paths' → renamed to 'bundles path-aliased project-local imports inline', asserts inlined bundling.
  • Added regression test 'bundles transitive aliased imports inside aliased helpers (Mux self-referencing package regression)' reproducing the exact Mux scenario: step → aliased helper → transitive aliased helper.

All 133 builder tests pass; typecheck clean.

Trade-off

Aliased helpers now contribute to step bundle size (inlined into each step bundle that imports them). This restores pre-#1613 behavior for that path. Relative-path imports continue to externalize as before, so this only affects projects using path aliases for project-local helpers.

…alizing

Externalizing aliased helpers was unsafe: their source on disk could
contain further alias imports that Node's ESM loader doesn't know about,
causing 'Package subpath ... is not defined by "exports"' /
ERR_MODULE_NOT_FOUND at runtime.

Now bundle inline so all alias resolution happens at build time.
Copilot AI review requested due to automatic review settings April 30, 2026 22:23
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: 7a19ee7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@workflow/builders Patch
@workflow/astro Patch
@workflow/cli Patch
@workflow/nest Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/vitest Patch
workflow Patch
@workflow/world-testing Patch
@workflow/nuxt Patch
@workflow/ai Patch
@workflow/core Patch
@workflow/web-shared Patch
@workflow/web Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Apr 30, 2026 10:26pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Apr 30, 2026 10:26pm
example-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-astro-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-express-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-fastify-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-hono-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-nitro-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-nuxt-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workbench-vite-workflow Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Apr 30, 2026 10:26pm
workflow-swc-playground Ready Ready Preview, Comment Apr 30, 2026 10:26pm
workflow-web Ready Ready Preview, Comment Apr 30, 2026 10:26pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.043s (-0.7%) 1.006s (~) 0.963s 10 1.00x
💻 Local Express 0.043s (-2.3%) 1.005s (~) 0.962s 10 1.01x
🐘 Postgres Express 0.059s (+2.4%) 1.009s (~) 0.950s 10 1.39x
🐘 Postgres Next.js (Turbopack) 0.068s 1.010s 0.942s 10 1.59x
🐘 Postgres Nitro 0.107s (+12.0% 🔺) 1.053s (+1.0%) 0.947s 10 2.49x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.127s (~) 2.006s (~) 0.879s 10 1.00x
💻 Local Express 1.128s (~) 2.006s (~) 0.879s 10 1.00x
🐘 Postgres Next.js (Turbopack) 1.140s 2.011s 0.871s 10 1.01x
🐘 Postgres Express 1.148s (~) 2.010s (~) 0.862s 10 1.02x
🐘 Postgres Nitro 1.351s (+18.6% 🔺) 2.032s (+1.1%) 0.681s 10 1.20x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 10.865s 11.018s 0.153s 3 1.00x
🐘 Postgres Express 10.921s (~) 11.021s (~) 0.100s 3 1.01x
💻 Local Nitro 10.926s (~) 11.024s (~) 0.098s 3 1.01x
💻 Local Express 10.950s (~) 11.024s (~) 0.074s 3 1.01x
🐘 Postgres Nitro 11.916s (+9.6% 🔺) 12.358s (+12.1% 🔺) 0.442s 3 1.10x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 14.443s 15.019s 0.576s 4 1.00x
🐘 Postgres Express 14.459s (-0.9%) 15.017s (~) 0.558s 4 1.00x
💻 Local Express 14.960s (~) 15.279s (+1.7%) 0.320s 4 1.04x
💻 Local Nitro 15.008s (~) 15.280s (-4.7%) 0.272s 4 1.04x
🐘 Postgres Nitro 16.191s (+10.9% 🔺) 16.545s (+10.1% 🔺) 0.354s 4 1.12x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 13.913s (-0.7%) 14.166s (-2.9%) 0.252s 7 1.00x
🐘 Postgres Next.js (Turbopack) 13.989s 14.307s 0.318s 7 1.01x
💻 Local Express 16.389s (-1.3%) 17.033s (~) 0.643s 6 1.18x
💻 Local Nitro 16.830s (~) 17.030s (~) 0.200s 6 1.21x
🐘 Postgres Nitro 16.941s (+21.3% 🔺) 17.417s (+21.7% 🔺) 0.476s 6 1.22x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.246s 2.009s 0.762s 15 1.00x
🐘 Postgres Express 1.274s (+1.1%) 2.009s (~) 0.735s 15 1.02x
💻 Local Nitro 1.490s (-8.7% 🟢) 2.005s (-3.3%) 0.515s 15 1.20x
💻 Local Express 1.505s (+1.1%) 2.005s (~) 0.500s 15 1.21x
🐘 Postgres Nitro 1.682s (+32.0% 🔺) 2.264s (+12.6% 🔺) 0.581s 14 1.35x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.322s (-1.6%) 3.008s (~) 0.686s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.375s 3.010s 0.635s 10 1.02x
🐘 Postgres Nitro 2.645s (+12.5% 🔺) 3.251s (+8.1% 🔺) 0.606s 10 1.14x
💻 Local Express 2.823s (-4.4%) 3.107s (-10.0% 🟢) 0.284s 10 1.22x
💻 Local Nitro 3.031s (-3.6%) 3.565s (-8.2% 🟢) 0.534s 9 1.31x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.479s (~) 4.011s (~) 0.532s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.631s 4.009s 0.379s 8 1.04x
🐘 Postgres Nitro 4.113s (+18.2% 🔺) 4.802s (+19.8% 🔺) 0.688s 7 1.18x
💻 Local Express 7.057s (-15.4% 🟢) 7.768s (-13.9% 🟢) 0.711s 4 2.03x
💻 Local Nitro 8.783s (+5.2% 🔺) 9.273s (+2.8%) 0.490s 4 2.52x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.221s 2.008s 0.786s 15 1.00x
🐘 Postgres Express 1.258s (~) 2.008s (~) 0.750s 15 1.03x
🐘 Postgres Nitro 1.368s (+8.8% 🔺) 2.022s (+0.7%) 0.654s 15 1.12x
💻 Local Express 1.489s (-21.4% 🟢) 2.006s (-15.1% 🟢) 0.518s 15 1.22x
💻 Local Nitro 1.562s (-16.3% 🟢) 2.006s (-14.3% 🟢) 0.444s 15 1.28x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.331s (~) 3.010s (~) 0.678s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.367s 3.008s 0.641s 10 1.02x
🐘 Postgres Nitro 2.697s (+15.3% 🔺) 3.196s (+6.2% 🔺) 0.499s 10 1.16x
💻 Local Express 2.874s (-8.2% 🟢) 3.108s (-17.4% 🟢) 0.234s 10 1.23x
💻 Local Nitro 3.046s (-0.6%) 3.885s (~) 0.839s 8 1.31x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.477s (-0.6%) 4.010s (~) 0.533s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.638s 4.010s 0.372s 8 1.05x
🐘 Postgres Nitro 4.182s (+20.2% 🔺) 4.674s (+16.6% 🔺) 0.493s 7 1.20x
💻 Local Express 7.772s (-11.7% 🟢) 8.271s (-10.8% 🟢) 0.499s 4 2.24x
💻 Local Nitro 8.996s (-1.6%) 9.525s (-5.0%) 0.529s 4 2.59x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.769s 1.006s 0.238s 60 1.00x
🐘 Postgres Express 0.818s (-2.5%) 1.023s (~) 0.206s 59 1.06x
💻 Local Nitro 1.008s (+2.8%) 1.401s (+28.0% 🔺) 0.393s 43 1.31x
💻 Local Express 1.013s (+3.0%) 1.672s (+55.4% 🔺) 0.659s 36 1.32x
🐘 Postgres Nitro 1.267s (+54.5% 🔺) 1.798s (+78.6% 🔺) 0.530s 34 1.65x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.900s 2.076s 0.176s 44 1.00x
🐘 Postgres Express 1.902s (-3.8%) 2.029s (-10.1% 🟢) 0.127s 45 1.00x
🐘 Postgres Nitro 2.766s (+43.5% 🔺) 3.276s (+56.0% 🔺) 0.510s 29 1.46x
💻 Local Express 3.021s (~) 3.586s (~) 0.565s 26 1.59x
💻 Local Nitro 3.036s (~) 3.760s (~) 0.723s 24 1.60x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 3.815s 4.009s 0.194s 30 1.00x
🐘 Postgres Express 3.910s (-2.0%) 4.252s (-2.7%) 0.342s 29 1.02x
🐘 Postgres Nitro 6.554s (+59.7% 🔺) 7.054s (+53.2% 🔺) 0.500s 18 1.72x
💻 Local Express 8.969s (-2.6%) 9.479s (-5.4% 🟢) 0.509s 13 2.35x
💻 Local Nitro 9.245s (-0.6%) 10.019s (~) 0.774s 12 2.42x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.250s 1.007s 0.757s 60 1.00x
🐘 Postgres Express 0.290s (+2.6%) 1.007s (~) 0.717s 60 1.16x
🐘 Postgres Nitro 0.517s (+82.6% 🔺) 1.119s (+11.0% 🔺) 0.601s 54 2.07x
💻 Local Express 0.578s (+3.2%) 1.004s (~) 0.426s 60 2.32x
💻 Local Nitro 0.594s (-1.8%) 1.004s (-1.7%) 0.411s 60 2.38x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.480s 1.006s 0.526s 90 1.00x
🐘 Postgres Express 0.509s (~) 1.007s (~) 0.497s 90 1.06x
🐘 Postgres Nitro 0.746s (+50.3% 🔺) 1.224s (+21.6% 🔺) 0.478s 74 1.55x
💻 Local Express 2.356s (-6.2% 🟢) 3.008s (~) 0.652s 30 4.91x
💻 Local Nitro 2.517s (-0.8%) 3.009s (~) 0.492s 30 5.24x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.764s 1.006s 0.243s 120 1.00x
🐘 Postgres Express 0.815s (-0.5%) 1.009s (-0.8%) 0.194s 119 1.07x
🐘 Postgres Nitro 1.014s (+28.4% 🔺) 1.404s (+39.4% 🔺) 0.390s 86 1.33x
💻 Local Express 10.338s (-7.6% 🟢) 11.029s (-7.6% 🟢) 0.691s 11 13.53x
💻 Local Nitro 11.144s (~) 11.849s (+1.6%) 0.705s 11 14.59x
💻 Local Next.js (Turbopack) ⚠️ missing - - - -
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.206s (-3.6%) 1.005s (~) 0.013s (~) 1.019s (~) 0.813s 10 1.00x
💻 Local Express 0.208s (+4.3%) 1.004s (~) 0.010s (-15.7% 🟢) 1.016s (~) 0.809s 10 1.01x
🐘 Postgres Next.js (Turbopack) 0.208s 1.000s 0.002s 1.011s 0.803s 10 1.01x
🐘 Postgres Express 0.212s (+3.3%) 0.994s (~) 0.002s (-6.3% 🟢) 1.010s (~) 0.798s 10 1.03x
🐘 Postgres Nitro 0.268s (+30.5% 🔺) 0.996s (~) 0.115s (+7533.3% 🔺) 1.122s (+11.0% 🔺) 0.855s 10 1.30x
💻 Local Next.js (Turbopack) ⚠️ missing - - - - -
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.594s 1.009s 0.004s 1.021s 0.427s 59 1.00x
🐘 Postgres Express 0.614s (-2.6%) 1.005s (~) 0.004s (+2.2%) 1.021s (~) 0.407s 59 1.03x
💻 Local Express 0.760s (~) 1.011s (-1.7%) 0.009s (-7.6% 🟢) 1.021s (-1.8%) 0.261s 59 1.28x
🐘 Postgres Nitro 0.860s (+37.7% 🔺) 1.265s (+25.7% 🔺) 0.086s (+1983.4% 🔺) 1.383s (+35.3% 🔺) 0.524s 44 1.45x
💻 Local Nitro 0.953s (+13.6% 🔺) 1.011s (~) 0.009s (-9.2% 🟢) 1.226s (+9.9% 🔺) 0.273s 49 1.60x
💻 Local Next.js (Turbopack) ⚠️ missing - - - - -
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.950s 1.225s 0.000s 1.232s 0.282s 49 1.00x
🐘 Postgres Express 0.990s (+3.1%) 1.212s (-5.2% 🟢) 0.000s (-54.0% 🟢) 1.228s (-6.0% 🟢) 0.238s 50 1.04x
💻 Local Express 1.217s (-0.6%) 2.021s (~) 0.000s (~) 2.023s (~) 0.805s 30 1.28x
💻 Local Nitro 1.247s (+2.0%) 2.021s (~) 0.000s (+100.0% 🔺) 2.023s (~) 0.776s 30 1.31x
🐘 Postgres Nitro 1.776s (+83.3% 🔺) 2.173s (+74.1% 🔺) 0.000s (-14.3% 🟢) 2.228s (+77.2% 🔺) 0.452s 28 1.87x
💻 Local Next.js (Turbopack) ⚠️ missing - - - - -
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.810s (+2.2%) 2.174s (~) 0.000s (+Infinity% 🔺) 2.188s (~) 0.378s 28 1.00x
🐘 Postgres Next.js (Turbopack) 1.813s 2.106s 0.000s 2.112s 0.299s 29 1.00x
🐘 Postgres Nitro 2.593s (+44.7% 🔺) 3.136s (+46.5% 🔺) 0.000s (+40.0% 🔺) 3.189s (+46.7% 🔺) 0.596s 20 1.43x
💻 Local Express 3.379s (-2.5%) 3.973s (-1.5%) 0.001s (+1.6%) 3.976s (-1.5%) 0.597s 16 1.87x
💻 Local Nitro 3.624s (+7.0% 🔺) 4.100s (+1.7%) 0.001s (~) 4.103s (+1.7%) 0.479s 15 2.00x
💻 Local Next.js (Turbopack) ⚠️ missing - - - - -

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Express 15/21
🐘 Postgres Next.js (Turbopack) 14/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 18/21
Next.js (Turbopack) 🐘 Postgres 21/21
Nitro 🐘 Postgres 12/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: success

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 989 0 67 1056
✅ 💻 Local Development 1066 0 86 1152
✅ 📦 Local Production 1066 0 86 1152
✅ 🐘 Local Postgres 1066 0 86 1152
✅ 🪟 Windows 96 0 0 96
✅ 📋 Other 270 0 18 288
Total 4553 0 343 4896

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 89 0 7
✅ example 89 0 7
✅ express 89 0 7
✅ fastify 89 0 7
✅ hono 89 0 7
✅ nextjs-turbopack 94 0 2
✅ nextjs-webpack 94 0 2
✅ nitro 89 0 7
✅ nuxt 89 0 7
✅ sveltekit 89 0 7
✅ vite 89 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 90 0 6
✅ express-stable 90 0 6
✅ fastify-stable 90 0 6
✅ hono-stable 90 0 6
✅ nextjs-turbopack-canary 77 0 19
✅ nextjs-turbopack-stable 96 0 0
✅ nextjs-webpack-canary 77 0 19
✅ nextjs-webpack-stable 96 0 0
✅ nitro-stable 90 0 6
✅ nuxt-stable 90 0 6
✅ sveltekit-stable 90 0 6
✅ vite-stable 90 0 6
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 90 0 6
✅ express-stable 90 0 6
✅ fastify-stable 90 0 6
✅ hono-stable 90 0 6
✅ nextjs-turbopack-canary 77 0 19
✅ nextjs-turbopack-stable 96 0 0
✅ nextjs-webpack-canary 77 0 19
✅ nextjs-webpack-stable 96 0 0
✅ nitro-stable 90 0 6
✅ nuxt-stable 90 0 6
✅ sveltekit-stable 90 0 6
✅ vite-stable 90 0 6
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 90 0 6
✅ express-stable 90 0 6
✅ fastify-stable 90 0 6
✅ hono-stable 90 0 6
✅ nextjs-turbopack-canary 77 0 19
✅ nextjs-turbopack-stable 96 0 0
✅ nextjs-webpack-canary 77 0 19
✅ nextjs-webpack-stable 96 0 0
✅ nitro-stable 90 0 6
✅ nuxt-stable 90 0 6
✅ sveltekit-stable 90 0 6
✅ vite-stable 90 0 6
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 96 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 90 0 6
✅ e2e-local-postgres-nest-stable 90 0 6
✅ e2e-local-prod-nest-stable 90 0 6

📋 View full workflow run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a runtime regression in @workflow/builders step bundling where project-local helpers imported via tsconfig paths / esbuild alias / self-referencing package names could be externalized to source files that still contain aliased imports, causing Node ESM runtime failures in non-vite-node environments (e.g., Nitro, plain Node ESM).

Changes:

  • Update the SWC esbuild plugin resolution behavior to bundle aliased, project-local files inline instead of externalizing them.
  • Update/extend builder tests to assert inline bundling for aliased project-local helpers, including a regression test covering the Mux self-referencing package scenario.
  • Add a changeset for a patch release of @workflow/builders describing the fix and rationale.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
packages/builders/src/swc-esbuild-plugin.ts Treat project-local results from the esbuild alias/paths fallback as bundle-inlined (return null) rather than externalized.
packages/builders/src/swc-esbuild-plugin.test.ts Update the alias test to assert inlining and add a transitive alias regression test (Mux scenario).
.changeset/bundle-aliased-project-local-helpers.md Patch changeset documenting the runtime error fix and behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@karthikscale3 karthikscale3 left a comment

Choose a reason for hiding this comment

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

AI Review

The fix is correct and the right call, but it's worth understanding the subtle trade-off it introduces.

What I like:

The root cause diagnosis is solid. The old behavior was fundamentally broken for a common pattern — self-referencing packages are pretty standard in monorepo setups (Mux, nx, turborepo-style projects all do this). return null to defer back to esbuild is also the idiomatic way to say "let esbuild handle this normally" — it's a clean one-liner fix rather than a complex workaround.

The comment about vi.mock() is also a nice honest admission — the original justification for externalizing in #1613 (so vitest can intercept via vi.mock()) was apparently wrong anyway, since step dependencies can't be mocked regardless of externalization due to architectural constraints. So there's genuinely no regression on the vitest side.

Where I'd push back or want more clarity:

The bundle size trade-off is real and could bite people. If you have a shared helper imported by 10 step files, it now gets inlined into 10 separate bundles instead of being shared as an external module. For a project with many steps and non-trivial helpers, that's a meaningful size increase — and it affects cold start time since each step bundle is larger.

The PR acknowledges this but frames it as "restores pre-#1613 behavior," which is a bit of a hand-wave. Pre-#1613 behavior was arguably also wrong in this regard — this is a good opportunity to note it as a known limitation rather than a resolved state.

What's missing:

There's no attempt at a smarter middle ground: you could theoretically detect whether a project-local aliased helper itself contains alias imports, and only then choose inlining. But honestly that's probably over-engineering it — the simpler blanket rule (all aliased project-local files → inline) is far easier to reason about and maintain, and correctness beats bundle size optimization here.

Bottom line: Merge it. The correctness fix is unambiguous, the regression test directly covers the reported bug, and the bundle size trade-off is acceptable given the alternative is runtime crashes in production.

@TooTallNate TooTallNate merged commit e0ec429 into main May 1, 2026
162 of 170 checks passed
@TooTallNate TooTallNate deleted the fix/bundle-aliased-project-local-helpers branch May 1, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants