mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
fefc8e21d1
* fix(app-router): preserve request.cf in route handlers Cloudflare Workers attaches request.cf at the inbound request boundary, but App Router dispatch rebuilt Route Handler requests with the standard Request constructor and discarded that runtime metadata.\n\nUse the existing metadata-preserving URL clone boundary so both Node and Edge Route Handlers retain cf while keeping their current URL normalization semantics. * fix(app-router): preserve request.cf through runtime wrapping Route Handler request tracking can rebuild an already-normalized request when restoring basePath or applying middleware header overrides. Those reconstructions discarded Cloudflare metadata before the final NextRequest reached user code.\n\nPreserve cf at both reconstruction points while retaining the existing body-transfer and RequestInit behavior, with runtime-level coverage for each path. * fix(app-router): treat request.cf as dynamic state Preserving Cloudflare request metadata makes request.cf observable to Route Handlers, but the request proxy did not classify reads as dynamic. Geo-dependent responses could therefore enter the route-wide ISR cache, while static route modes exposed request-specific metadata.\n\nApply the existing ip and geo policy to cf: track reads in automatic mode, hide it under force-static, and reject it under dynamic error mode. * fix(app-router): preserve request.cf across clones Tracked Route Handler requests inherit the standard Request clone implementation, which omits Cloudflare request metadata. A handler that cloned before reading cf therefore lost the metadata even though the original tracked request retained it.\n\nCentralize tracked request cloning so cf is reattached before the clone is recursively wrapped with the same dynamic request policy. * fix(app-router): track reflective request.cf access Tracked Route Handler requests enforced dynamic policy only through the Proxy get trap. Descriptor, membership, and key-enumeration reads could expose Cloudflare metadata without marking the handler dynamic or respecting static modes. Route explicit cf reflection through the same policy, filtering configurable cf keys under force-static and rejecting reflective access under dynamic error mode. * fix(app-router): retain request proxy through valueOf Binding every Request method to the underlying NextRequest let valueOf return the raw target. Subsequent request.cf reads could then bypass dynamic tracking and static-mode policy. Bind valueOf to the proxy receiver while leaving branded Request methods on the underlying target, and cover the escape in all three request modes. * fix(app-router): bind reflection to request proxy Inherited Object reflection helpers were still bound to the raw NextRequest, allowing ownership checks to bypass request.cf dynamic policy. Bind exact Object.prototype methods to the proxy receiver while preserving the branded target for Web Request methods, and cover ownership and enumerability checks in every request mode. * fix(app-router): preserve proxy for request extensions Binding unknown request properties to the raw NextRequest let user-defined methods and getters bypass request.cf policy. Snapshot the runtime's built-in Request surface for branded target access while keeping own and unknown extensions on the proxy receiver. Cover both method and accessor escapes in every request mode. * fix(app-router): hide cf before locking requests Force-static proxies filtered request.cf from reflective operations, but a non-extensible target made that omission violate Proxy invariants. Remove the configurable metadata before preventing extensions, and cover preventExtensions, seal, and freeze while preserving force-static policy. * fix(app-router): bind Worker request members correctly Cloudflare may expose Web IDL Request members as own properties, so classifying every own member as a user extension caused illegal invocation errors in Workers. Use the captured built-in API surface regardless of property placement, while keeping unknown extension names on the proxy receiver. Add coverage for an own branded accessor. * fix(app-router): distinguish request member shadows Name-only branded member detection fixed Workers own-property layouts but treated post-wrap user shadows as runtime Web IDL members, allowing their this-based reads to escape the proxy. Snapshot runtime-owned descriptors before route code receives the request and bind only unchanged implementations to the target. Later shadows retain the proxy receiver in every request mode. * fix(app-router): detect request prototype shadows Own-descriptor snapshots still treated replaced inherited Request members as branded, allowing prototype getters to bypass request.cf policy. Snapshot each resolved built-in implementation and compare the currently resolved descriptor before selecting the raw target. Own and prototype shadows now retain the tracked receiver. * fix(app-router): snapshot request built-ins before routes * fix(app-router): preserve branded Worker request methods * fix(app-router): preserve reflected cf after locking * refactor(app-router): contain request.cf policy on its target Route handlers need Workers metadata to follow static-generation policy without changing the semantics of every Request property. Use the configurable cf accessor copied onto NextRequest as the single policy boundary, and delegate request reconstruction to the canonical clone helpers. * fix(app-router): avoid synthesizing absent request.cf Ordinary requests should not gain an own cf property merely because route-handler dynamic tracking is active. Install the target accessor only when Workers metadata exists, while keeping direct absent reads subject to automatic, force-static, and error policy. * fix(app-router): harden request.cf tracking * chore(test): register worker route fixtures * chore(app-router): deduplicate request.cf descriptor --------- Co-authored-by: James <james@eli.cx>
167 lines
7.0 KiB
TypeScript
167 lines
7.0 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import type { KnipConfig } from "knip";
|
|
|
|
function entriesFromPackageJson(relativePath: string): string[] {
|
|
const pkg = JSON.parse(readFileSync(new URL(relativePath, import.meta.url), "utf8")) as {
|
|
bin?: string | Record<string, string>;
|
|
exports?: Record<string, unknown>;
|
|
};
|
|
const targets = new Set<string>();
|
|
|
|
const visit = (value: unknown) => {
|
|
if (typeof value === "string") targets.add(value);
|
|
else if (value && typeof value === "object") for (const v of Object.values(value)) visit(v);
|
|
};
|
|
|
|
visit(pkg.bin);
|
|
visit(pkg.exports);
|
|
|
|
return [...targets]
|
|
.filter((t) => t.endsWith(".js"))
|
|
.map((t) =>
|
|
t
|
|
.replace(/^\.\//, "")
|
|
.replace(/^dist\//, "src/")
|
|
.replace(/\.js$/, ".{ts,tsx}"),
|
|
);
|
|
}
|
|
|
|
export default {
|
|
workspaces: {
|
|
".": {
|
|
entry: [
|
|
"scripts/*.{js,ts,mjs,mts}",
|
|
"tests/**/*.test.ts",
|
|
"tests/helpers.ts",
|
|
// Filesystem route entries in the standalone Vite/Worker fixture.
|
|
"tests/e2e/cloudflare-workers/fixture/app/**/route.ts",
|
|
],
|
|
project: ["tests/**/*.{js,ts}", "!tests/fixtures/**"],
|
|
},
|
|
"packages/vinext": {
|
|
entry: [
|
|
...entriesFromPackageJson("packages/vinext/package.json"),
|
|
// Build-time entries referenced by path constant (Vite reads them
|
|
// from disk via `fs`), so knip wouldn't otherwise trace them.
|
|
"src/server/app-browser-entry.ts",
|
|
"src/server/app-browser-server-action-client.ts",
|
|
"src/server/app-ssr-entry.ts",
|
|
// Forked as a child process by prerender-server-pool.ts via a path
|
|
// constant (child_process.fork), so knip can't trace it as imported.
|
|
"src/build/prerender-server-entry.ts",
|
|
// Runtime helpers imported by generated virtual entries. The imports
|
|
// are emitted as strings, so knip cannot trace them statically.
|
|
"src/client/react-instance-bootstrap.ts",
|
|
"src/server/app-middleware.ts",
|
|
"src/server/app-page-dispatch.ts",
|
|
"src/server/app-page-head.ts",
|
|
"src/server/app-page-ppr-runtime.ts",
|
|
"src/server/app-prerender-static-params.ts",
|
|
"src/server/app-route-module-loader.ts",
|
|
// Client-side instrumentation bundle: loaded as a side-effect module
|
|
// by the generated hydration entries (import "vinext/instrumentation-client"),
|
|
// so its public surface (clientInstrumentationHooks, getClientInstrumentationHooks)
|
|
// is consumed by the user's app at runtime, not by imports knip can follow.
|
|
"src/client/instrumentation-client.ts",
|
|
"src/client/instrumentation-client-state.ts",
|
|
// Shims for `next/*` internal modules — their exports are consumed by
|
|
// type-only imports in third-party packages' .d.ts files (e.g.
|
|
// @clerk/nextjs, @sentry/nextjs, nextjs-toploader). Those imports
|
|
// originate in node_modules and are invisible to knip.
|
|
"src/shims/internal/api-utils.ts",
|
|
"src/shims/internal/app-router-context.ts",
|
|
"src/shims/internal/utils.ts",
|
|
// Typed WorkUnitStore exports consumed by cache.ts via AsyncLocalStorage
|
|
// generic — knip cannot trace type-only dependencies through ALS.
|
|
"src/shims/internal/work-unit-async-storage.ts",
|
|
// Imported via template string in app-rsc-entry.ts (generated code),
|
|
// so knip cannot trace the import statically.
|
|
"src/server/prerender-work-unit-setup.ts",
|
|
"src/server/app-page-element-builder.ts",
|
|
"src/server/app-hook-warning-suppression.ts",
|
|
"src/server/app-post-middleware-context.ts",
|
|
"src/server/app-request-context.ts",
|
|
"src/server/app-rsc-error-handler.ts",
|
|
"src/server/isr-cache.ts",
|
|
"src/server/rsc-stream-hints.ts",
|
|
// #726-CACHE-01/04 defines the disabled proof boundary before runtime
|
|
// observation recording or cache reuse is wired in later slices.
|
|
"src/server/cache-proof.ts",
|
|
// #726-SKIP layout-safety observation foundation. Consumed by the
|
|
// planner, dispatch wiring, and render in later slices.
|
|
"src/server/app-layout-param-observation.ts",
|
|
// #726-SKIP static layout reuse proof model. Consumed by render in a
|
|
// later slice; standalone planner + helpers here.
|
|
"src/server/skip-cache-proof.ts",
|
|
"src/server/static-layout-client-reuse-proof.ts",
|
|
],
|
|
project: ["src/**/*.{ts,tsx}"],
|
|
},
|
|
"packages/cloudflare": {
|
|
entry: [...entriesFromPackageJson("packages/cloudflare/package.json")],
|
|
project: ["src/**/*.{ts,tsx}"],
|
|
},
|
|
"packages/create-vinext-app": {
|
|
entry: [...entriesFromPackageJson("packages/create-vinext-app/package.json")],
|
|
project: ["src/**/*.{ts,tsx}"],
|
|
ignoreDependencies: [
|
|
// create-vinext-app bundles vinext init helpers into dist. These are
|
|
// imported by the bundled helper modules, not by src/index.ts directly.
|
|
"am-i-vibing",
|
|
"magic-string",
|
|
// Kept as an explicit package-local Vite+ toolchain dependency.
|
|
"vite",
|
|
],
|
|
},
|
|
},
|
|
ignoreWorkspaces: ["examples/**", "tests/fixtures/**", "benchmarks/**"],
|
|
ignoreDependencies: [
|
|
// Imported only by declarations vendored from Next.js. @next/env and
|
|
// sharp are covered by ambient stubs in @vinext/types; server-only is a
|
|
// marker import supplied by compatible runtimes.
|
|
"@next/env",
|
|
"server-only",
|
|
"sharp",
|
|
|
|
// Declared at root package.json but imported from workspace/example code:
|
|
// @mdx-js/react — no direct imports; retained for MDX runtime resolution.
|
|
// @mdx-js/rollup — imported from examples/app-router-playground/vite.config.ts
|
|
// which doesn't declare it locally and relies on root hoisting.
|
|
"@mdx-js/react",
|
|
"@mdx-js/rollup",
|
|
|
|
// probed via require.resolve
|
|
"next-intl",
|
|
|
|
// internal module name, not an actual dependency
|
|
"private-next-instrumentation-client",
|
|
|
|
// Cloudflare Workers runtime virtual module — provides `env` for
|
|
// accessing wrangler bindings. Not an npm package. Knip strips the
|
|
// `cloudflare:workers` specifier down to the bare scheme.
|
|
"cloudflare",
|
|
],
|
|
ignoreBinaries: [
|
|
// workspace's own bin, invoked in CI
|
|
"vinext",
|
|
// system/user-project binaries invoked by runtime scripts
|
|
"ps",
|
|
"taskkill",
|
|
"eslint",
|
|
"gh",
|
|
"jq",
|
|
],
|
|
ignoreFiles: [
|
|
"tests/e2e/app-router/nextjs-compat/playwright.nextjs-compat.config.ts",
|
|
"tests/e2e/app-front-redirect-issue/fixture/**/*.{js,ts,tsx}",
|
|
// stub module loaded via `path.resolve()` as a Vite alias target
|
|
"packages/vinext/src/client/empty-module.ts",
|
|
],
|
|
// Catalog check is noisy here (deps consumed from the workspace's own
|
|
// sub-packages or `examples/**` which we intentionally ignore).
|
|
// `duplicates` flags intentional compat aliases — see
|
|
// packages/vinext/src/shims/internal/work-unit-async-storage.ts where
|
|
// `requestAsyncStorage` is a legacy alias for `workUnitAsyncStorage`.
|
|
exclude: ["catalog", "duplicates"],
|
|
} satisfies KnipConfig;
|