Skip to content

@workflow/nitro: steps bundle externalizes sibling workspace packages — no way to set projectRoot (breaks Nuxt/Nitro monorepo dev) #2074

@davidstrouk

Description

@davidstrouk

Bug Report

Environment

  • workflow: 4.2.4
  • @workflow/nitro: 4.0.5
  • @workflow/nuxt: 4.0.5
  • @workflow/builders: 4.0.5
  • nuxt: 4.2.2 (Nitro 2.13.1, Vite 6.4.1)
  • Node.js: v22.20.0
  • Monorepo: pnpm workspaces (apps/* + packages/*)

Description

In a pnpm-workspaces monorepo, a Nuxt app (apps/app, using workflow/nuxt) has workflows that import a sibling workspace package (@acme/shared at packages/shared, consumed as TypeScript source — "type": "module", main: ./src/index.ts, with extensionless relative imports like export * from './db/index').

Under nuxt dev, SSR 500s on every route with:

Cannot find module '/<repo>/packages/shared/src/db/index' imported from /<repo>/packages/shared/src/index.ts

The workflow steps bundle externalizes @acme/shared instead of bundling it, so at runtime Node's native ESM loader hits the package's extensionless relative imports and fails. The production Rollup/Nitro build inlines the package (so the funnel routes work there), which is why this currently only breaks nuxt dev — but the same builder runs for the Vercel build, so it looks latent there too.

Root cause

@workflow/nitro's LocalBuilder / VercelBuilder (dist/builders.js) construct the builder with:

createBaseBuilderConfig({ workingDir: nitro.options.rootDir, /* ...no projectRoot */ })

BaseBuilder then defaults projectRoot = config.projectRoot || config.workingDir (i.e. apps/app). The module-specifier logic computes relative(projectRoot, filePath) for the sibling package → ../../packages/shared/... (escapes projectRoot), so it's treated as external rather than a bundle-able workspace package.

The framework-integrations docs already prescribe the fix — "If your framework integration lives in a subdirectory and your workflows import code from sibling workspace packages, pass projectRoot to BaseBuilder … use the smallest directory that contains every workspace package imported by your workflows." But @workflow/nitro's ModuleOptions only exposes dirs / typescriptPlugin / runtime / sourcemap — there's no way to set or override projectRoot, and the integration doesn't auto-derive it. (The 5.0.0-beta line doesn't add it either.)

Related prior art (both closed/completed, but for @workflow/next / general bundling — the projectRoot handling didn't reach the Nitro/Nuxt integration): #419, #1680.

Reproduction

  1. pnpm-workspaces monorepo with apps/app (Nuxt + workflow/nuxt) and packages/shared (@acme/shared, "type": "module", main: ./src/index.ts, extensionless internal imports).
  2. A workflow under apps/app/server/workflows/* imports from @acme/shared.
  3. Run the app's dev server → request / → 500 with the Cannot find module …/src/db/index error above.

(A minimal repro can be built from workbench/nitro-v3 + a sibling workspace package.)

Proposed fix (happy to open a PR)

Make the Nitro integration honor projectRoot, via either:

  1. Auto-derive the workspace root by walking up from nitro.options.rootDir to the nearest pnpm-workspace.yaml / package.json#workspaces / lockfile, and pass it as projectRoot to createBaseBuilderConfig. Zero-config; matches the docs' guidance.
  2. and/or expose projectRoot (or workspaceRoot) in @workflow/nitro ModuleOptions, forwarded by @workflow/nuxt.

Option 1 (auto-detect, with Option 2 as an explicit override) seems most aligned with the framework-integration design. Which would you prefer? Happy to send a PR with a test modeled on the existing discover-entries / module-specifier projectRoot tests plus a workbench/nitro-v3-based repro.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions