vinext 1.0.0-beta.1 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/build/client-build-config.js +1 -0
- package/dist/build/report.d.ts +8 -1
- package/dist/build/report.js +12 -2
- package/dist/check.js +15 -2
- package/dist/cli.js +3 -2
- package/dist/config/config-matchers.d.ts +13 -37
- package/dist/config/config-matchers.js +31 -121
- package/dist/config/next-config.d.ts +3 -2
- package/dist/config/next-config.js +3 -1
- package/dist/config/request-context.d.ts +14 -0
- package/dist/config/request-context.js +26 -0
- package/dist/entries/app-rsc-entry.js +27 -9
- package/dist/entries/app-rsc-manifest.js +17 -0
- package/dist/entries/pages-server-entry.js +3 -2
- package/dist/index.js +82 -58
- package/dist/init.d.ts +6 -4
- package/dist/init.js +20 -22
- package/dist/plugins/og-assets.js +1 -1
- package/dist/routing/app-route-graph.d.ts +13 -5
- package/dist/routing/app-route-graph.js +59 -13
- package/dist/routing/app-router.js +12 -6
- package/dist/routing/file-matcher.d.ts +11 -22
- package/dist/routing/file-matcher.js +21 -33
- package/dist/routing/pages-router.js +23 -29
- package/dist/routing/route-matching.d.ts +14 -1
- package/dist/routing/route-matching.js +13 -1
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +7 -4
- package/dist/routing/route-trie.d.ts +5 -1
- package/dist/routing/route-trie.js +5 -2
- package/dist/server/app-browser-entry.js +7 -6
- package/dist/server/app-browser-history-controller.d.ts +1 -1
- package/dist/server/app-browser-history-controller.js +3 -3
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +3 -0
- package/dist/server/app-fallback-renderer.js +4 -2
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-page-boundary-render.d.ts +12 -1
- package/dist/server/app-page-boundary-render.js +84 -2
- package/dist/server/app-page-cache-finalizer.d.ts +1 -1
- package/dist/server/app-page-cache.d.ts +1 -1
- package/dist/server/app-page-dispatch.d.ts +11 -2
- package/dist/server/app-page-dispatch.js +14 -17
- package/dist/server/app-page-element-builder.d.ts +7 -2
- package/dist/server/app-page-element-builder.js +112 -27
- package/dist/server/app-page-head.d.ts +54 -2
- package/dist/server/app-page-head.js +164 -78
- package/dist/server/app-page-http-access-fallback-metadata.d.ts +29 -0
- package/dist/server/app-page-http-access-fallback-metadata.js +106 -0
- package/dist/server/app-page-render.d.ts +2 -2
- package/dist/server/app-page-render.js +1 -1
- package/dist/server/app-page-request.d.ts +12 -0
- package/dist/server/app-page-request.js +133 -13
- package/dist/server/app-page-route-wiring.d.ts +9 -0
- package/dist/server/app-page-route-wiring.js +43 -3
- package/dist/server/app-post-middleware-context.d.ts +1 -1
- package/dist/server/app-post-middleware-context.js +1 -1
- package/dist/server/app-route-handler-dispatch.js +31 -17
- package/dist/server/app-route-handler-execution.d.ts +6 -1
- package/dist/server/app-route-handler-execution.js +42 -16
- package/dist/server/app-route-handler-policy.d.ts +2 -0
- package/dist/server/app-route-handler-policy.js +3 -3
- package/dist/server/app-route-module-loader.d.ts +3 -1
- package/dist/server/app-route-module-loader.js +1 -0
- package/dist/server/app-rsc-cache-busting.d.ts +1 -1
- package/dist/server/app-rsc-errors.d.ts +1 -1
- package/dist/server/app-rsc-errors.js +12 -2
- package/dist/server/app-rsc-handler.d.ts +10 -5
- package/dist/server/app-rsc-handler.js +52 -24
- package/dist/server/app-rsc-request-normalization.d.ts +3 -2
- package/dist/server/app-rsc-request-normalization.js +8 -2
- package/dist/server/app-rsc-response-finalizer.d.ts +2 -2
- package/dist/server/app-rsc-response-finalizer.js +5 -11
- package/dist/server/app-rsc-route-matching.d.ts +20 -0
- package/dist/server/app-rsc-route-matching.js +87 -11
- package/dist/server/app-segment-config.js +13 -3
- package/dist/server/app-server-action-execution.d.ts +3 -1
- package/dist/server/app-server-action-execution.js +9 -7
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/app-ssr-stream.d.ts +1 -7
- package/dist/server/app-ssr-stream.js +2 -10
- package/dist/server/config-headers.d.ts +24 -0
- package/dist/server/config-headers.js +52 -0
- package/dist/server/dev-initial-server-error.d.ts +1 -1
- package/dist/server/dev-server.js +29 -13
- package/dist/server/isr-cache.d.ts +1 -1
- package/dist/server/middleware-matcher-pattern.d.ts +22 -0
- package/dist/server/middleware-matcher-pattern.js +219 -0
- package/dist/server/middleware-matcher.d.ts +2 -7
- package/dist/server/middleware-matcher.js +13 -60
- package/dist/server/middleware-path-to-regexp.d.ts +14 -0
- package/dist/server/middleware-path-to-regexp.js +228 -0
- package/dist/server/middleware-runtime.js +4 -3
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +2 -1
- package/dist/server/pages-data-route.d.ts +11 -1
- package/dist/server/pages-data-route.js +16 -1
- package/dist/server/pages-get-initial-props.d.ts +15 -2
- package/dist/server/pages-get-initial-props.js +16 -6
- package/dist/server/pages-page-data.d.ts +3 -1
- package/dist/server/pages-page-data.js +16 -7
- package/dist/server/pages-page-handler.d.ts +4 -2
- package/dist/server/pages-page-handler.js +16 -17
- package/dist/server/pages-page-response.js +1 -1
- package/dist/server/pages-readiness.d.ts +3 -3
- package/dist/server/pages-request-pipeline.d.ts +1 -0
- package/dist/server/pages-request-pipeline.js +25 -11
- package/dist/server/pages-router-entry.js +6 -0
- package/dist/server/prerender-route-params.js +2 -2
- package/dist/server/prod-server.d.ts +1 -4
- package/dist/server/prod-server.js +36 -23
- package/dist/server/request-pipeline.d.ts +15 -29
- package/dist/server/request-pipeline.js +24 -59
- package/dist/server/rsc-stream-hints.js +122 -18
- package/dist/shims/app.d.ts +1 -1
- package/dist/shims/cache-request-state.d.ts +27 -1
- package/dist/shims/cache-request-state.js +60 -1
- package/dist/shims/cache-runtime.d.ts +12 -1
- package/dist/shims/cache-runtime.js +22 -7
- package/dist/shims/cache.d.ts +5 -5
- package/dist/shims/cache.js +26 -10
- package/dist/shims/document.d.ts +42 -83
- package/dist/shims/document.js +62 -53
- package/dist/shims/dynamic.d.ts +5 -25
- package/dist/shims/dynamic.js +29 -8
- package/dist/shims/error.d.ts +1 -1
- package/dist/shims/error.js +1 -1
- package/dist/shims/fetch-cache.js +12 -3
- package/dist/shims/font-google-base.d.ts +11 -4
- package/dist/shims/font-local.d.ts +10 -3
- package/dist/shims/head.d.ts +13 -2
- package/dist/shims/head.js +3 -3
- package/dist/shims/headers.d.ts +28 -31
- package/dist/shims/headers.js +109 -49
- package/dist/shims/image.d.ts +27 -37
- package/dist/shims/image.js +21 -14
- package/dist/shims/internal/cookie-serialize.d.ts +3 -1
- package/dist/shims/internal/cookie-serialize.js +3 -1
- package/dist/shims/internal/hybrid-client-route-owner.js +3 -1
- package/dist/shims/internal/utils.d.ts +3 -3
- package/dist/shims/legacy-image.d.ts +3 -37
- package/dist/shims/legacy-image.js +6 -16
- package/dist/shims/link.d.ts +15 -22
- package/dist/shims/link.js +20 -11
- package/dist/shims/navigation-context-state.d.ts +1 -1
- package/dist/shims/navigation.d.ts +7 -3
- package/dist/shims/navigation.js +24 -13
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/next-shims-augmentations.d.ts +1 -0
- package/dist/shims/next-shims-public.d.ts +1 -0
- package/dist/shims/public-shim-map.json.js +103 -0
- package/dist/shims/readonly-url-search-params.d.ts +3 -3
- package/dist/shims/readonly-url-search-params.js +3 -3
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/router.d.ts +55 -21
- package/dist/shims/router.js +162 -37
- package/dist/shims/script.d.ts +6 -10
- package/dist/shims/script.js +6 -3
- package/dist/shims/server.d.ts +45 -22
- package/dist/shims/server.js +44 -5
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/shims/url-safety.d.ts +1 -1
- package/dist/shims/url-safety.js +2 -2
- package/dist/typegen.d.ts +8 -2
- package/dist/typegen.js +76 -15
- package/dist/utils/domain-locale.d.ts +2 -2
- package/dist/utils/external-url.d.ts +5 -0
- package/dist/utils/external-url.js +7 -0
- package/dist/utils/regex-safety.d.ts +8 -0
- package/dist/utils/regex-safety.js +737 -0
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -206,7 +206,10 @@ vinext works everywhere. It natively supports Cloudflare Workers (with `npx @vin
|
|
|
206
206
|
vinext is a Vite plugin that reimplements the public Next.js API — routing, server rendering, `next/*` module imports, the CLI — so you can run Next.js applications on Vite instead of the Next.js compiler toolchain. It can be deployed anywhere: Cloudflare Workers is the first natively supported target, with other platforms available via Nitro. Native adapters for more platforms are [planned](https://github.com/cloudflare/vinext/issues/80).
|
|
207
207
|
|
|
208
208
|
**Is this a fork of Next.js?**
|
|
209
|
-
No. vinext is an alternative implementation of the Next.js API surface built on Vite.
|
|
209
|
+
No. vinext is an alternative implementation of the Next.js API surface built on Vite. The core is written from scratch. The goal is not to create a competing framework or add features beyond what Next.js offers; it is to provide the same well-defined API surface on Vite's toolchain.
|
|
210
|
+
|
|
211
|
+
**Does vinext require Next.js to be installed?**
|
|
212
|
+
No. vinext ships fallback declarations for the supported `next` and `next/*` APIs, so applications can run and type-check without the `next` package. If both packages are installed, vinext keeps using Next.js's authoritative types and adds only its own extensions. Compatibility features that consume Next.js internals, such as `styled-jsx`, may still require a matching Next.js installation when used.
|
|
210
213
|
|
|
211
214
|
**How is this different from OpenNext?**
|
|
212
215
|
[OpenNext](https://opennext.js.org/) adapts the _output_ of a standard `next build` to run on various platforms. Because it builds on Next.js's own output, it inherits broad API coverage and has been well-tested for much longer. vinext takes a different approach: it reimplements the Next.js APIs on Vite from scratch, which means faster builds and smaller bundles, but less coverage of the long tail of Next.js features. If you need a mature, well-tested way to run Next.js outside Vercel, OpenNext is the safer choice. If you want a lighter Vite-based toolchain and do not need every Next.js API, vinext may be a good fit.
|
package/dist/build/report.d.ts
CHANGED
|
@@ -44,6 +44,13 @@ declare function hasExportedName(code: string, name: string): boolean;
|
|
|
44
44
|
*/
|
|
45
45
|
declare function extractExportConstString(code: string, name: string): string | null;
|
|
46
46
|
declare function extractMiddlewareMatcherConfig(filePath: string): StaticMiddlewareMatcher | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Extract the statically analyzable `config.matcher` value without first
|
|
49
|
+
* narrowing it to vinext's runtime matcher type. Build validation needs the
|
|
50
|
+
* raw value so malformed matcher objects are rejected instead of disappearing
|
|
51
|
+
* as though no matcher had been configured.
|
|
52
|
+
*/
|
|
53
|
+
declare function extractMiddlewareMatcherConfigValue(filePath: string): unknown;
|
|
47
54
|
/**
|
|
48
55
|
* Extracts the numeric value of `export const <name> = <number|false>`.
|
|
49
56
|
* Supports integers, decimals, negative values, `Infinity`, and `false`.
|
|
@@ -137,4 +144,4 @@ declare function printBuildReport(options: {
|
|
|
137
144
|
prerenderResult?: PrerenderResult;
|
|
138
145
|
}): Promise<void>;
|
|
139
146
|
//#endregion
|
|
140
|
-
export { RouteRow, RouteType, StaticMiddlewareMatcher, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
|
|
147
|
+
export { RouteRow, RouteType, StaticMiddlewareMatcher, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, extractMiddlewareMatcherConfigValue, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
|
package/dist/build/report.js
CHANGED
|
@@ -135,6 +135,16 @@ function extractStringFromConstInitializer(initializer) {
|
|
|
135
135
|
return null;
|
|
136
136
|
}
|
|
137
137
|
function extractMiddlewareMatcherConfig(filePath) {
|
|
138
|
+
const value = extractMiddlewareMatcherConfigValue(filePath);
|
|
139
|
+
return isStaticMiddlewareMatcher(value) ? value : void 0;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extract the statically analyzable `config.matcher` value without first
|
|
143
|
+
* narrowing it to vinext's runtime matcher type. Build validation needs the
|
|
144
|
+
* raw value so malformed matcher objects are rejected instead of disappearing
|
|
145
|
+
* as though no matcher had been configured.
|
|
146
|
+
*/
|
|
147
|
+
function extractMiddlewareMatcherConfigValue(filePath) {
|
|
138
148
|
let code;
|
|
139
149
|
try {
|
|
140
150
|
code = fs.readFileSync(filePath, "utf8");
|
|
@@ -148,7 +158,7 @@ function extractMiddlewareMatcherConfig(filePath) {
|
|
|
148
158
|
const matcherExpression = objectPropertyValue(config, "matcher");
|
|
149
159
|
if (!matcherExpression) return void 0;
|
|
150
160
|
const value = extractStaticJsonValue(matcherExpression);
|
|
151
|
-
return
|
|
161
|
+
return value === UNSUPPORTED_STATIC_VALUE ? void 0 : value;
|
|
152
162
|
}
|
|
153
163
|
function objectPropertyValue(object, key) {
|
|
154
164
|
for (const property of object.properties) {
|
|
@@ -596,4 +606,4 @@ async function printBuildReport(options) {
|
|
|
596
606
|
}
|
|
597
607
|
}
|
|
598
608
|
//#endregion
|
|
599
|
-
export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
|
|
609
|
+
export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, extractMiddlewareMatcherConfig, extractMiddlewareMatcherConfigValue, formatBuildReport, getAppRouteRenderEntryPath, hasExportedName, hasNamedExport, printBuildReport };
|
package/dist/check.js
CHANGED
|
@@ -390,6 +390,19 @@ function findSourceFiles(dir, extensions = [
|
|
|
390
390
|
}
|
|
391
391
|
return results;
|
|
392
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Find files that can contribute to the application compatibility surface.
|
|
395
|
+
* Test modules and test-runner configuration are executed by their own runners
|
|
396
|
+
* rather than bundled into the vinext application, so reporting their imports
|
|
397
|
+
* or CJS globals as migration blockers produces false positives.
|
|
398
|
+
*/
|
|
399
|
+
function findRuntimeSourceFiles(root) {
|
|
400
|
+
return findSourceFiles(root).filter((file) => {
|
|
401
|
+
const basename = path.basename(file);
|
|
402
|
+
const isTestRunnerConfig = /^(?:jest|playwright|vitest)\.config\.[cm]?[jt]sx?$/.test(basename);
|
|
403
|
+
return !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(basename) && !isTestRunnerConfig;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
393
406
|
function isIdentStart(c) {
|
|
394
407
|
return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_" || c === "$";
|
|
395
408
|
}
|
|
@@ -588,7 +601,7 @@ function hasFreeCjsGlobal(content) {
|
|
|
588
601
|
* Scan source files for `import ... from 'next/...'` statements.
|
|
589
602
|
*/
|
|
590
603
|
function scanImports(root) {
|
|
591
|
-
const files =
|
|
604
|
+
const files = findRuntimeSourceFiles(root);
|
|
592
605
|
const importUsage = /* @__PURE__ */ new Map();
|
|
593
606
|
const importRegex = /(?:import\s+(?:[\w{},\s*]+\s+from\s+)?|require\s*\()['"]([^'"]+)['"]\)?/g;
|
|
594
607
|
const typeOnlyImportRegex = /import\s+type\s+/;
|
|
@@ -911,7 +924,7 @@ function checkConventions(root) {
|
|
|
911
924
|
detail: "required for Vite — vinext init will add it automatically"
|
|
912
925
|
});
|
|
913
926
|
}
|
|
914
|
-
const allSourceFiles =
|
|
927
|
+
const allSourceFiles = findRuntimeSourceFiles(root);
|
|
915
928
|
const viewTransitionRegex = /import\s+\{[^}]*\bViewTransition\b[^}]*\}\s+from\s+['"]react['"]/;
|
|
916
929
|
const viewTransitionFiles = [];
|
|
917
930
|
const cjsGlobalFiles = [];
|
package/dist/cli.js
CHANGED
|
@@ -482,11 +482,12 @@ async function typegen() {
|
|
|
482
482
|
root,
|
|
483
483
|
mode: "production"
|
|
484
484
|
});
|
|
485
|
-
const
|
|
485
|
+
const result = await generateRouteTypes({
|
|
486
486
|
root,
|
|
487
487
|
pageExtensions: (await resolveNextConfig(await loadNextConfig(root, PHASE_PRODUCTION_BUILD), root)).pageExtensions
|
|
488
488
|
});
|
|
489
|
-
|
|
489
|
+
const nextEnvMessage = result.nextEnvStatus === "unchanged" ? `${path.relative(root, result.nextEnvPath)} is up to date` : `${result.nextEnvStatus === "created" ? "Created" : "Updated"} ${path.relative(root, result.nextEnvPath)}`;
|
|
490
|
+
console.log(`\n Generated route types at ${path.relative(root, result.routeTypesPath)}\n ${nextEnvMessage}\n`);
|
|
490
491
|
}
|
|
491
492
|
async function initCommand() {
|
|
492
493
|
const parsed = parseArgs(rawArgs);
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { HasCondition, NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "./next-config.js";
|
|
2
|
+
import { RequestContext, normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
|
|
3
|
+
import { isExternalUrl } from "../utils/external-url.js";
|
|
2
4
|
|
|
3
5
|
//#region src/config/config-matchers.d.ts
|
|
4
6
|
/**
|
|
5
7
|
* Detect regex patterns vulnerable to catastrophic backtracking (ReDoS).
|
|
6
8
|
*
|
|
7
|
-
* Uses
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Uses the same deterministic structural analysis as middleware matcher
|
|
10
|
+
* validation. Nested bounded repetition is accepted only when its repeated
|
|
11
|
+
* language has fixed width and unambiguous branches; a fixed outer count can
|
|
12
|
+
* otherwise still cause polynomially catastrophic backtracking on long near
|
|
13
|
+
* misses.
|
|
11
14
|
*
|
|
12
15
|
* Returns true if the pattern appears safe, false if it's potentially dangerous.
|
|
13
16
|
*/
|
|
14
|
-
declare function isSafeRegex(pattern: string): boolean;
|
|
17
|
+
declare function isSafeRegex(pattern: string, flags?: string): boolean;
|
|
15
18
|
/**
|
|
16
19
|
* Compile a regex pattern safely. Returns the compiled RegExp or null if the
|
|
17
20
|
* pattern is invalid or vulnerable to ReDoS.
|
|
@@ -27,16 +30,6 @@ declare function safeRegExp(pattern: string, flags?: string): RegExp | null;
|
|
|
27
30
|
* which CodeQL flags as incomplete sanitization), then groups are restored.
|
|
28
31
|
*/
|
|
29
32
|
declare function escapeHeaderSource(source: string): string;
|
|
30
|
-
/**
|
|
31
|
-
* Request context needed for evaluating has/missing conditions.
|
|
32
|
-
* Callers extract the relevant parts from the incoming Request.
|
|
33
|
-
*/
|
|
34
|
-
type RequestContext = {
|
|
35
|
-
readonly headers: Headers;
|
|
36
|
-
readonly cookies: Record<string, string>;
|
|
37
|
-
readonly query: URLSearchParams;
|
|
38
|
-
readonly host: string;
|
|
39
|
-
};
|
|
40
33
|
/**
|
|
41
34
|
* basePath gating state passed alongside the pathname to every matcher.
|
|
42
35
|
*
|
|
@@ -59,22 +52,6 @@ type BasePathMatchState = {
|
|
|
59
52
|
*/
|
|
60
53
|
hadBasePath: boolean;
|
|
61
54
|
};
|
|
62
|
-
/**
|
|
63
|
-
* Parse a Cookie header string into a key-value record.
|
|
64
|
-
*/
|
|
65
|
-
declare function parseCookies(cookieHeader: string | null): Record<string, string>;
|
|
66
|
-
/**
|
|
67
|
-
* Build a RequestContext from a Web Request object.
|
|
68
|
-
*
|
|
69
|
-
* `cookies` and `query` are lazy memoized getters: they are consumed only by
|
|
70
|
-
* `has`/`missing` condition evaluation (`checkHasConditions` /
|
|
71
|
-
* `matchesRuleConditions`), and most apps configure no such conditions. The
|
|
72
|
-
* cookie split and `searchParams` access are therefore deferred until first
|
|
73
|
-
* read and computed at most once. Mirrors `headersContextFromRequest` in
|
|
74
|
-
* `shims/headers.ts`.
|
|
75
|
-
*/
|
|
76
|
-
declare function requestContextFromRequest(request: Request): RequestContext;
|
|
77
|
-
declare function normalizeHost(hostHeader: string | null, fallbackHostname: string): string;
|
|
78
55
|
/**
|
|
79
56
|
* Unpack `x-middleware-request-*` headers from the collected middleware
|
|
80
57
|
* response headers into the actual request, and strip all `x-middleware-*`
|
|
@@ -147,7 +124,7 @@ declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx:
|
|
|
147
124
|
* to evaluate has/missing conditions. Next.js always has request context
|
|
148
125
|
* when evaluating rewrites, so this parameter is required.
|
|
149
126
|
*/
|
|
150
|
-
declare function matchRewrite(pathname: string, rewrites: NextRewrite[], ctx: RequestContext, basePathState?: BasePathMatchState): string | null;
|
|
127
|
+
declare function matchRewrite(pathname: string, rewrites: NextRewrite[], ctx: RequestContext, basePathState?: BasePathMatchState, paramsPathname?: string): string | null;
|
|
151
128
|
/**
|
|
152
129
|
* Check whether a rewrite source can match a pathname without evaluating its
|
|
153
130
|
* request-dependent `has` / `missing` conditions.
|
|
@@ -175,7 +152,6 @@ declare function sanitizeDestination(dest: string): string;
|
|
|
175
152
|
* Detects any URL scheme (http:, https:, data:, javascript:, blob:, etc.)
|
|
176
153
|
* per RFC 3986, plus protocol-relative URLs (//).
|
|
177
154
|
*/
|
|
178
|
-
declare function isExternalUrl(url: string): boolean;
|
|
179
155
|
/**
|
|
180
156
|
* Merge the original request's query params into a config-redirect
|
|
181
157
|
* destination, preserving them on the resulting `Location`.
|
|
@@ -218,8 +194,8 @@ declare function matchHeaders(pathname: string, headers: NextHeader[], ctx: Requ
|
|
|
218
194
|
value: string;
|
|
219
195
|
}>;
|
|
220
196
|
/**
|
|
221
|
-
* Apply Next.js i18n locale-prefix transformation to a set of redirect
|
|
222
|
-
* rewrite rules. Mirrors the relevant slice of Next.js's `processRoutes`
|
|
197
|
+
* Apply Next.js i18n locale-prefix transformation to a set of redirect,
|
|
198
|
+
* rewrite, or header rules. Mirrors the relevant slice of Next.js's `processRoutes`
|
|
223
199
|
* (load-custom-routes.ts) with one deliberate divergence noted below.
|
|
224
200
|
*
|
|
225
201
|
* For each rule:
|
|
@@ -250,8 +226,8 @@ declare function matchHeaders(pathname: string, headers: NextHeader[], ctx: Requ
|
|
|
250
226
|
* Mirrors the Next.js reference in
|
|
251
227
|
* packages/next/src/lib/load-custom-routes.ts — see `processRoutes`.
|
|
252
228
|
*/
|
|
253
|
-
declare function applyLocaleToRoutes<T extends NextRedirect | NextRewrite>(routes: T[], i18n: NextI18nConfig | null | undefined, type: "redirect" | "rewrite", options?: {
|
|
229
|
+
declare function applyLocaleToRoutes<T extends NextRedirect | NextRewrite | NextHeader>(routes: T[], i18n: NextI18nConfig | null | undefined, type: "redirect" | "rewrite" | "header", options?: {
|
|
254
230
|
trailingSlash?: boolean;
|
|
255
231
|
}): T[];
|
|
256
232
|
//#endregion
|
|
257
|
-
export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
|
|
233
|
+
export { BasePathMatchState, type RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
|
|
2
2
|
import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
|
|
3
|
-
import {
|
|
3
|
+
import { analyzeRegexSafety } from "../utils/regex-safety.js";
|
|
4
|
+
import { normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
|
|
5
|
+
import { isExternalUrl } from "../utils/external-url.js";
|
|
4
6
|
//#region src/config/config-matchers.ts
|
|
5
7
|
/**
|
|
6
8
|
* Cache for compiled regex patterns in matchConfigPattern.
|
|
@@ -191,75 +193,16 @@ function stripHopByHopRequestHeaders(headers) {
|
|
|
191
193
|
/**
|
|
192
194
|
* Detect regex patterns vulnerable to catastrophic backtracking (ReDoS).
|
|
193
195
|
*
|
|
194
|
-
* Uses
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
196
|
+
* Uses the same deterministic structural analysis as middleware matcher
|
|
197
|
+
* validation. Nested bounded repetition is accepted only when its repeated
|
|
198
|
+
* language has fixed width and unambiguous branches; a fixed outer count can
|
|
199
|
+
* otherwise still cause polynomially catastrophic backtracking on long near
|
|
200
|
+
* misses.
|
|
198
201
|
*
|
|
199
202
|
* Returns true if the pattern appears safe, false if it's potentially dangerous.
|
|
200
203
|
*/
|
|
201
|
-
function isSafeRegex(pattern) {
|
|
202
|
-
|
|
203
|
-
let depth = 0;
|
|
204
|
-
let i = 0;
|
|
205
|
-
while (i < pattern.length) {
|
|
206
|
-
const ch = pattern[i];
|
|
207
|
-
if (ch === "\\") {
|
|
208
|
-
i += 2;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (ch === "[") {
|
|
212
|
-
i++;
|
|
213
|
-
while (i < pattern.length && pattern[i] !== "]") {
|
|
214
|
-
if (pattern[i] === "\\") i++;
|
|
215
|
-
i++;
|
|
216
|
-
}
|
|
217
|
-
i++;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (ch === "(") {
|
|
221
|
-
depth++;
|
|
222
|
-
if (quantifierAtDepth.length <= depth) quantifierAtDepth.push(false);
|
|
223
|
-
else quantifierAtDepth[depth] = false;
|
|
224
|
-
i++;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (ch === ")") {
|
|
228
|
-
const hadQuantifier = depth > 0 && quantifierAtDepth[depth];
|
|
229
|
-
if (depth > 0) depth--;
|
|
230
|
-
const next = pattern[i + 1];
|
|
231
|
-
if (next === "+" || next === "*" || next === "{") {
|
|
232
|
-
if (hadQuantifier) return false;
|
|
233
|
-
if (depth >= 0 && depth < quantifierAtDepth.length) quantifierAtDepth[depth] = true;
|
|
234
|
-
}
|
|
235
|
-
i++;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (ch === "+" || ch === "*") {
|
|
239
|
-
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
240
|
-
i++;
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (ch === "?") {
|
|
244
|
-
const prev = i > 0 ? pattern[i - 1] : "";
|
|
245
|
-
if (prev !== "+" && prev !== "*" && prev !== "?" && prev !== "}") {
|
|
246
|
-
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
247
|
-
}
|
|
248
|
-
i++;
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (ch === "{") {
|
|
252
|
-
let j = i + 1;
|
|
253
|
-
while (j < pattern.length && /[\d,]/.test(pattern[j])) j++;
|
|
254
|
-
if (j < pattern.length && pattern[j] === "}" && j > i + 1) {
|
|
255
|
-
if (depth > 0) quantifierAtDepth[depth] = true;
|
|
256
|
-
i = j + 1;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
i++;
|
|
261
|
-
}
|
|
262
|
-
return true;
|
|
204
|
+
function isSafeRegex(pattern, flags) {
|
|
205
|
+
return analyzeRegexSafety(pattern, { ignoreCase: flags?.includes("i") }) === null;
|
|
263
206
|
}
|
|
264
207
|
/**
|
|
265
208
|
* Compile a regex pattern safely. Returns the compiled RegExp or null if the
|
|
@@ -268,8 +211,8 @@ function isSafeRegex(pattern) {
|
|
|
268
211
|
* Logs a warning when a pattern is rejected so developers can fix their config.
|
|
269
212
|
*/
|
|
270
213
|
function safeRegExp(pattern, flags) {
|
|
271
|
-
if (!isSafeRegex(pattern)) {
|
|
272
|
-
console.warn(`[vinext] Rejecting potentially unsafe regex pattern (ReDoS risk): ${pattern}\n
|
|
214
|
+
if (!isSafeRegex(pattern, flags)) {
|
|
215
|
+
console.warn(`[vinext] Rejecting potentially unsafe regex pattern (ReDoS risk): ${pattern}\n Nested or ambiguous repetition can cause catastrophic backtracking.\n Simplify the pattern to make repeated matches fixed and unambiguous.`);
|
|
273
216
|
return null;
|
|
274
217
|
}
|
|
275
218
|
try {
|
|
@@ -342,40 +285,6 @@ function shouldEvaluateRule(ruleBasePath, state) {
|
|
|
342
285
|
return ruleBasePath === false ? !state.hadBasePath : state.hadBasePath;
|
|
343
286
|
}
|
|
344
287
|
/**
|
|
345
|
-
* Parse a Cookie header string into a key-value record.
|
|
346
|
-
*/
|
|
347
|
-
function parseCookies(cookieHeader) {
|
|
348
|
-
return parseCookieHeader(cookieHeader);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Build a RequestContext from a Web Request object.
|
|
352
|
-
*
|
|
353
|
-
* `cookies` and `query` are lazy memoized getters: they are consumed only by
|
|
354
|
-
* `has`/`missing` condition evaluation (`checkHasConditions` /
|
|
355
|
-
* `matchesRuleConditions`), and most apps configure no such conditions. The
|
|
356
|
-
* cookie split and `searchParams` access are therefore deferred until first
|
|
357
|
-
* read and computed at most once. Mirrors `headersContextFromRequest` in
|
|
358
|
-
* `shims/headers.ts`.
|
|
359
|
-
*/
|
|
360
|
-
function requestContextFromRequest(request) {
|
|
361
|
-
const url = new URL(request.url);
|
|
362
|
-
let cookies;
|
|
363
|
-
let query;
|
|
364
|
-
return {
|
|
365
|
-
headers: request.headers,
|
|
366
|
-
get cookies() {
|
|
367
|
-
return cookies ??= parseCookies(request.headers.get("cookie"));
|
|
368
|
-
},
|
|
369
|
-
get query() {
|
|
370
|
-
return query ??= url.searchParams;
|
|
371
|
-
},
|
|
372
|
-
host: normalizeHost(request.headers.get("host"), url.hostname)
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
function normalizeHost(hostHeader, fallbackHostname) {
|
|
376
|
-
return (hostHeader ?? fallbackHostname).split(":", 1)[0].toLowerCase();
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
288
|
* Unpack `x-middleware-request-*` headers from the collected middleware
|
|
380
289
|
* response headers into the actual request, and strip all `x-middleware-*`
|
|
381
290
|
* internal signals so they never reach clients.
|
|
@@ -410,7 +319,7 @@ function _emptyParams() {
|
|
|
410
319
|
return Object.create(null);
|
|
411
320
|
}
|
|
412
321
|
function _matchConditionValue(actualValue, expectedValue) {
|
|
413
|
-
if (expectedValue
|
|
322
|
+
if (!expectedValue) return actualValue ? _emptyParams() : null;
|
|
414
323
|
const re = _cachedConditionRegex(expectedValue);
|
|
415
324
|
if (re) {
|
|
416
325
|
const match = re.exec(actualValue);
|
|
@@ -440,9 +349,10 @@ function matchSingleCondition(condition, ctx) {
|
|
|
440
349
|
return _matchConditionValue(cookieValue, condition.value);
|
|
441
350
|
}
|
|
442
351
|
case "query": {
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
|
|
352
|
+
const queryValues = ctx.query.getAll(condition.key);
|
|
353
|
+
if (queryValues.length === 0) return null;
|
|
354
|
+
if (!condition.value && queryValues.length > 1) return _emptyParams();
|
|
355
|
+
return _matchConditionValue(queryValues[queryValues.length - 1], condition.value);
|
|
446
356
|
}
|
|
447
357
|
case "host":
|
|
448
358
|
if (condition.value !== void 0) return _matchConditionValue(ctx.host, condition.value);
|
|
@@ -710,11 +620,12 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
710
620
|
* to evaluate has/missing conditions. Next.js always has request context
|
|
711
621
|
* when evaluating rewrites, so this parameter is required.
|
|
712
622
|
*/
|
|
713
|
-
function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT) {
|
|
623
|
+
function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT, paramsPathname = pathname) {
|
|
714
624
|
for (const rewrite of rewrites) {
|
|
715
625
|
if (!shouldEvaluateRule(rewrite.basePath, basePathState)) continue;
|
|
716
|
-
const
|
|
717
|
-
if (
|
|
626
|
+
const matchedParams = matchConfigPattern(pathname, rewrite.source);
|
|
627
|
+
if (matchedParams) {
|
|
628
|
+
const params = paramsPathname === pathname ? matchedParams : matchConfigPattern(paramsPathname, rewrite.source) ?? matchedParams;
|
|
718
629
|
const conditionParams = rewrite.has || rewrite.missing ? collectConditionParams(rewrite.has, rewrite.missing, ctx) : _emptyParams();
|
|
719
630
|
if (!conditionParams) continue;
|
|
720
631
|
const rewriteParams = {
|
|
@@ -868,9 +779,6 @@ function sanitizeDestination(dest) {
|
|
|
868
779
|
* Detects any URL scheme (http:, https:, data:, javascript:, blob:, etc.)
|
|
869
780
|
* per RFC 3986, plus protocol-relative URLs (//).
|
|
870
781
|
*/
|
|
871
|
-
function isExternalUrl(url) {
|
|
872
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
|
|
873
|
-
}
|
|
874
782
|
/**
|
|
875
783
|
* Merge the original request's query params into a config-redirect
|
|
876
784
|
* destination, preserving them on the resulting `Location`.
|
|
@@ -1004,8 +912,8 @@ function _escapeRegexString(value) {
|
|
|
1004
912
|
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
1005
913
|
}
|
|
1006
914
|
/**
|
|
1007
|
-
* Apply Next.js i18n locale-prefix transformation to a set of redirect
|
|
1008
|
-
* rewrite rules. Mirrors the relevant slice of Next.js's `processRoutes`
|
|
915
|
+
* Apply Next.js i18n locale-prefix transformation to a set of redirect,
|
|
916
|
+
* rewrite, or header rules. Mirrors the relevant slice of Next.js's `processRoutes`
|
|
1009
917
|
* (load-custom-routes.ts) with one deliberate divergence noted below.
|
|
1010
918
|
*
|
|
1011
919
|
* For each rule:
|
|
@@ -1048,7 +956,8 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
|
|
|
1048
956
|
out.push(r);
|
|
1049
957
|
continue;
|
|
1050
958
|
}
|
|
1051
|
-
const
|
|
959
|
+
const destination = "destination" in r ? r.destination : void 0;
|
|
960
|
+
const isExternal = !!destination && !destination.startsWith("/");
|
|
1052
961
|
if (!isExternal) for (const locale of defaultLocales) {
|
|
1053
962
|
const localizedSource = `/${locale}${suffixFor(r.source)}`;
|
|
1054
963
|
out.push({
|
|
@@ -1057,13 +966,14 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
|
|
|
1057
966
|
});
|
|
1058
967
|
}
|
|
1059
968
|
const internalSource = `${internalLocale}${suffixFor(r.source)}`;
|
|
1060
|
-
let internalDestination =
|
|
969
|
+
let internalDestination = destination;
|
|
1061
970
|
if (internalDestination && internalDestination.startsWith("/") && !isExternal) internalDestination = `/:nextInternalLocale${internalDestination === "/" && !trailingSlash ? "" : internalDestination}`;
|
|
1062
|
-
|
|
971
|
+
const internalRoute = {
|
|
1063
972
|
...r,
|
|
1064
|
-
source: internalSource
|
|
1065
|
-
|
|
1066
|
-
|
|
973
|
+
source: internalSource
|
|
974
|
+
};
|
|
975
|
+
if ("destination" in internalRoute && internalDestination !== void 0) internalRoute.destination = internalDestination;
|
|
976
|
+
out.push(internalRoute);
|
|
1067
977
|
out.push(r);
|
|
1068
978
|
}
|
|
1069
979
|
return out;
|
|
@@ -54,7 +54,8 @@ type NextHeader = {
|
|
|
54
54
|
key: string;
|
|
55
55
|
value: string;
|
|
56
56
|
}>; /** See {@link NextRedirect.basePath}. */
|
|
57
|
-
basePath?: false;
|
|
57
|
+
basePath?: false; /** See {@link NextRedirect.locale}. */
|
|
58
|
+
locale?: false;
|
|
58
59
|
};
|
|
59
60
|
type NextI18nConfig = {
|
|
60
61
|
/** List of supported locales */locales: string[]; /** The default locale (used when no locale prefix is in the URL) */
|
|
@@ -71,7 +72,7 @@ type NextI18nConfig = {
|
|
|
71
72
|
domain: string;
|
|
72
73
|
defaultLocale: string;
|
|
73
74
|
locales?: string[];
|
|
74
|
-
http?:
|
|
75
|
+
http?: true;
|
|
75
76
|
}>;
|
|
76
77
|
};
|
|
77
78
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
|
|
2
2
|
import { normalizePageExtensions } from "../routing/file-matcher.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isExternalUrl } from "../utils/external-url.js";
|
|
4
|
+
import { applyLocaleToRoutes } from "./config-matchers.js";
|
|
4
5
|
import { isUnknownRecord } from "../utils/record.js";
|
|
5
6
|
import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
|
|
6
7
|
import { flattenPluginOptions } from "../utils/plugin-options.js";
|
|
@@ -815,6 +816,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
815
816
|
afterFiles: applyLocaleToRoutes(rewrites.afterFiles, i18n, "rewrite", opts),
|
|
816
817
|
fallback: applyLocaleToRoutes(rewrites.fallback, i18n, "rewrite", opts)
|
|
817
818
|
};
|
|
819
|
+
headers = applyLocaleToRoutes(headers, i18n, "header", opts);
|
|
818
820
|
}
|
|
819
821
|
const images = config.images ? {
|
|
820
822
|
...config.images,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/config/request-context.d.ts
|
|
2
|
+
/** Request context needed for evaluating has/missing conditions. */
|
|
3
|
+
type RequestContext = {
|
|
4
|
+
readonly headers: Headers;
|
|
5
|
+
readonly cookies: Record<string, string>;
|
|
6
|
+
readonly query: URLSearchParams;
|
|
7
|
+
readonly host: string;
|
|
8
|
+
};
|
|
9
|
+
declare function parseCookies(cookieHeader: string | null): Record<string, string>;
|
|
10
|
+
declare function normalizeHost(hostHeader: string | null, fallbackHostname: string): string;
|
|
11
|
+
/** Build a lazily parsed request context from a Web Request. */
|
|
12
|
+
declare function requestContextFromRequest(request: Request): RequestContext;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { RequestContext, normalizeHost, parseCookies, requestContextFromRequest };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseCookieHeader } from "../utils/parse-cookie.js";
|
|
2
|
+
//#region src/config/request-context.ts
|
|
3
|
+
function parseCookies(cookieHeader) {
|
|
4
|
+
return parseCookieHeader(cookieHeader);
|
|
5
|
+
}
|
|
6
|
+
function normalizeHost(hostHeader, fallbackHostname) {
|
|
7
|
+
return (hostHeader ?? fallbackHostname).split(":", 1)[0].toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
/** Build a lazily parsed request context from a Web Request. */
|
|
10
|
+
function requestContextFromRequest(request) {
|
|
11
|
+
const url = new URL(request.url);
|
|
12
|
+
let cookies;
|
|
13
|
+
let query;
|
|
14
|
+
return {
|
|
15
|
+
headers: request.headers,
|
|
16
|
+
get cookies() {
|
|
17
|
+
return cookies ??= parseCookies(request.headers.get("cookie"));
|
|
18
|
+
},
|
|
19
|
+
get query() {
|
|
20
|
+
return query ??= url.searchParams;
|
|
21
|
+
},
|
|
22
|
+
host: normalizeHost(request.headers.get("host"), url.hostname)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { normalizeHost, parseCookies, requestContextFromRequest };
|