vercel / vercel/workflow

Decorators in a workspace package fail to parse after 4.5.0 (SWC transform failed: Expression expected)

Open
#3,356 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.4k
Forks
365
Avg merge
2d 11h
Merged PRs (30d)
169

Description

Summary

In a pnpm workspace monorepo, upgrading workflow from 4.5.0 to any later 4.x release makes the SWC workflow plugin fail to parse TypeScript decorators in a workspace package that the Next.js app depends on. Every decorated class in that package fails with:

SWC transform failed:   x Expression expected
    ,-[lib/shared/src/services/taskService.ts:46:1]
 46 | @processCoachInjectable()
    : ^
Caused by:
    Syntax Error

experimentalDecorators: true is set in every relevant tsconfig.json, and the same build is clean on 4.5.0.

Versions
workflow @workflow/builders web build
4.5.0 4.1.0 pass, 0 errors
4.6.0 4.1.1 fail, 41 errors
4.6.2 4.1.3 fail, 41 errors
4.7.0 4.1.4 fail, 41 errors
4.8.0 4.1.5 fail, 41 errors

Next.js 16, pnpm 10.26.2, Node 22.22.2, macOS. Same result in CI on Linux.

Setup
repo-root/
  pnpm-workspace.yaml          <- no tsconfig.json at this level
  web/                         <- Next.js app, withWorkflow(nextConfig)
    tsconfig.json              <- experimentalDecorators: true
  lib/shared/                  <- workspace package "@lib/shared"
    tsconfig.json              <- experimentalDecorators: true, emitDecoratorMetadata: true
    src/…                      <- ~150 classes decorated for a tsyringe DI container

web/tsconfig.json maps @lib/shared/workflows/* to ../lib/shared/src/workflows/* so the compiler sees "use step" / "use workflow" directives in source. Workflow steps reach the DI container, which lazily loads the service graph with a dynamic import(). On 4.6+ the compiler follows that dynamic import into the workspace package's TypeScript source and then fails to parse the decorators there.

What I ruled out

Not a missing experimentalDecorators. All three tsconfigs set it. Suspecting the new workspace-root resolution meant findUp landed somewhere without a tsconfig, I added a root tsconfig.json with experimentalDecorators and emitDecoratorMetadata. Still 41 errors — including after deleting web/.next to rule out a cached transform.

Not user-configurable. applySwcTransform takes projectRoot and moduleSpecifierRoot, and swc-esbuild-plugin reads options.projectRoot, but workflow/next never passes either — grepping the shipped dist for those names returns nothing. withWorkflow's documented options are workflows.local.port only.

Suspected cause

Diffing @workflow/builders 4.1.0 against 4.1.5, the change that lines up is the new resolveProjectRoot() in config-helpers.js, which walks up to the workspace root by looking for pnpm-workspace.yaml (then lockfiles), together with the split of the old single projectRoot into projectRoot (tsconfig lookup) and moduleSpecifierRoot (package resolution).

The consequence for a monorepo: files in lib/shared/src used to be project-local relative to web/, and are now resolved as a workspace package. isProjectLocalFile(path, moduleSpecifierRoot) returns false for them where it previously returned true. I did not chase exactly which branch then drops the decorator parser options, but the version boundary and the diff both point here.

Workaround

Route the workspace package's internals through its exports map so the compiler sees compiled dist JavaScript, which has no decorators left to parse. That clears all 41 errors, but within a single compilation the src and dist copies of a class are distinct nominal types, so every boundary where they meet needs its types realigned — impractical past a handful of files.

Ask

Either restore the pre-4.6 project-root default, or expose projectRoot / moduleSpecifierRoot through withWorkflow so a monorepo can opt out. If treating workspace packages as packages is intentional, documenting that decorated source in a workspace package must be consumed as compiled output would at least make the upgrade path discoverable — there is currently no migration note for 4.5 → 4.6 (the shipped docs/changelog/index.mdx reads ## 2026 / - TBD).

Happy to test a patch against the real repo.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing @workflow/builders 4.1.0 and 4.1.5 around resolveProjectRoot() in config-helpers.js, then trace projectRoot and moduleSpecifierRoot through applySwcTransform, swc-esbuild-plugin, and workflow/next. Reproduce the failure with decorated TypeScript in a workspace package; done means the Next.js build succeeds after the supported fix or the required monorepo behavior and migration path are documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, typescript
Domain
build-system, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.