Decorators in a workspace package fail to parse after 4.5.0 (SWC transform failed: Expression expected)
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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