Skip to content

v4.2.0

Latest

Choose a tag to compare

@Tobbe Tobbe released this 13 May 19:13
· 2457 commits to main since this release

Release Notes

Prisma typedSql support

It's now possible to configure parameters to pass to prisma generate when a new client is generated. This makes it possible to pass --sql, which is needed for typedSql support.

See more here: #1769

yarn cedar setup neon

Switch from the default sqlite database to a Neon Postgres database. It's free to use. No signup required. Just run the setup command and it will provision a database for you ready to use.

Even if you (eventually) want to use another Postgres provider, this is a pretty easy way to configure everything to get Prisma and your app set up for Postgres.

CEDAR_DELAY_API_RESTART

I've said this before, but I'm preparing to remove the RWJS_DELAY_RESTART environment variable. In this release I introduced a warning if that env var is detected. In the rare event you used this (previously undocumented) environment variable, please use CEDAR_DELAY_API_RESTART instead.

Changelog

🚀 Features

feat(ud): Register routes with UD using addEntry (#1723) by @Tobbe

This PR adds per-route addEntry() UD registrations. Each discovered API function (GraphQL, auth, health, regular functions) gets its own virtual module and UD store entry

feat(ud): Phase 5 - one Vite process (#1718) by @Tobbe

Use only one Vite process for building both web and api

feat(cli): setup neon (#1762) by @Tobbe

Add setup command for using a new Neon Postgres database

feat(codemods): Prisma v7 tsconfig module (#1729) by @Tobbe

Set "module": "node20" in api/tsconfig.json to allow require(esm)

feat(ud): `cedar serve --ud` (#1727) by @Tobbe

Match cedar serve for the --ud path

feat(cli): Add `prisma generate` args to cedar.toml (#1769) by @Tobbe

Allow setting "prismaGenerateArgs": ["--sql"] in cedar.toml, which will be used when Cedar internally generates the Prisma Client

🛠️ Fixes

fix(build): Restore v4 backwards compatibility (#1720) by @Tobbe

Cedar v4.1.0 accidentally broke v4 compatibility. This PR tries to bring it back. So users can go from v4.0.0 to v4.2.0 without needing any code or tooling changes

fix(api): support Fetch Request in getAuthProviderHeader (#1750) by @bellcoTech

Problem

Bearer-token auth silently fails when an @cedarjs/api handler is invoked via the Web Standard / middleware path (a Fetch Request rather than a Lambda APIGatewayProxyEvent). A request with valid auth-provider and Authorization: Bearer … headers comes back as unauthenticated, and getAuthenticationContext returns undefined — so any auth provider that uses Bearer tokens (Auth0, Supabase non-cookie, Clerk JWT, custom JWT, etc.) is broken on that path.

Root cause

getAuthProviderHeader (in packages/api/src/auth/index.ts) does:

Object.keys(event?.headers ?? {}).find(
  (key) => key.toLowerCase() === AUTH_PROVIDER_HEADER,
)

For a Lambda event, event.headers is a plain object, so Object.keys returns the header names. For a Fetch Request, event.headers is a Headers instance — Object.keys(headersInstance) returns [], the function returns undefined, and the short-circuit at the top of getAuthenticationContext decides the request is unauthenticated:

if (!typeFromHeader && !cookieHeader) {
  return undefined
}

So the Bearer-token code path is never even attempted on the middleware path.

Fix

When headers exposes a .get() method (the Headers API), look the value up directly via headers.get('auth-provider'). Fall back to the existing plain-object iteration for Lambda callers, so existing handlers behave exactly as they do today.

Surface area is intentionally tiny — one function, one file — and the Lambda path is the explicit fallback, so the change is additive rather than a behaviour swap.

Checks

Per CONTRIBUTING.md:

  • yarn check — clean (no constraint or dedupe issues)
  • yarn test in packages/api — 245 passing, including a new regression test in packages/api/src/auth/__tests__/getAuthenticationContext.test.ts that exercises Request + auth-provider header + Authorization: Bearer …
  • yarn build in packages/api — clean (ESM + CJS)
  • yarn lint — clean (via pre-commit hook)

No new dependencies.

fix(ud): Build order and function fallback (#1725) by @Tobbe

Fix build order so that the ud output isn't overwritten

Support both handleRequest and handler, where the latter is wrapped by wrapLegacyHandler()

fix(cca): Yarn ESM overlay package.json deps and resolutions (#1712) by @Tobbe

These changes should have been part of #1673

fix(ud): Integrate UD's vite plugin (#1755) by @Tobbe

Add @universal-deploy/vite the Cedar's internal array of vite plugins. Let UD handle virtual module generation etc

fix(ud): Update the UD serv help message (#1728) by @Tobbe

The underlying tech for UD isn't important for the help message

fix(cli): Detect already generated prisma client (#1767) by @Tobbe

The Cedar cli tried to detect if a Prisma Client had already been generated or not. If it had, generation was skipped.

The problem was that the check was broken. It checked for CJS syntax when Prisma was actually using ESM syntax.

This broken check means the client was always regenerated every time the dev server was started. Turns out this was actually good. Otherwise schema changes wouldn't be reflected in the client.

I've now switched to a hash-based approach instead. If the computed hash for the prisma config and schema files changes, a new client needs to be generated. If the hash is the same, the old client can be reused.

fix(cli): collapse isLockSet existsSync/statSync race (#1753) by @bellcoTech

Problem

cedar build (and any other command that goes through setLock / isLockSet — including the update-check middleware that runs on most CLI commands) can crash with:

Error: ENOENT: no such file or directory, stat '.cedar/locks/<id>'
    at Object.statSync (node:fs:1626:25)
    at isLockSet (.../@cedarjs/cli/dist/lib/locking.js:…)
    at setLock (.../@cedarjs/cli/dist/lib/locking.js:…)

We hit it consistently on Vercel deploys, where the build cache restores .cedar/locks/<id> as a dangling symlink — but the underlying race exists on any host. The error propagates up through every caller (there is no try/catch around setLock in the CLI plumbing) and aborts the build before it produces any output.

Root cause

isLockSet (in packages/cli/src/lib/locking.ts) is a classic check-then-act:

const exists = fs.existsSync(lockfilePath)
if (!exists) {
  return false
}

const createdAt = fs.statSync(lockfilePath).birthtimeMs   // ← can throw ENOENT

The two filesystem calls aren't atomic, so the lockfile can disappear between them in two realistic ways:

  1. Concurrent unset. Another process unsets the lock between the existsSync and the statSync. unsetLock in this same file already wraps its fs.rmSync in a try/catch that swallows ENOENT for exactly this reason, but isLockSet doesn't.
  2. Broken symlink restored by a build cache. Vercel's incremental builds restore .cedar/locks/<id> as a dangling symlink. existsSync returns true (the symlink node itself exists), but statSync follows the link and throws ENOENT because the target file is gone.

Either case throws straight through isLockSet, which is on the hot path of every long-running CLI command via setLock and the update-check middleware. The result is a build that fails on the very first lock query.

Fix

Drop the existsSync pre-check and statSync directly, treating ENOENT as "not locked" and re-throwing anything else. This is exactly the pattern unsetLock in the same file already uses for the analogous rmSync call, so it lines up with the existing style and pairs naturally with the existing isErrorWithCode helper:

let createdAt: number
try {
  createdAt = fs.statSync(lockfilePath).birthtimeMs
} catch (error) {
  if (isErrorWithCode(error, 'ENOENT')) {
    return false
  }
  throw error
}

if (Date.now() - createdAt > 3600000) {
  unsetLock(identifier)
  return false
}

return true

Real fs errors that aren't ENOENT — permission errors, EIO, etc. — are still surfaced unchanged, so the lock layer doesn't paper over genuine filesystem problems.

Test changes

locking.test.ts and updateCheck.test.ts both had a fs.statSync mock of the form:

fs.statSync = vi.fn(() => ({ birthtimeMs: Date.now() }))

…with the comment "Prevent the appearance of stale locks". That mock returned a fresh birthtimeMs for every call, regardless of whether the lockfile actually existed in the underlying memfs filesystem — which only worked because the production code happened to gate on existsSync first. Now that statSync is the gating call, the mock has to mirror real fs semantics:

fs.statSync = vi.fn((lockfilePath) => {
  if (!vol.existsSync(lockfilePath as string)) {
    const error = new Error(
      `ENOENT: no such file or directory, stat '${lockfilePath}'`,
    ) as NodeJS.ErrnoException
    error.code = 'ENOENT'
    throw error
  }
  return { birthtimeMs: Date.now() }
})

Same change applied in both files. Without it the updateCheck tests would assume the lock is always set, which masks the bug being fixed here.

Two new regression tests in locking.test.ts cover the production code paths:

  • Returns false when statSync throws ENOENT — simulates the broken-symlink / concurrent-unset race directly.
  • Re-throws non-ENOENT statSync errors — guards against the obvious over-correction of swallowing all errors.

Checks

Per CONTRIBUTING.md:

  • yarn check — clean (no constraint or dedupe issues)
  • yarn test src/lib/__tests__/locking.test.ts src/lib/__tests__/updateCheck.test.ts in packages/cli — 30 passing (11 locking + 19 updateCheck)
  • yarn build in packages/cli — clean
  • yarn lint — clean (via pre-commit hook)

No new dependencies. Pre-existing failures in unrelated CLI tests (cwd.test.js) reproduce on main and are not introduced by this PR.

fix(cli): replace unknown-as cast in prerender flatMap with proper return type (#1697) by @mvanhorn

Fixes #1626.

What changed

In packages/cli/src/commands/prerenderHandler.ts, the routesToPrerender.flatMap callback used a type lie to satisfy TypeScript:

return [] as unknown as { title: string; task: () => Promise<void> }

Replaced with an explicit return type on the callback, so both branches return arrays that flatMap flattens normally:

type PrerenderTask = { title: string; task: () => Promise<void> }
return routesToPrerender.flatMap((routeToPrerender): PrerenderTask[] => {
  if (routerPathFilter && routeToPrerender.path !== routerPathFilter) {
    return []
  }
  ...
  return [{ title: ..., task: async () => { ... } }]
})

Also removed the TODO comment and the dangling link to a redwoodjs/graphql PR; the cast was the entire reason that comment block existed.

Why this matters

as unknown as is the strongest type assertion TypeScript offers — it silences any error, including ones that would catch real bugs in the same file. Here it was hiding nothing dangerous (flatMap does the right thing with []), but the pattern is easy to copy-paste into spots where it's not safe. Replacing it with an explicit callback return type closes the door without changing behavior.

Verification

  • flatMap([] | [{...}]) flattens to [{...}] — same shape Listr2 received before.
  • Diff is contained to one function. No call sites change; the resulting listrTasks array still has the same element type.
fix(prisma): Explain schema.prisma config settings (#1761) by @Tobbe

Had this comment first, but it felt like it was too long

// For CJS Cedar apps:
// It might feel a little out of place to have .mts files in the codebase, and
// especially importing them like we do in `src/lib/db`.
// For ESM Cedar apps:
// It might feel a little weird to ask Prisma to generate CJS compatible format.
//
// Setting `moduleFormat = "cjs"` and using `.mts` extensions is however the
// only combination that makes Prisma 7, which is ESM-only, work in CJS Cedar
// apps while also having the same configuration work for ESM Cedar apps.
//
// The only reason this works is thanks to Node 24's support for require(esm).
// https://www.prisma.io/docs/orm/prisma-schema/overview/generators

So I shortened it to what we have now, and then pointing here for more info.

This all came out of the discussion on PR #1752

📚 Docs

docs(tutorial): fix chapter 1 — CLI commands, callouts, app name (#1705) by @lisa-assistant

Changes

yarn redwoodyarn cedar across all chapter 1 pages:

  • yarn redwood generate page home /yarn cedar generate page home /
  • yarn redwood generate page aboutyarn cedar generate page about
  • yarn redwood g layout blogyarn cedar g layout blog
  • yarn redwood devyarn cedar dev
  • All prose references to the redwood CLI updated to cedar

Tutorial app renamed: redwoodblogcedarblog

Discourse link removed from prerequisites (was linking to the RedwoodJS community forum), Discord link kept.

Broken :::info callouts fixed: MDX 3 requires :::info[Title] (bracket syntax) for titled admonitions — the old :::info Title (space syntax) renders as literal text. Fixed in:

  • prerequisites.md — "Installing Node and Yarn"
  • first-page.md — "Automatic import of pages in the Routes file"
  • second-page.md — "Code-splitting each page"
  • layouts.md — "The src alias" and "Why are things named the way they are?"
docs(tutorial): replace stale version callout with Discord tip (#1703) by @lisa-assistant

The callout saying "This tutorial assumes you are using version 0.1.0 or greater of CedarJS" was outdated — CedarJS is now on 3.x.

Rather than just removing it, this replaces it with a tip pointing readers to the CedarJS Discord if they get stuck — which is genuinely useful in a tutorial context and still demonstrates the callout concept.

docs(gqlorm): Initial pass at docs (#1749) by @Tobbe

Two docs alternatives. Which one is better?

docs(forward): Fix Discord invite link (#1708) by @Tobbe

Proper invite link

docs(tutorial): What is CedarJS updates (#1706) by @Tobbe

Follow-up to #1704, addressing a few more outdated sections

docs(tutorial): fix outdated content in 'What is CedarJS?' (#1704) by @lisa-assistant

Several pieces of the "What is CedarJS?" page were inherited from RedwoodJS and are no longer accurate.

Changes

  • Tech list: VitestJest (default Cedar apps use Jest; only the ESM templates use Vitest)
  • Storybook command: yarn redwood storybookyarn cedar storybook
  • Build tooling: "compiled by Babel" → "compiled by esbuild" for the api side; section heading updated accordingly
  • Coming Soon: removed broken redwoodjs.com/roadmap link and "Bighorn Epoch" reference (a RedwoodJS concept); replaced with a link to github.com/cedarjs/cedar#roadmap
  • Backing: clarified that CedarJS is a fork of RedwoodJS — it was not created by Tom Preston-Werner
  • Community: removed "very active" claim, removed Discourse forum link (RedwoodJS resource), kept Discord link only
docs(plans): DB module (#1700) by @Tobbe

Two competing/complimentary plans on how to support moving the db code into its own package

One plan on how to improve gqlorm to not assume a db path, and also not depend on a cedar.toml setting for the db path

docs(tutorial): fix chapter 2 — CLI names, callouts, and factual errors (#1713) by @lisa-assistant

Summary

Fixes CedarJS tutorial chapter 2 (Getting Dynamic, Cells, Routing Params) inherited from RedwoodJS:

  • CLI commands: Replace all yarn rw and yarn redwood with yarn cedar
  • Remove rw alias tip: CedarJS has no rw alias — remove the tip block in getting-dynamic.md that introduced it
  • Broken callouts: Fix all titled admonitions from :::info Title (space syntax) to :::info[Title] (bracket syntax) required by MDX 3 / Docusaurus 3
  • SDL file extension: posts.sdl.{jsx,ts}posts.sdl.{js,ts} (SDL files are .js/.ts, not .jsx)
  • Example URLs: redwoodblog.comcedarblog.com

Files changed

  • docs/docs/tutorial/chapter2/getting-dynamic.md
  • docs/docs/tutorial/chapter2/cells.md
  • docs/docs/tutorial/chapter2/routing-params.md

Test plan

  • Verify all callouts render with titles on the live docs site
  • Verify no yarn rw or yarn redwood references remain in chapter 2

🤖 Generated with Claude Code

docs(versioning): Fix v4.0 docs (#1707) by @Tobbe image
docs(gqlorm): First revision (#1759) by @Tobbe

Updating the docs as I read them through now that they're published to https://cedarjs.com/docs/canary/graphql/gqlorm/

📦 Dependencies

Click to see all 15 dependency updates

🧹 Chore

Click to see all chore contributions
  • chore(test-project): Clean up old fixtures (f2b27bc) by @Tobbe
  • Version docs to 4.2 (6229e11) by @Tobbe
  • chore(yarn): Fix build after yarn 4.14.1 upgrade (#1721) by @Tobbe
  • chore(test-projects): Bump postcss to ^8.5.13 (#1701) by @Tobbe
  • chore(cfw): remove legacy RWFW_PATH and RW_PATH env var fallbacks (#1696) by @lisa-assistant
  • chore(vite): replace vite-node with Vite's native RunnableDevEnvironment (#1717) by @lisa-assistant
  • chore(build): Use nx run-many instead of lerna run (#1738) by @Tobbe
  • chore(cca): Remove unwanted yarn.lock files (#1722) by @Tobbe
  • chore(ci): Update flaky windows CI analysis doc (root cause found) (#1763) by @Tobbe
  • chore(release): Fix canary release script (#1743) by @Tobbe
  • chore(ci): Cache prebuild entries (#1742) by @Tobbe
  • chore(ud): Add e2e tests (#1726) by @Tobbe
  • chore(ci): Work on improving flaky Windows playwright smoke tests (#1756) by @Tobbe
  • chore(smoke-tests): Auth tests cleanup/fixes (#1741) by @Tobbe
  • chore(docs): Move UD phase 5 handoff (#1719) by @Tobbe
  • chore(ci): Fix Windows cache race condition (#1724) by @Tobbe
  • chore(publish): Remove lerna (#1739) by @Tobbe
  • chore(ci): fix create-cedar-rsc-app yarn install on Windows (#1745) by @Tobbe
  • chore(ci): Allow postcss changes in fixtures (#1714) by @Tobbe
  • chore(ci): Cache prebuild entries - check if path exists (#1744) by @Tobbe
  • chore(smoke-tests): Merge auth smoke tests with dev (#1740) by @Tobbe
  • chore(local-testing-project): Update to Cedar v4.1.0 (#1757) by @Tobbe
  • chore(lefthook): proseWrap new md files (#1754) by @Tobbe
  • chore(ci): Explain usage of pull_request_target (#1760) by @Tobbe
  • chore(api-server): warn when deprecated RWJS_DELAY_RESTART env var is used (#1764) by @lisa-assistant