* fix(proxy): match Next.js file conventions
* test(proxy): keep convention fixture out of workspace
* test(proxy): provide middleware path in handler fixture
* refactor(utils): dedupe shared helpers and lint redefinitions
Shared helper logic was being reimplemented across generated entries, runtime modules, and shims. That made small utility semantics easy to drift and gave agents no mechanical feedback when they hand-rolled copies.
Add a local Oxlint rule that discovers exported shared helpers and reports local redefinitions, including helpers embedded in generated source strings. Centralize the existing low-risk duplicates through shared utility modules.
* fix(lint): harden shared utility rule validation
* test(lint): avoid matching oxlint success summary
* fix(lint): make shared utility rule cwd independent
* fix(lint): address bonk review comments
- Drop redundant "default" export conditions in package.json; the
generated worker template resolves these subpaths via "import" like
its siblings (request-pipeline, pages-i18n, config-matchers).
- Mask backtick template literals in prefer-shared-utils so an
apostrophe or comment sequence inside generated template source can
no longer open string/comment masking and swallow a subsequent
helper definition (false negative). Template bodies stay scannable.
---------
Co-authored-by: James <james@eli.cx>
Vitest integration CI shards by test file, so the 340-test App Router integration file becomes one oversized scheduling unit. That makes the slowest shard depend on one monolithic file even after adding native shards.
Split the App Router integration coverage into focused files and increase the integration matrix to four native Vitest shards. This keeps fileParallelism disabled for shared-fixture safety while giving CI smaller scheduling units to distribute.
Validation: vp check tests/app-router-*.test.ts vite.config.ts .github/workflows/ci.yml; vp test run --project integration tests/app-router-client-preloading.test.ts tests/app-router-dev-server.test.ts tests/app-router-external-rewrite.test.ts tests/app-router-font-google-prod.test.ts tests/app-router-isr-codegen.test.ts tests/app-router-malformed-url.test.ts tests/app-router-metadata-routes.test.ts tests/app-router-middleware-next-request.test.ts tests/app-router-next-config-codegen.test.ts tests/app-router-next-config-dev.test.ts tests/app-router-origin-check.test.ts tests/app-router-production-build.test.ts tests/app-router-production-server.test.ts tests/app-router-rsc-flight-hint.test.ts tests/app-router-rsc-plugin.test.ts tests/app-router-static-export.test.ts tests/app-router-worker-entry.test.ts
* refactor(cache): extract Cloudflare cache adapters into @vinext/cloudflare
Move the Cloudflare KV data cache and edge CDN cache adapters out of
vinext into a new publishable @vinext/cloudflare package:
- cache/kv-data-adapter(.runtime).ts (KVCacheHandler, kvDataAdapter)
- cache/cdn-adapter(.runtime).ts (CloudflareCdnCacheAdapter, cdnAdapter)
tpr.ts stays in vinext. vinext now depends on @vinext/cloudflare
(workspace:*) and the package declares vinext as a peer dep; both build
from source via tsconfig paths so there is no build-order cycle. The
vinext/cloudflare barrel still re-exports KVCacheHandler for back-compat.
Wires up tsconfig paths, a vitest source alias, root build/postinstall,
and the preview/publish workflows for the new package. Updates internal
consumers (apps/web, examples/workers-cache), docs, and tests.
* ci(create-next-app): install @vinext/cloudflare from local tarball
vinext now depends on @vinext/cloudflare, which isn't published to npm
yet. The create-next-app smoke test packs vinext locally and resolves
its deps from the registry, so the install (and dev server) failed with
ERR_PNPM_FETCH_404 for @vinext/cloudflare.
Pack @vinext/cloudflare alongside vinext and add a pnpm override in the
scaffolded project pointing at the local tarball so the dependency
resolves offline.
* refactor(cloudflare): address review feedback
- Remove the root barrel export from @vinext/cloudflare; expose only the
./cache/* subpaths via a wildcard export (no root main/types).
- vinext/cloudflare re-exports KVCacheHandler from the full subpath.
- Drop the redundant .npmignore (the package.json "files" allowlist
already restricts the publish to dist).
- Remove the unsupported imperative setCacheHandler/KVCacheHandler usage
from both READMEs; the cache plugin config is the supported approach.
- Simplify test wiring: drop the now-unused @vinext/cloudflare tsconfig
path and dedupe the vitest source alias into a shared constant.
* chore(cloudflare): drop unused vite devDependency
The @vinext/cloudflare config uses vite-plus and nothing imports vite, so
the vite devDependency was unused. build/check/knip stay green without it.
* Apply suggestion from @james-elicx
* feat(release): commit-driven changesets with auto-generated changesets
Adopt stock Changesets for versioning/changelog/publishing, with the only
bespoke surface being a script that creates changesets automatically from
Conventional Commits. Auto-generated changeset files never live on `main` —
they are written to the CI working tree, consumed by changesets/action into a
rolling Version PR, and discarded. Manually authored changesets still work.
- scripts/create-changeset.mjs: derive per-package bumps from Conventional
Commits (paths -> package, type -> bump). Includes the version-vs-tag guard
so a merged Version PR publishes instead of re-opening a PR.
- scripts/version.mjs: `changeset version` + append a `## Contributors` list to
each bumped package's CHANGELOG.md (idempotent, pure rewrite unit-tested).
- .github/workflows/release-pr.yml: version-only changesets/action (no publish).
- .github/workflows/publish.yml: guarded OIDC publish (preserves
`vp pm publish --provenance`); version now comes from `changeset version`.
- Reconcile packages/vinext version (0.0.5 -> 0.0.55) so the guard is coherent.
Unit tests for both scripts pass (35). The release orchestration (Version PR
creation, OIDC publish, gh contributor resolution) can only be validated in a
live CI run.
* refactor(release): collapse to a single changesets workflow
Let changesets/action own as much as possible. Delete the separate publish.yml
(guard job, manual bump, manual OIDC/tag/release/notify steps) and the
release-pr.yml split. One workflow now:
- create-changeset.mjs writes auto changesets to the working tree (its
version-vs-tag guard yields nothing right after a Version PR merges).
- changesets/action maintains the Version PR and, when no changesets remain,
publishes via `changeset publish` with OIDC trusted publishing + provenance,
and creates the git tag + GitHub Release.
Removes the unused release:version script. Contributors list still handled by
scripts/version.mjs as the action's version command.
* refactor(release): convert release scripts to .mts, drop .d.mts files
Replace the .mjs + hand-written .d.mts declaration pairs with real TypeScript
(.mts) source. Node >=24 (the setup default) runs .mts directly via native type
stripping; .mts is unambiguously ESM so it needs no "type": "module" and emits
no MODULE_TYPELESS warning. The cross-import uses an explicit .mts specifier
(Node requires it), permitted in tsc via allowImportingTsExtensions (safe: the
project is noEmit).
- scripts/create-changeset.{mjs,d.mts} -> scripts/create-changeset.mts
- scripts/version.{mjs,d.mts} -> scripts/version.mts
- tsconfig: allowImportingTsExtensions
- release.yml: run node scripts/*.mts
vp check (format + lint + types) clean; 35/35 unit tests pass.
* refactor(release): trim release scripts (~690→~470 source lines)
Cut comment bloat, remove dead code, tighten without dropping behavior:
- Condense verbose JSDoc/@param blocks to one-line purpose comments; keep the
load-bearing "why" (correctness-rule header, insertContributors idempotency).
- Delete unused `newestChangelogVersion` (+ its tests) — exported/tested but
never called.
- Tighten run()/insertContributors/latestTagVersion without behavior change.
Multi-package machinery and the bottom `## Contributors` list are retained per
requirements. vp check clean; 33 unit tests pass.
* feat(release): grouped conventional changelog + filter bot contributors
The default changesets changelog groups by bump level (### Minor Changes) and
renders our changeset summary as a nested bullet dump. Replace it with a real
conventional-commits changelog: version.mts now regroups each release's commits
into ### Features / ### Bug Fixes / ### Performance sections (scope bolded, type
prefix dropped) and rewrites the newest CHANGELOG section, then appends the
## Contributors list with [bot] accounts filtered out.
- create-changeset.mts: extract conventionalParts + collectReleaseCommits +
releaseRangeStart and export the commit-walk so version.mts reuses it (no
duplicate git logic).
- version.mts: groupedChangelogBody + rewriteReleaseSection (idempotent: only
`## <digit>` is a section boundary); dedupeSortLogins drops `[bot]` logins.
- Tests updated for the new pure builders.
vp check clean; 33 unit tests pass. Verified end-to-end with a local dry run.
* refactor(release): simplify per PR review
- discoverPublishablePackages: scan only packages/* (publishable packages don't
live elsewhere), drop the apps/examples/benchmarks scan.
- Delete isReleaseCommit: the "chore: version packages" release commit is a
`chore`, already excluded by parseBumpFromSubject returning null. No need to
hardcode release-message formats.
vp check clean; 31 unit tests pass.
* docs(release): clarify why the version-vs-tag guard exists
Per PR review: the guard was confusing. Document inline that it only suppresses
the post-merge/pre-publish window (version bumped, tag not yet created), which
would otherwise re-open a Version PR instead of publishing. Keeping the
never-on-main design per review decision.
* feat(release): sub-group changelog areas and humanize area names
Within each type section (### Features / ### Bug Fixes / …), areas (commit
scopes) with more than 3 items now get their own `#### <Area>` sub-group with
the scope prefix dropped; smaller areas and scopeless commits fall under
`#### Other`. When no area qualifies the list stays flat. Area names are
humanized (app-router → "App Router", css → "CSS", ppr → "PPR", i18n → "i18n")
in headings and in the bold prefixes.
Also: groupedChangelogBody now renders only known release types (GROUPS) — it
no longer leaks non-release types into an "Other Changes" bucket.
vp check clean; 32 unit tests pass. Verified with a local dry run.
* feat(release): rename changelog Other sub-group to Misc
* feat(release): sub-group areas with 3+ items (was >3)
* fix(release): address PR review (stale refs, contributor API, doc)
- Fix stale references to deleted files: .mjs → .mts and release-pr.yml →
release.yml in .changeset/README.md and the release.yml header comment.
- resolveContributors: one paginated `gh api compare` call instead of one
`gh api commits/<sha>` per commit (N round-trips → 1). This also removes
version.mts's local git() helper, so no more `fatal:` probe noise from it.
No behavior change to the changelog/contributor output (verified via dry run:
same 4 contributors resolved). vp check clean; 32 unit tests pass.
The bot's "no git tags" bootstrap concern does not apply: tags v0.0.10..v0.0.55
exist and CI checks out with fetch-depth: 0, so the guard resolves 0.0.55.
* fix(release): run pinned changeset CLI; drop dead affectedPackages branch
Per second bonk review:
- Invoke the pinned, installed @changesets/cli via `vp exec changeset` instead
of `vp dlx @changesets/cli` (which fetches a floating latest at run time) — in
release.yml publish and version.mts. Removes the reproducibility gap vs the
lockfile-pinned 2.31.0.
- affectedPackages: remove the unreachable `dir === "."` / empty-prefix branch
now that discovery only returns `packages/*` keys.
vp check clean; 32 unit tests pass.
* fix(release): only emit valid GitHub logins in Contributors; doc fix
Per third bonk review:
- resolveContributors: `.author.login // empty` (was `// .commit.author.name`),
so commits with an unlinked email contribute no entry instead of a raw git
display name. dedupeSortLogins now keeps only `[a-zA-Z0-9-]+` shapes, which
also subsumes the previous `[bot]` filter — no more broken `- @Full Name`
mentions.
- .changeset/README.md: suggest `vp exec changeset` instead of the floating
`vp dlx @changesets/cli` for manual changeset authoring.
vp check clean; 32 unit tests pass; dry run resolves the same valid handles.
* test(msw): introduce MSW infrastructure and migrate font-google-build
Replaces ad-hoc `globalThis.fetch` hijacking in tests with Mock Service
Worker. MSW intercepts both `globalThis.fetch` and `node:http`/`https`,
so it covers fetches issued by in-process Vite servers and fixture pages
without each test having to manage its own stub.
The setup file enables `onUnhandledRequest: 'error'` so any future test
that issues an unmocked external request fails loudly instead of
silently hitting the network. Loopback hostnames (localhost, 127.0.0.1,
::1) are bypassed because the integration project deliberately hits
in-process test servers via fetch.
What this PR contains:
- `tests/_msw/{handlers,server,setup}.ts` — shared infra
- `setupFiles` wired into both vitest projects (unit + integration)
- `msw` + `@mswjs/interceptors` added to the workspace catalog
- `msw` postinstall skipped via `allowBuilds` (it only copies a browser
service worker; we exclusively use `msw/node`)
- `tests/font-google-build.test.ts` migrated as the proof of pattern
Temporary handlers for `httpbin.org/uuid` and
`example.com/not-cacheable` keep the existing fixture pages
(`revalidate-tag-test`, `dynamic-error-fetch`) working under the new
guard. These are placeholders flagged with `TODO(msw-migration)` — a
follow-up will replace those live URLs with test-local endpoints.
This is PR 1 of 3:
- PR 2 will migrate `tests/fetch-cache.test.ts`
- PR 3 will migrate `tests/font-google.test.ts` and the font block in
`tests/app-router.test.ts`
* test(msw): restrict MSW to the unit project + use passthrough for loopback
Two fixes to the initial MSW wiring that surfaced when CI ran the full
suite:
1. The integration project is now excluded from MSW. Integration tests
spin up in-process HTTP servers and fixture dev servers and exercise
them via `fetch("http://127.0.0.1:<port>/...")`. Even with a loopback
`passthrough()` handler installed, the @mswjs/interceptors layer
interferes with that traffic in subtle ways:
- `tests/prerender.test.ts > errors without writing .rsc when the
middleware short-circuit fallback RSC request fails` timed out at
30s — the 5xx response body never reached the caller.
- `tests/ecosystem.test.ts > nuqs` hung in `beforeAll` waiting for
its fixture process to come up, because the readiness fetch
stalled inside the interceptor.
Integration tests already talk to real local servers, not the
internet, so the unhandled-request guard buys little there. The
value of MSW for this repo is mocking external HTTP for unit tests
of fetch wrappers (font-google, font-google-build, fetch-cache),
which is preserved.
2. The `onUnhandledRequest` callback in `setup.ts` that tried to
silently let loopback through wasn't sufficient — MSW's interceptor
still ran for those requests and could stall them. Replaced with a
default `http.all(LOOPBACK_URL_PATTERN, () => passthrough())`
handler in `handlers.ts` and a plain `onUnhandledRequest: "error"`
in `setup.ts`. Unit tests like the middleware-rewrite proxy in
`tests/shims.test.ts` and the JSX-in-JS suite need this to keep
their in-process HTTP servers reachable.
* test(msw): address BigBonk review notes
- Update PR description in the commit body to drop the stale mention of
TODO(msw-migration) handlers — they were removed when the integration
project was excluded from MSW; the fixture pages run there.
- Tighten the loopback IPv4 octet regex from `\d+` to `\d{1,3}` so it
doesn't match absurdly long octet strings. `URL.hostname` normalises
real addresses, so this is purely a readability fix.
* test(msw): address remaining BigBonk notes from the initial review
- Add `0.0.0.0` to the loopback passthrough pattern so test servers that
bind to the unspecified address keep working without per-test mocks.
- Add a `TODO(msw-3)` comment in `server.ts` flagging the
`SetupServerApi` deprecation in MSW 2.14.6 — the `FetchInterceptor`-only
construction will need to migrate to `defineNetwork`'s interceptor
config when we bump to MSW 3.
Both raised by BigBonk on the first review of this PR (the second review
focused on the stale description and the loose `\d+` octet bound, which
were addressed in the previous commit).
* test(msw): scope server.use() inside the try block in font-google-build
Minor structural fix from BigBonk's review: moves the `server.use()`
handler registration inside the `try` block so all setup and the
matching `fs.unlink` cleanup share the same try/finally scope.
No behaviour change — `server.use()` is synchronous and infallible, so
this is purely a structural tidy-up.
* test(msw): correct comment in font-google-build to match interceptor config
BigBonk caught that the comment described the default `setupServer()`
behaviour (which uses both `FetchInterceptor` and `ClientRequestInterceptor`),
but this project's `server.ts` deliberately constructs `SetupServerApi`
with only `FetchInterceptor`. Update the comment to reflect that — it
intercepts `globalThis.fetch`, not `node:http`/`node:https`.
No code change, comment only.
* chore(lint): enable stricter promise and error rules
The lint config did not enforce several promise, exhaustive switch, template expression, self-import, and error-construction checks on source files. That let void callback promise boundaries and empty Error fixtures slip through review.
Enable the rules for vinext source files, apply the useful test subset, and make the existing async callback boundaries explicit. Also remove the redundant unknown union and close response bodies in the affected Pages Router tests so server teardown remains deterministic.
* chore(lint): require exhaustive test switches
Test files already enforce strict async callback and error construction checks, but exhaustive switch checking was source-only.
Enable switch exhaustiveness for tests and make the Pages Router response decoder handle the undefined encoding case explicitly. Tighten the allowedDevOrigins suite cleanup so the temporary dev server closes deterministically.
* fix: replace relative shim imports with bare specifier vinext/shims/X (#1001)
Internally, server modules imported their own 'use client' shims via
relative paths (e.g. from "../shims/slot.js"). @vitejs/plugin-rsc's
packageSources tracking only fires for bare specifier imports, so
relative shim imports took a broken fallback path generating absolute
filesystem paths that fail against the package's exports field.
Changes:
- 67 relative shim imports across 34 source files rewritten to
"vinext/shims/<name>"
- Added .tsconfig paths mapping so TypeScript resolves shim types to
source (avoids type mismatch between dist and source module graphs)
- Added vitest project-level resolve aliases for vinext/shims -> source
(prevents separate module graph instances in unit tests)
- Added package.json exports for ./dist/shims/*.js as a safety net
- Added regression test (tests/shim-imports.test.ts) that asserts no
relative shim imports exist in source
* fix: address review feedback for relative shim imports (#1006)
- Convert missed side-effect import in dev-server.ts to vinext/shims/router-state
- Extend regression test regex to catch bare side-effect import statements
- Remove redundant ./dist/shims/*.js exports from package.json
* replace test with oxlint plugin
---------
Co-authored-by: James <james@eli.cx>
* chore: bump pnpm to 10.32.1 and enable vitest agent reporter
* chore: upgrade GitHub Actions to Node 24 compatible versions
* perf: re-land unit/integration split and add CI sharding
- Restore unit/integration vitest project split (lost in vp migration)
- vitest 4 supports fileParallelism per-project (was global-only in v3),
so unit tests now run in parallel (~11s) while integration stays serial
- Add 3-shard matrix for integration tests in CI, cutting wall time from
~4.5min to ~1.5min
- Move kv-cache-handler to integration to avoid flakiness under parallelism
* chore: remove unused deploy-preview slash command workflow
* ci: add sentinel job for stable branch protection rule
* chore: migrate to vite plus
* Disable typeAware and typeCheck
* Update CI
* Fix CI
* Fix test
* Clean
* Run test with vp
* Try revert
* react: false In test
* Fix test
* Revert "Try revert"
This reverts commit 009da10473.
* Update
* Update
* Try revert ci changes
* revert
* Run vp migrate
* Disable typeAware and typeCheck for now
* Better resolve for test
* Use vp dev instead of vite
* Update expect
* Fix NormalizeManifestModuleId
* Try increase timeout
* Update to use vp
* Try new check
* Bring back npx vp
* Migrate CI
* Make next-intl resolvable
* Update
* Update
* Update