Skip to content

fix(check): diagnose CommonJS app roots instead of reporting 0 routes - #701

Merged
blove merged 3 commits into
mainfrom
blove/check-commonjs-app-root
Sep 17, 2026
Merged

blove merged 3 commits into
mainfrom
blove/check-commonjs-app-root

Conversation

@blove

@blove blove commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #685.

Problem

When an app root's package.json lacks "type": "module", b4 check reported B4.run app is valid: 0 routes discovered. and exited 0. The tsx loader compiles each route index.ts as CommonJS, so import() returns the ESM/CommonJS interop wrapper ({ default: { __esModule: true, default: <agent> } }), inferRouteKind returns null, and the route is silently dropped. Routes using a named export const workflow still worked, so the failure only hit the scaffolded export default agent(...) shape. The same wrapper also made b4.config.ts load as { __esModule, default }, silently ignoring the author's config.

Fix

  • B4_E1006 App root package.json is not an ES module. findB4App reads the app root's package.json and rejects it unless "type": "module" is set, naming the file, what it found, and the fix. This runs before config loading, so every command that discovers routes (check, dev, build, verify, routes, typegen, the testing harness) fails early instead of misbehaving. An unparseable package.json is also reported instead of being passed to tsx.
  • B4_E1007 Route entry has no recognisable export. A route directory whose index.ts exports nothing B4.run recognises is now an error naming the file and the exports it found. When the namespace has the CommonJS interop shape ("module.exports" key or default.__esModule === true), the message names the nearest package.json as the likely cause. A directory that is not a route should carry a _ prefix, and the message says so. This replaces the previous "skip silently" behaviour in discoverRoutes.
  • B4AppError (new, @b4run/core) carries a registry code so b4 check renders the [B4_Exxxx] See <docs> footer through the existing CliError path.

Tests

  • @b4run/sdk: registry entries for both codes.
  • @b4run/core: discovery rejects a missing / "commonjs" / malformed package.json with B4_E1006; an index.ts with no recognisable export throws B4_E1007 naming the file; pure unit tests for the interop-shape explanation (explainUnrecognisedRouteExports).
  • @b4run/cli: b4 check emits both codes with docs links in-process, plus a built-CLI subprocess test that reproduces the issue exactly (app root without "type", default-export agent) and the nested-package.json variant. The subprocess test is necessary: under vitest the route import goes through vite-node, which compiles TypeScript regardless of package.json type, so the interop shape is only reachable through the real tsx loader.
  • Test fixtures that wrote a bare {} package.json (about 140 lines across the CLI tests plus the four committed test/fixtures/contracts/* apps) now declare "type": "module", which is what a real app must have.

Docs

  • b4 check bullets for both codes on /docs/cli.
  • errors.mdx regenerated. The generator's intro said "B4 failure" while the committed page said "B4.run failure"; the generator now matches the page so regeneration is reproducible.
  • B4AppError on the core API page (docs completeness check).
  • SEO lastmod manifest refreshed for the three edited pages.

Verification

Local: pnpm lint, pnpm typecheck, pnpm test (the only failures in the full run were a workspace 64 MiB timeout and a Docker-smoke fake-marker timeout, both of which pass in isolation and were CPU contention from concurrent sessions), node scripts/check-docs.mjs, node scripts/check-changesets.mjs, pnpm test:release-integrity.

Manual, with the built CLI against the issue's repro:

$ b4 check --cwd <app-without-type-module>
Validation failed: Invalid B4.run app at <root>: <root>/package.json must set "type": "module" (found no "type" field).
B4.run loads b4.config.ts and every route index.ts as ES modules. Without "type": "module" Node compiles them as CommonJS, and routes exported with `export default agent(...)` are not recognised.
Add "type": "module" to <root>/package.json.
  [B4_E1006] See https://b4.run/docs/cli#b4-check

Not released; changeset is patch for @b4run/sdk, @b4run/core, @b4run/cli.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
b4-run Ready Ready Preview Sep 17, 2026 4:45pm UTC

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

blove and others added 3 commits September 17, 2026 09:43
`b4 check` reported a clean `0 routes discovered` for an app whose
package.json lacked `"type": "module"`: tsx compiled every route as
CommonJS, `import()` handed back the `{ default: { __esModule, default } }`
interop wrapper, `inferRouteKind` returned null, and the route was silently
dropped (#685).

- `findB4App` now rejects an app root whose package.json does not set
  `"type": "module"` (B4_E1006), naming the file and the fix.
- Route discovery now fails (B4_E1007) when a route index.ts has no
  recognisable export, naming the file, the exports it found, and — when the
  namespace has the CommonJS interop shape — the nearest package.json as the
  likely cause. Non-route directories belong behind a `_` prefix.
- New `B4AppError` in @b4run/core carries the registry code so `b4 check`
  renders the `[B4_Exxxx] See <docs>` footer.
- Tests: pure unit tests for the interop-shape explanation, discovery tests
  for both codes, and a built-CLI test that exercises the real tsx path
  (vitest routes imports through vite-node, which never produces the shape).
- Fixtures that wrote a bare `{}` package.json now declare `"type": "module"`.
- Docs: `b4 check` bullets, regenerated error-code table, `B4AppError` on
  the core API page; the generator's intro wording is aligned with the
  committed page.

Closes #685

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ange

Self-review follow-ups on the #685 fix.

- Node's own package.json reader tolerates a UTF-8 BOM, but `JSON.parse`
  rejects it, so the new app-root check turned a manifest Node loads happily
  into "package.json is not valid JSON" and failed every command. Strip a
  leading BOM before parsing, with a regression test that fails without it.
- Document the transition in upgrading.mdx: B4_E1006 also breaks apps that
  worked, because Node's CommonJS lexer hoists `export const workflow` into a
  named export, so route discovery succeeded while b4.config.ts was silently
  read through the interop wrapper and every setting on it ignored.
- Refresh the SEO lastmod manifest for the edited page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/check-commonjs-app-root branch from 0210d14 to c103fa9 Compare September 17, 2026 16:43
@blove
blove merged commit 814f4f9 into main Sep 17, 2026
29 of 30 checks passed
@blove
blove deleted the blove/check-commonjs-app-root branch September 17, 2026 17:25
blove added a commit that referenced this pull request Sep 17, 2026
Only the generated SEO lastmod manifest conflicted; regenerated rather
than hand-merged. No code overlap with #694, #701, or the
software-factory work order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active and 1 inactive deployments
vercel-preview — c103fa98 Deployed Sep 17, 2026 by blove via vercel-native #1965
Preview — c103fa98 Deployed Sep 17, 2026 by vercel[bot]
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.

b4 check silently reports 0 routes when package.json lacks "type": "module"

1 participant