* fix: pass NextFetchEvent as second argument to middleware
All 3 middleware invocation sites were calling `middlewareFn(nextRequest)`
with only one argument. The `NextMiddleware` type expects `(request, event)`
but `NextFetchEvent` was never instantiated or passed. Any middleware using
`event.waitUntil()` would throw at runtime because `event` is `undefined`.
Fix: instantiate `NextFetchEvent({ page })` at each call site, pass it as
the second argument, and drain `_waitUntilPromises` via
`Promise.allSettled()` (fire-and-forget) after middleware returns.
Call sites fixed:
- `packages/vinext/src/server/middleware.ts` (shared runner, used by prod)
- `packages/vinext/src/index.ts` (Pages Router dev codegen)
- `packages/vinext/src/server/app-dev-server.ts` (App Router dev codegen)
* address review: add drainWaitUntil(), wire ctx.waitUntil() in Workers entry, fix async test
* fix deploy tests: update assertions for ctx-aware fetch and runMiddleware signatures
* address review: fix misleading comment, update JSDoc, use queueMicrotask in test
* pass undefined explicitly to runMiddleware in prod-server (no ctx on Node.js)
---------
Co-authored-by: James <james@eli.cx>
* fix: warn when existing vite.config.ts is missing cloudflare plugin on deploy
Closes#312.
- Add `viteConfigHasCloudflarePlugin()` to deploy.ts: when `vinext deploy`
finds an existing vite.config.ts that doesn't import @cloudflare/vite-plugin,
it now prints an actionable warning with the exact config fix rather than
silently proceeding to a broken build.
- Add README prerequisites section to the Cloudflare Workers deploy docs:
covers wrangler login vs CLOUDFLARE_API_TOKEN (with required permissions)
and how to supply account_id.
- Add 7 unit tests for `viteConfigHasCloudflarePlugin()`.
* fix: pages router warning shows cloudflare() not cloudflare(())
* refactor: extract applyMiddlewareRequestHeaders into config-matchers
Move the x-middleware-request-* unpacking + postMwReqCtx rebuild logic
out of prod-server.ts and deploy.ts and into a shared
applyMiddlewareRequestHeaders() in config-matchers.ts, eliminating ~30
lines of duplicated code from each callsite.
* fix(test): update deploy test to reflect applyMiddlewareRequestHeaders refactor
* fix(deploy): monorepo-aware lock file and node_modules resolution
vinext deploy failed in monorepos in two ways:
1. Wrong package manager detected — detectPackageManager only checked for
bun.lockb (legacy binary format), missing bun.lock (text format, Bun
v1.0+). It also only searched the immediate project directory, so any
monorepo where the lock file lives at the workspace root fell through to
npm, causing a confusing "npm error Invalid Version:" failure.
2. Dependency detection and wrangler binary path hard-coded to the app
directory — hasCloudflarePlugin, hasRscPlugin, hasWrangler, hasMdxRollup
all checked {root}/node_modules only, producing false negatives for
hoisted packages. runWranglerDeploy then crashed with ENOENT because
the wrangler binary was at the workspace root, not the app root.
Changes:
- Add walkUpUntil<T>(start, check) to utils/project.ts — a shared primitive
that walks ancestor directories until check() returns non-null. Both the
lock-file and node_modules walkers are built on top of this.
- Rewrite detectPackageManager / detectPackageManagerName using walkUpUntil;
add bun.lock (text) check alongside existing bun.lockb (binary).
- Add findInNodeModules(start, subPath) using walkUpUntil — walks up to find
node_modules/{subPath}, returning the absolute path or null.
- Use findInNodeModules in detectProject (hasCloudflarePlugin, hasRscPlugin,
hasWrangler), getMissingDeps (hasMdxRollup), and runWranglerDeploy.
Tests (12 new):
- detectPackageManager: pnpm, yarn, bun.lock, bun.lockb, npm fallback,
monorepo root traversal, closest-wins precedence
- findInNodeModules: package lookup, binary lookup, null fallback,
monorepo root traversal, closest-wins precedence
* fix(test): clear npm_config_user_agent in npm fallback test
On CI, pnpm sets process.env.npm_config_user_agent which causes
detectPackageManagerName to return 'pnpm' instead of falling through
to the npm default. Save and restore the env var around the assertion.
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* fix: preserve multiple Set-Cookie headers in prod-server and worker entry
The response header merging in prod-server.ts and the generated Cloudflare
worker entry used Record<string, string> which flattened multiple Set-Cookie
headers — the last value won, or cookies with Expires dates got corrupted
by comma-joining.
Extract mergeResponseHeaders() helper that uses getSetCookie() to preserve
array-valued Set-Cookie headers. Apply the same fix to the worker entry
template in deploy.ts using the Headers API.
Fixes#295
* fix: address review feedback on Set-Cookie header preservation
- Add `as string` cast in deploy.ts worker entry for x-middleware-request-*
header unpacking (matches prod-server.ts)
- Normalize Vary header with Array.isArray check in sendCompressed to handle
the widened type correctly
- Add edge-case test for middleware cookie as plain string (not array)
* fix: apply Set-Cookie fix to example workers, remove redundant toLowerCase
- Fix the same Set-Cookie flattening bug in hand-written example worker
entries (pages-router-cloudflare, realworld-api-rest)
- Remove 4 redundant .toLowerCase() calls — Headers.forEach() always
yields lowercase keys
* fix: add missing as string cast in deploy.ts middleware header collection
* refactor: extract mergeHeaders to shared vinext/server/worker-utils
The mergeHeaders function was duplicated in both example worker entries
(pages-router-cloudflare, realworld-api-rest) and the deploy.ts template.
Extract it to a shared module that example workers import. The deploy.ts
template still inlines it (code generation constraint).
Reduces 3 copies to 2 (shared module + generated template).
* fix: array-accumulate Set-Cookie in config headers, fix JSDoc, add behavioral test
- deploy.ts template: config headers section was comma-joining Set-Cookie values
using += which corrupts cookies with Expires dates and loses all but the last
cookie when multiple config rules match. Matches the array-accumulation pattern
already used in prod-server.ts (lines 899-907) and the middleware section above.
- worker-utils.ts + deploy.ts template JSDoc: 'Response headers take precedence'
was misleading — Set-Cookie is additive, not overriding. Clarify both docs.
- tests/deploy.test.ts: add behavioral test for mergeHeaders via worker-utils import
(same function inlined in the generated template), replacing reliance on
string-contains assertions alone.
* fix: indentation in deploy.ts template, array-accumulate Set-Cookie in example workers
- deploy.ts: fix extra leading space in config headers block (lines 636-659)
and JSDoc lines 740-743 introduced in previous commit
- pages-router-cloudflare, realworld-api-rest: config headers section was
doing a plain assignment (middlewareHeaders[lk] = h.value) which overwrites
array-valued Set-Cookie built up by the middleware section above; use the
same array-accumulation pattern as prod-server.ts and the deploy.ts template
* fix: remove as any casts in prod-server.ts, add Vary handling to example workers
- prod-server.ts lines 904/906: middlewareHeaders is now Record<string, string | string[]>
so the as any casts are unnecessary; replace with as string (consistent with the
middleware collection section above at line 848)
- pages-router-cloudflare, realworld-api-rest: add Vary comma-joining to config
headers section to match prod-server.ts (line 908) and deploy.ts template (line 653)
---------
Co-authored-by: James <james@eli.cx>
* fix: lowercase package name before sanitizing Worker name
* test: assert exact sanitized project name value
Strengthen the sanitization test to assert the exact expected value
('my-app-v2') instead of just matching a regex pattern. The regex
assertion passed even when the bug was present.
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* fix: preserve middleware request override headers in runMiddleware
* Strip x-middleware-* headers, detect bun, update fixtures
Preserve and unpack x-middleware-request-* headers while removing any other x-middleware-* headers in the worker entry (defense-in-depth). Add bun package manager detection in project utils (detectPackageManager and detectPackageManagerName). Update tests to assert the new header-stripping behavior. Update pages-basic fixture entry: add explicit void for promise calls, inject a middleware header override route, add dangerous-scheme blocking for Link hrefs, include credentials on prefetch, update dynamic import asset names and file metadata, and rename several getServerSideProps helper identifiers to avoid collisions.
* Normalize absolute file paths in fixture
Update fileName annotations in tests/fixtures/pages-basic/dist/server/entry.js to replace local developer paths (/Users/sunilpai/...) with CI runner paths (/home/runner/work/...). This cleans up generated JSXDEV metadata in the compiled test fixture to avoid leaking local machine paths and make outputs reproducible.
---------
Co-authored-by: Sunil Pai <spai@cloudflare.com>
* Skip deploy previews for fork PRs that lack Cloudflare secrets
Fork PRs don't have access to repository secrets (CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ACCOUNT_ID), so deploy/smoke-test/comment steps always fail.
Add a fork detection condition to skip these steps gracefully. The build
steps still run, so example builds are still validated for fork PRs.
* fix: align Pages Router worker entry with prod-server request handling
The generated Cloudflare Worker entry for Pages Router apps was missing
several request handling steps that the Node.js production server
(prod-server.ts) already handled. This brings the two in sync:
- Run middleware (runMiddleware) before routing
- Apply next.config.js redirects, rewrites (before/after/fallback), and headers
- Handle basePath stripping and trailing slash normalization
- Merge middleware response headers with correct precedence
- Guard renderPage with typeof check
Also updates the two Pages Router examples (pages-router-cloudflare,
realworld-api-rest) and exports vinext/config/config-matchers so the
worker entry can import the shared matching utilities.
* chore: sync pnpm-lock.yaml with upstream package.json changes
* fix: install react-server-dom-webpack during init and deploy for App Router
vinext init was missing react-server-dom-webpack from its dependency list,
causing 'Could not resolve react-server-dom-webpack/server.edge' errors on
fresh create-next-app projects. Additionally, react-server-dom-webpack@19.2.4
requires react@^19.2.4, but create-next-app ships react@19.2.3 -- the version
mismatch caused npm to nest rsdw inside vinext/node_modules where Vite can't
resolve it.
Fix: add react-server-dom-webpack to getInitDeps for App Router, detect when
the installed React version is too old, and upgrade react/react-dom as regular
dependencies before installing dev deps (avoiding ERESOLVE peer-dep conflicts
and keeping react in 'dependencies' rather than 'devDependencies').
Also applies the same fix to vinext deploy for parity, and adds a CI job that
scaffolds a real create-next-app project, runs vinext init, and verifies the
dev server returns HTTP 200.
Closes#22
* refactor: use createRequire for package resolution instead of direct node_modules reads
Address review feedback (PR #18 pattern): replace fs.existsSync(node_modules/...)
with createRequire-based resolution, which handles hoisting, pnpm symlinks,
monorepos, and Yarn PnP correctly.
- deploy.ts: extract isPackageResolvable() helper using createRequire, use it
in getMissingDeps for react-server-dom-webpack detection
- init.ts: refactor getReactUpgradeDeps() to use createRequire + findPackageVersion
walk-up pattern instead of reading node_modules/react/package.json directly
- tests: add isPackageResolvable unit tests, use dependency injection for
getMissingDeps rsdw test (Vitest's createRequire intercepts resolution)
* ci: add Windows to create-next-app smoke test
Run the create-next-app compatibility test on both ubuntu-latest and
windows-latest. Replace bash-specific server health check (curl, kill,
backgrounding) with a cross-platform Node script using child_process
and http. Use runner.temp instead of /tmp for portable paths.
* fix: use pathToFileURL for dynamic import() calls on Windows
On Windows, Node's ESM loader requires file:// URLs for absolute paths.
Bare paths like D:\... are rejected with ERR_UNSUPPORTED_ESM_URL_SCHEME.
Wrap all dynamic import() calls that receive absolute paths from
require.resolve() or path.join() with pathToFileURL().href:
- index.ts: RSC plugin import, RSC transforms import, PostCSS config
loading, PostCSS plugin resolution (5 call sites)
- cli.ts: Vite module import from project root
* fix: pathToFileURL for cache-runtime codegen and test imports
Additional Windows fixes found via PR #43 review:
- index.ts: use pathToFileURL for runtimeModulePath in 'use cache'
transform code generation (2 locations) — these embed absolute paths
into generated import() statements via JSON.stringify
- pages-router.test.ts: wrap 9 import(serverEntryPath) calls with
pathToFileURL so tests work on Windows
* feat(deploy): add --env support for wrangler deploy
Adds vinext deploy --env <name> and threads it through to wrangler deploy --env <name>. Keeps --preview working as shorthand for preview environment, adds unit tests for wrangler arg construction, and updates README/help examples. Refs #33.
* fix(deploy): use execFileSync and util.parseArgs for --env support
Build on #44 by liuxiaopai-ai — adds --env flag for wrangler deploy.
Security: switch from execSync(shell string) to execFileSync(binary, args)
to eliminate command injection via unsanitized --env/--name values.
Replace hand-rolled flag parsing (parseStringFlag, parseNumericFlag,
rawArgs.includes) with Node.js built-in util.parseArgs. This gives us
strict mode (throws on unknown flags), automatic --flag=value handling,
and proper error messages for missing values.
Also: buildWranglerDeployArgs now returns { args, env } directly instead
of re-parsing the args array for the log message.
---------
Co-authored-by: root <root@localhost.localdomain>
- Install vinext itself as a devDependency during init (#11)
- Remove explicit rsc() from generated configs since vinext auto-registers it (#12)
- Widen react/react-dom peer dep to >=19.2.0 for create-next-app compat (#13)
Co-authored-by: Fred K. Schott <fschott@cloudflare.com>