vinext 1.2.0 → 1.3.0
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 +13 -10
- package/dist/build/client-build-config.d.ts +64 -3
- package/dist/build/client-build-config.js +131 -4
- package/dist/build/client-reference-groups.d.ts +163 -0
- package/dist/build/client-reference-groups.js +534 -0
- package/dist/build/module-dependency-cache.d.ts +31 -0
- package/dist/build/module-dependency-cache.js +51 -1
- package/dist/build/next-client-runtime-manifests.d.ts +10 -1
- package/dist/build/next-client-runtime-manifests.js +19 -4
- package/dist/build/nitro-public-files.d.ts +24 -0
- package/dist/build/nitro-public-files.js +60 -0
- package/dist/build/output-root-externals.d.ts +21 -0
- package/dist/build/output-root-externals.js +40 -0
- package/dist/build/pages-client-assets-module.js +1 -1
- package/dist/build/prerender-paths.js +6 -3
- package/dist/build/prerender.js +1 -1
- package/dist/build/server-actions-encryption-key.d.ts +23 -0
- package/dist/build/server-actions-encryption-key.js +101 -0
- package/dist/build/ssr-manifest.d.ts +3 -0
- package/dist/build/ssr-manifest.js +10 -1
- package/dist/check.js +5 -6
- package/dist/cli-dev-config.js +3 -3
- package/dist/cli.js +1 -1
- package/dist/client/client-rewrite-params.d.ts +9 -0
- package/dist/client/client-rewrite-params.js +20 -0
- package/dist/client/dev-error-overlay.js +2 -1
- package/dist/client/navigation-runtime.d.ts +25 -2
- package/dist/client/navigation-runtime.js +42 -11
- package/dist/client/vinext-next-data.d.ts +0 -1
- package/dist/config/config-matchers.d.ts +15 -40
- package/dist/config/config-matchers.js +195 -262
- package/dist/config/config-source.d.ts +34 -0
- package/dist/config/config-source.js +141 -0
- package/dist/config/header-params.d.ts +29 -0
- package/dist/config/header-params.js +106 -0
- package/dist/config/next-config.d.ts +7 -5
- package/dist/config/next-config.js +120 -7
- package/dist/entries/app-browser-entry.d.ts +14 -1
- package/dist/entries/app-browser-entry.js +26 -36
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +92 -57
- package/dist/entries/app-rsc-manifest.d.ts +10 -0
- package/dist/entries/app-rsc-manifest.js +20 -1
- package/dist/entries/app-ssr-entry.d.ts +4 -1
- package/dist/entries/app-ssr-entry.js +9 -2
- package/dist/entries/pages-client-entry.d.ts +1 -0
- package/dist/entries/pages-client-entry.js +6 -6
- package/dist/entries/pages-server-entry.js +17 -14
- package/dist/index.d.ts +11 -0
- package/dist/index.js +350 -175
- package/dist/init.d.ts +1 -1
- package/dist/init.js +103 -6
- package/dist/plugins/ast-utils.d.ts +53 -2
- package/dist/plugins/ast-utils.js +90 -2
- package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
- package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
- package/dist/plugins/commonjs-esm-facade.js +2 -2
- package/dist/plugins/css-module-imports.js +13 -20
- package/dist/plugins/dynamic-preload-metadata.js +2 -3
- package/dist/plugins/extensionless-dynamic-import.js +2 -3
- package/dist/plugins/fonts.d.ts +1 -1
- package/dist/plugins/fonts.js +3 -3
- package/dist/plugins/ignore-dynamic-requests.js +2 -3
- package/dist/plugins/import-meta-url.js +3 -4
- package/dist/plugins/instrumentation-client.js +3 -2
- package/dist/plugins/middleware-export-validation.js +2 -3
- package/dist/plugins/og-harfbuzz.js +2 -3
- package/dist/plugins/optimize-imports.d.ts +25 -2
- package/dist/plugins/optimize-imports.js +109 -24
- package/dist/plugins/pages-node-externals.js +2 -3
- package/dist/plugins/remove-console.js +2 -18
- package/dist/plugins/require-condition-resolution.js +3 -3
- package/dist/plugins/require-context.js +2 -3
- package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
- package/dist/plugins/rsc-client-reference-loaders.js +336 -18
- package/dist/plugins/strip-server-exports.js +3 -4
- package/dist/plugins/styled-jsx.js +2 -3
- package/dist/plugins/use-cache-callable.d.ts +26 -2
- package/dist/plugins/use-cache-callable.js +101 -36
- package/dist/plugins/worker-image-imports.d.ts +1 -0
- package/dist/plugins/worker-image-imports.js +31 -34
- package/dist/plugins/worker-use-server.js +38 -14
- package/dist/routing/app-route-graph.d.ts +1 -1
- package/dist/routing/browser-route-manifest.d.ts +43 -0
- package/dist/routing/browser-route-manifest.js +195 -0
- package/dist/routing/hashed-route-patterns.d.ts +4 -0
- package/dist/routing/hashed-route-patterns.js +22 -0
- package/dist/routing/optimistic-route-trie.d.ts +27 -0
- package/dist/routing/optimistic-route-trie.js +102 -0
- package/dist/routing/route-validation.d.ts +6 -1
- package/dist/routing/route-validation.js +26 -10
- package/dist/server/api-handler.js +1 -1
- package/dist/server/app-action-forwarding.d.ts +8 -0
- package/dist/server/app-action-forwarding.js +17 -3
- package/dist/server/app-browser-entry.js +149 -62
- package/dist/server/app-browser-error.d.ts +3 -1
- package/dist/server/app-browser-error.js +7 -2
- package/dist/server/app-browser-interception-context.d.ts +2 -2
- package/dist/server/app-browser-interception-context.js +1 -1
- package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
- package/dist/server/app-browser-mpa-navigation.js +6 -5
- package/dist/server/app-browser-navigation-controller.d.ts +17 -6
- package/dist/server/app-browser-navigation-controller.js +88 -26
- package/dist/server/app-browser-prefetch-response.js +2 -2
- package/dist/server/app-browser-state.d.ts +2 -2
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-supplemental-refresh.js +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +4 -4
- package/dist/server/app-client-reference-preloader.d.ts +23 -0
- package/dist/server/app-client-reference-preloader.js +30 -7
- package/dist/server/app-fallback-renderer.js +2 -1
- package/dist/server/app-middleware.d.ts +6 -0
- package/dist/server/app-middleware.js +8 -10
- package/dist/server/app-optimistic-routing.d.ts +8 -10
- package/dist/server/app-optimistic-routing.js +4 -95
- package/dist/server/app-page-boundary-render.js +8 -1
- package/dist/server/app-page-cache-render.js +1 -0
- package/dist/server/app-page-dispatch.js +3 -1
- package/dist/server/app-page-render.js +4 -2
- package/dist/server/app-page-rsc-render-status.d.ts +1 -0
- package/dist/server/app-page-rsc-render-status.js +1 -0
- package/dist/server/app-page-search-params-observation.d.ts +1 -4
- package/dist/server/app-page-search-params-observation.js +8 -10
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-pages-bridge.d.ts +2 -1
- package/dist/server/app-pages-bridge.js +12 -8
- package/dist/server/app-prerender-endpoints.js +1 -1
- package/dist/server/app-prerender-static-params.js +3 -2
- package/dist/server/app-render-abort-error.d.ts +19 -0
- package/dist/server/app-render-abort-error.js +31 -1
- package/dist/server/app-render-dependency.js +42 -2
- package/dist/server/app-request-stage-independent-entry.js +7 -7
- package/dist/server/app-response-stage-entry.js +5 -5
- package/dist/server/app-route-handler-dispatch.js +3 -1
- package/dist/server/app-route-handler-execution.d.ts +2 -0
- package/dist/server/app-route-handler-execution.js +9 -2
- package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
- package/dist/server/app-route-handler-middleware-context.js +3 -3
- package/dist/server/app-route-handler-response.d.ts +2 -1
- package/dist/server/app-route-handler-response.js +2 -2
- package/dist/server/app-route-handler-runtime.d.ts +2 -0
- package/dist/server/app-route-handler-runtime.js +21 -4
- package/dist/server/app-route-module-loader.js +7 -6
- package/dist/server/app-route-tree.d.ts +20 -0
- package/dist/server/app-route-tree.js +18 -0
- package/dist/server/app-router-entry.js +7 -7
- package/dist/server/app-rsc-cache-busting.d.ts +16 -0
- package/dist/server/app-rsc-cache-busting.js +29 -12
- package/dist/server/app-rsc-combined-handler.js +8 -1
- package/dist/server/app-rsc-errors.d.ts +7 -0
- package/dist/server/app-rsc-errors.js +9 -2
- package/dist/server/app-rsc-handler.d.ts +4 -1
- package/dist/server/app-rsc-handler.js +151 -135
- package/dist/server/app-rsc-request-normalization.js +3 -3
- package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.js +5 -3
- package/dist/server/app-rsc-route-matching.d.ts +1 -0
- package/dist/server/app-rsc-route-matching.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +23 -6
- package/dist/server/app-server-action-execution.js +51 -57
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +21 -11
- package/dist/server/app-ssr-stream.d.ts +7 -0
- package/dist/server/app-ssr-stream.js +13 -1
- package/dist/server/app-visited-response-cache.js +2 -2
- package/dist/server/config-headers.js +13 -4
- package/dist/server/defer-until-stream-consumed.d.ts +2 -1
- package/dist/server/defer-until-stream-consumed.js +3 -1
- package/dist/server/dev-server.js +1 -1
- package/dist/server/image-optimization.js +1 -0
- package/dist/server/isolate-shared-promise.d.ts +10 -0
- package/dist/server/isolate-shared-promise.js +33 -0
- package/dist/server/isr-cache.js +3 -2
- package/dist/server/metadata-route-response.js +2 -2
- package/dist/server/middleware-matcher-pattern.d.ts +0 -1
- package/dist/server/middleware-matcher-pattern.js +11 -140
- package/dist/server/middleware-matcher.js +5 -6
- package/dist/server/middleware-response-headers.d.ts +31 -0
- package/dist/server/middleware-response-headers.js +57 -1
- package/dist/server/middleware-runtime.js +30 -17
- package/dist/server/navigation-planner.d.ts +2 -2
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/nitro-public-files-plugin.d.ts +38 -0
- package/dist/server/nitro-public-files-plugin.js +106 -0
- package/dist/server/nitro-public-files.d.ts +21 -0
- package/dist/server/nitro-public-files.js +39 -0
- package/dist/server/open-redirect.d.ts +11 -4
- package/dist/server/open-redirect.js +11 -4
- package/dist/server/pages-api-route.js +2 -2
- package/dist/server/pages-asset-tags.d.ts +5 -0
- package/dist/server/pages-asset-tags.js +9 -2
- package/dist/server/pages-client-assets.d.ts +4 -1
- package/dist/server/pages-data-route.js +2 -1
- package/dist/server/pages-i18n.d.ts +18 -0
- package/dist/server/pages-i18n.js +18 -2
- package/dist/server/pages-node-compat.d.ts +5 -3
- package/dist/server/pages-node-compat.js +35 -16
- package/dist/server/pages-page-data.js +1 -1
- package/dist/server/pages-page-handler.js +1 -1
- package/dist/server/pages-page-response.js +1 -1
- package/dist/server/pages-request-pipeline.d.ts +6 -1
- package/dist/server/pages-request-pipeline.js +35 -39
- package/dist/server/pages-request-stage-entry.js +20 -10
- package/dist/server/pages-response-stage-entry.js +1 -1
- package/dist/server/pages-revalidate.js +3 -2
- package/dist/server/pages-router-entry.js +2 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +74 -36
- package/dist/server/proxy-trust.d.ts +6 -0
- package/dist/server/proxy-trust.js +12 -6
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +12 -1
- package/dist/server/rsc-stream-hints.d.ts +1 -1
- package/dist/server/rsc-stream-hints.js +21 -3
- package/dist/server/server-reference-loader.js +10 -1
- package/dist/server/static-file-cache.js +15 -3
- package/dist/server/worker-image-optimization.js +19 -6
- package/dist/server/worker-utils.js +1 -1
- package/dist/shims/cache-handler.js +3 -2
- package/dist/shims/cache-request-state.d.ts +40 -3
- package/dist/shims/cache-request-state.js +70 -5
- package/dist/shims/cache-runtime.js +47 -19
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +17 -8
- package/dist/shims/cdn-purge-routes.d.ts +24 -0
- package/dist/shims/cdn-purge-routes.js +77 -0
- package/dist/shims/dynamic.js +17 -4
- package/dist/shims/form.js +8 -3
- package/dist/shims/head.js +1 -1
- package/dist/shims/headers.js +2 -2
- package/dist/shims/image-props.d.ts +93 -0
- package/dist/shims/image-props.js +323 -0
- package/dist/shims/image.d.ts +4 -18
- package/dist/shims/image.js +4 -319
- package/dist/shims/internal/headers-state.d.ts +7 -0
- package/dist/shims/internal/headers-state.js +14 -2
- package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +32 -15
- package/dist/shims/internal/hybrid-client-route-owner.js +9 -8
- package/dist/shims/link-prefetch.js +5 -4
- package/dist/shims/link.js +7 -5
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.js +21 -10
- package/dist/shims/og.js +35 -10
- package/dist/shims/request-context.d.ts +13 -0
- package/dist/shims/router.js +28 -52
- package/dist/shims/server.d.ts +42 -8
- package/dist/shims/server.js +99 -15
- package/dist/shims/thenable-params.d.ts +6 -1
- package/dist/shims/thenable-params.js +11 -2
- package/dist/shims/unified-request-context.d.ts +5 -0
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/shims/url-utils.d.ts +7 -0
- package/dist/shims/url-utils.js +12 -3
- package/dist/utils/asset-prefix.d.ts +7 -0
- package/dist/utils/asset-prefix.js +10 -1
- package/dist/utils/client-build-manifest.js +12 -5
- package/dist/utils/client-runtime-metadata.d.ts +1 -0
- package/dist/utils/client-runtime-metadata.js +15 -2
- package/dist/utils/domain-locale.js +1 -1
- package/dist/utils/lazy-chunks.d.ts +4 -0
- package/dist/utils/manifest-paths.d.ts +14 -0
- package/dist/utils/manifest-paths.js +22 -1
- package/dist/utils/path-to-regexp.d.ts +21 -0
- package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
- package/dist/utils/plugin-react-version.d.ts +15 -0
- package/dist/utils/plugin-react-version.js +64 -0
- package/dist/utils/plugin-rsc-version.d.ts +2 -0
- package/dist/utils/plugin-rsc-version.js +28 -2
- package/dist/utils/public-routes.d.ts +2 -0
- package/dist/utils/public-routes.js +18 -7
- package/dist/utils/react-version.d.ts +7 -0
- package/dist/utils/react-version.js +39 -16
- package/package.json +6 -6
- package/dist/build/nitro-route-rules.d.ts +0 -47
- package/dist/build/nitro-route-rules.js +0 -79
- package/dist/plugins/cache-flight-codec.d.ts +0 -19
- package/dist/plugins/cache-flight-codec.js +0 -68
- package/dist/server/middleware-path-to-regexp.d.ts +0 -13
- package/dist/utils/regex-safety.d.ts +0 -17
- package/dist/utils/regex-safety.js +0 -789
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath } from "./middleware-path-to-regexp.js";
|
|
1
|
+
import { normalizePathTokens, parsePathPattern, pathTokensToRegExpSource } from "../utils/path-to-regexp.js";
|
|
3
2
|
//#region src/server/middleware-matcher-pattern.ts
|
|
4
3
|
const MATCHER_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
5
4
|
"source",
|
|
@@ -52,154 +51,26 @@ function matcherObjectSource(value) {
|
|
|
52
51
|
function isValidMiddlewareMatcherObjectConfig(value) {
|
|
53
52
|
return matcherObjectSource(value).error === void 0;
|
|
54
53
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
return new RegExp(`^(?:${pattern})$`).test(value);
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function atomsOverlap(left, right) {
|
|
63
|
-
return regexAtomsMayOverlap(left, right, true);
|
|
64
|
-
}
|
|
65
|
-
function groupCanMatchEmpty(group) {
|
|
66
|
-
if (/^\?(?:[=!]|<[=!])/.test(group)) return true;
|
|
67
|
-
return patternMatches(group.startsWith("?:") ? group.slice(2) : group, "");
|
|
68
|
-
}
|
|
69
|
-
function hasOverlappingSequentialRepetition(pattern) {
|
|
70
|
-
const repeatedAtDepth = [[]];
|
|
71
|
-
const groupStarts = [];
|
|
72
|
-
let depth = 0;
|
|
73
|
-
for (let index = 0; index < pattern.length; index++) {
|
|
74
|
-
const character = pattern[index];
|
|
75
|
-
if (character === "(") {
|
|
76
|
-
groupStarts.push(index);
|
|
77
|
-
depth++;
|
|
78
|
-
repeatedAtDepth[depth] = [];
|
|
79
|
-
if (pattern[index + 1] === "?") {
|
|
80
|
-
index++;
|
|
81
|
-
if (pattern[index + 1] === "<" && /[=!]/.test(pattern[index + 2] ?? "")) index += 2;
|
|
82
|
-
else if (/[=:!]/.test(pattern[index + 1] ?? "")) index++;
|
|
83
|
-
}
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (character === ")") {
|
|
87
|
-
const groupStart = groupStarts.pop();
|
|
88
|
-
repeatedAtDepth[depth] = [];
|
|
89
|
-
depth = Math.max(0, depth - 1);
|
|
90
|
-
if (groupStart !== void 0) {
|
|
91
|
-
const modifier = pattern[index + 1];
|
|
92
|
-
if (!(modifier === "*" || modifier === "?" || modifier === "{" && /^\{0(?:,\d*)?\}/.test(pattern.slice(index + 1))) && !groupCanMatchEmpty(pattern.slice(groupStart + 1, index))) repeatedAtDepth[depth] = [];
|
|
93
|
-
}
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (character === "|") {
|
|
97
|
-
repeatedAtDepth[depth] = [];
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (character === "^" || character === "$") continue;
|
|
101
|
-
if (character === "*" || character === "+" || character === "?" || character === "{") continue;
|
|
102
|
-
let atom = character;
|
|
103
|
-
if (character === "\\") {
|
|
104
|
-
if (index + 1 >= pattern.length) return true;
|
|
105
|
-
atom += pattern[++index];
|
|
106
|
-
} else if (character === "[") {
|
|
107
|
-
let classEnd = index + 1;
|
|
108
|
-
if (pattern[classEnd] === "^") classEnd++;
|
|
109
|
-
while (classEnd < pattern.length && pattern[classEnd] !== "]") {
|
|
110
|
-
if (pattern[classEnd] === "\\") classEnd++;
|
|
111
|
-
classEnd++;
|
|
112
|
-
}
|
|
113
|
-
if (classEnd >= pattern.length) return true;
|
|
114
|
-
atom = pattern.slice(index, classEnd + 1);
|
|
115
|
-
index = classEnd;
|
|
116
|
-
} else if (character === ".") atom = ".";
|
|
117
|
-
const quantifierStart = index + 1;
|
|
118
|
-
const quantifier = pattern[quantifierStart];
|
|
119
|
-
let unbounded = quantifier === "*" || quantifier === "+";
|
|
120
|
-
let canBeEmpty = quantifier === "*";
|
|
121
|
-
let quantifierEnd = quantifierStart;
|
|
122
|
-
if (quantifier === "{") {
|
|
123
|
-
const match = /^\{(\d+),\}/.exec(pattern.slice(quantifierStart));
|
|
124
|
-
if (match) {
|
|
125
|
-
unbounded = true;
|
|
126
|
-
canBeEmpty = Number(match[1]) === 0;
|
|
127
|
-
quantifierEnd += match[0].length - 1;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (!unbounded) {
|
|
131
|
-
if (quantifier !== "?") repeatedAtDepth[depth] = [];
|
|
132
|
-
if (quantifier === "?") index = quantifierStart;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (repeatedAtDepth[depth].some((previous) => atomsOverlap(previous, atom))) return true;
|
|
136
|
-
repeatedAtDepth[depth] = canBeEmpty ? [...repeatedAtDepth[depth], atom] : [atom];
|
|
137
|
-
index = quantifierEnd;
|
|
138
|
-
}
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
function unsafeTokenReason(token, namedGroups) {
|
|
142
|
-
const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
|
|
143
|
-
ignoreCase: true,
|
|
144
|
-
namedGroups
|
|
145
|
-
});
|
|
146
|
-
if (regexSafetyIssue) {
|
|
147
|
-
if (regexSafetyIssue === "analysis budget exceeded") return `parameter "${token.name}" exceeds the regex analysis budget`;
|
|
148
|
-
return `parameter "${token.name}" contains ${regexSafetyIssue}`;
|
|
149
|
-
}
|
|
150
|
-
if (hasOverlappingSequentialRepetition(token.pattern)) return `parameter "${token.name}" contains overlapping sequential repetition`;
|
|
151
|
-
if (token.modifier !== "*" && token.modifier !== "+") return null;
|
|
152
|
-
if (patternMatches(token.pattern, "") || patternMatches(token.pattern, "/")) return `repeated parameter "${token.name}" may match an empty value or path delimiter`;
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
function validateTokens(tokens) {
|
|
156
|
-
const namedGroups = tokens.some((token) => typeof token !== "string" && mayHaveNamedGroups(token.pattern));
|
|
157
|
-
for (const token of tokens) {
|
|
158
|
-
if (typeof token === "string") continue;
|
|
159
|
-
const reason = unsafeTokenReason(token, namedGroups);
|
|
160
|
-
if (reason) return reason;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
54
|
+
function middlewareRegExp(tokens) {
|
|
55
|
+
return new RegExp(`${pathTokensToRegExpSource(tokens)}[\\/#\\?]?$`);
|
|
163
56
|
}
|
|
164
57
|
function compileMiddlewareMatcherPattern(source) {
|
|
165
|
-
if (!source.startsWith("/")) return {
|
|
166
|
-
|
|
167
|
-
error: "source must start with /"
|
|
168
|
-
};
|
|
169
|
-
if (source.length > 4096) return {
|
|
170
|
-
kind: "invalid",
|
|
171
|
-
error: "source exceeds max built length of 4096"
|
|
172
|
-
};
|
|
58
|
+
if (!source.startsWith("/")) return { error: "source must start with /" };
|
|
59
|
+
if (source.length > 4096) return { error: "source exceeds max built length of 4096" };
|
|
173
60
|
let tokens;
|
|
174
61
|
try {
|
|
175
|
-
tokens =
|
|
62
|
+
tokens = parsePathPattern(source);
|
|
176
63
|
} catch (error) {
|
|
177
|
-
return {
|
|
178
|
-
kind: "invalid",
|
|
179
|
-
error: error instanceof Error ? error.message : "matcher could not be parsed"
|
|
180
|
-
};
|
|
64
|
+
return { error: error instanceof Error ? error.message : "matcher could not be parsed" };
|
|
181
65
|
}
|
|
182
|
-
const unsafeReason = validateTokens(tokens);
|
|
183
|
-
if (unsafeReason) return {
|
|
184
|
-
kind: "unsafe",
|
|
185
|
-
error: unsafeReason
|
|
186
|
-
};
|
|
187
66
|
try {
|
|
188
|
-
return { regexp:
|
|
67
|
+
return { regexp: middlewareRegExp(tokens) };
|
|
189
68
|
} catch {
|
|
190
|
-
const normalizedTokens =
|
|
191
|
-
const normalizedUnsafeReason = validateTokens(normalizedTokens);
|
|
192
|
-
if (normalizedUnsafeReason) return {
|
|
193
|
-
kind: "unsafe",
|
|
194
|
-
error: normalizedUnsafeReason
|
|
195
|
-
};
|
|
69
|
+
const normalizedTokens = normalizePathTokens(tokens);
|
|
196
70
|
try {
|
|
197
|
-
return { regexp:
|
|
71
|
+
return { regexp: middlewareRegExp(normalizedTokens) };
|
|
198
72
|
} catch (error) {
|
|
199
|
-
return {
|
|
200
|
-
kind: "invalid",
|
|
201
|
-
error: error instanceof Error ? error.message : "matcher could not be compiled"
|
|
202
|
-
};
|
|
73
|
+
return { error: error instanceof Error ? error.message : "matcher could not be compiled" };
|
|
203
74
|
}
|
|
204
75
|
}
|
|
205
76
|
}
|
|
@@ -8,7 +8,7 @@ const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
|
8
8
|
query: new URLSearchParams(),
|
|
9
9
|
host: ""
|
|
10
10
|
};
|
|
11
|
-
const
|
|
11
|
+
const INVALID_MATCHER_PATTERN = Symbol("invalid matcher pattern");
|
|
12
12
|
const _mwPatternCache = /* @__PURE__ */ new Map();
|
|
13
13
|
function matchesMiddleware(pathname, matcher, request, i18nConfig, localeContext) {
|
|
14
14
|
if (!matcher) return true;
|
|
@@ -63,7 +63,7 @@ function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
|
|
|
63
63
|
return matchPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
|
|
64
64
|
}
|
|
65
65
|
function matchNextDataPattern(pathname, source, i18nConfig, localized) {
|
|
66
|
-
if (compiledPattern(source) ===
|
|
66
|
+
if (compiledPattern(source) === INVALID_MATCHER_PATTERN) return true;
|
|
67
67
|
return matchPattern(pathname, nextDataMatcherSource(source, i18nConfig, localized));
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -89,7 +89,7 @@ function stripFirstPathSegment(pathname) {
|
|
|
89
89
|
function matchPattern(pathname, pattern) {
|
|
90
90
|
if (pattern === "/" && (pathname === "//" || pathname === "/?" || pathname === "/#")) return false;
|
|
91
91
|
const compiled = compiledPattern(pattern);
|
|
92
|
-
if (compiled ===
|
|
92
|
+
if (compiled === INVALID_MATCHER_PATTERN) return true;
|
|
93
93
|
return compiled.test(pathname);
|
|
94
94
|
}
|
|
95
95
|
function compiledPattern(pattern) {
|
|
@@ -103,9 +103,8 @@ function compiledPattern(pattern) {
|
|
|
103
103
|
function compileMatcherPattern(pattern) {
|
|
104
104
|
const result = compileMiddlewareMatcherPattern(pattern);
|
|
105
105
|
if (result.regexp) return result.regexp;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return UNSAFE_MATCHER_PATTERN;
|
|
106
|
+
console.warn(`[vinext] Rejecting invalid middleware matcher: ${pattern}\n ${result.error}.\n Middleware will run for all paths to avoid bypassing request guards.`);
|
|
107
|
+
return INVALID_MATCHER_PATTERN;
|
|
109
108
|
}
|
|
110
109
|
//#endregion
|
|
111
110
|
export { matchPattern, matchesMiddleware, matchesMiddlewarePathname };
|
|
@@ -8,4 +8,35 @@ export declare function mergeVaryHeader(target: Headers, value: string): void;
|
|
|
8
8
|
* response headers like Cache-Control.
|
|
9
9
|
*/
|
|
10
10
|
export declare function mergeMiddlewareResponseHeaders(target: Headers, middlewareHeaders: Headers | null): void;
|
|
11
|
+
export type MiddlewareResponseMergeOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* Keep the response's WWW-Authenticate and Proxy-Authenticate after
|
|
14
|
+
* middleware's, as Next.js does for App route handlers. Pages API handlers
|
|
15
|
+
* write to the Node response that already holds middleware's headers, so
|
|
16
|
+
* their callers opt out.
|
|
17
|
+
*/
|
|
18
|
+
appendResponseChallenges?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Merge middleware response headers onto an already-built response's headers.
|
|
22
|
+
*
|
|
23
|
+
* Next.js writes middleware response headers before the handler runs and then
|
|
24
|
+
* appends the handler response's Set-Cookie, WWW-Authenticate and
|
|
25
|
+
* Proxy-Authenticate values, so middleware values come first and the
|
|
26
|
+
* response's own are kept. A handler's cookie for the same name (a logout
|
|
27
|
+
* deletion or session rotation) is then the one the browser applies last.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeMiddlewareResponseHeadersOntoResponse(target: Headers, middlewareHeaders: Headers | null, options?: MiddlewareResponseMergeOptions): void;
|
|
30
|
+
/**
|
|
31
|
+
* Build the response for an external rewrite from the headers routing staged
|
|
32
|
+
* before proxying.
|
|
33
|
+
*
|
|
34
|
+
* Next.js writes the staged config and middleware headers to its outgoing
|
|
35
|
+
* response and then proxies, and the proxy sets each upstream header over
|
|
36
|
+
* them. Its Set-Cookie patch keeps middleware's cookies ahead of the
|
|
37
|
+
* upstream's, so an upstream cookie replaces a staged config cookie while
|
|
38
|
+
* middleware's are kept. Without an upstream Set-Cookie the staged cookies
|
|
39
|
+
* stay as they are.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withExternalRewriteResponseHeaders(upstream: Response, staged: Headers, middlewareCookies: readonly string[]): Response;
|
|
11
42
|
//#endregion
|
|
@@ -42,5 +42,61 @@ function mergeMiddlewareResponseHeaders(target, middlewareHeaders) {
|
|
|
42
42
|
target.set(key, value);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
const RESPONSE_APPENDED_HEADER_NAMES = [
|
|
46
|
+
"set-cookie",
|
|
47
|
+
"www-authenticate",
|
|
48
|
+
"proxy-authenticate"
|
|
49
|
+
];
|
|
50
|
+
const RESPONSE_APPENDED_COOKIE_HEADER_NAMES = ["set-cookie"];
|
|
51
|
+
/**
|
|
52
|
+
* Merge middleware response headers onto an already-built response's headers.
|
|
53
|
+
*
|
|
54
|
+
* Next.js writes middleware response headers before the handler runs and then
|
|
55
|
+
* appends the handler response's Set-Cookie, WWW-Authenticate and
|
|
56
|
+
* Proxy-Authenticate values, so middleware values come first and the
|
|
57
|
+
* response's own are kept. A handler's cookie for the same name (a logout
|
|
58
|
+
* deletion or session rotation) is then the one the browser applies last.
|
|
59
|
+
*/
|
|
60
|
+
function mergeMiddlewareResponseHeadersOntoResponse(target, middlewareHeaders, options = {}) {
|
|
61
|
+
if (!middlewareHeaders) return;
|
|
62
|
+
const responseValues = [];
|
|
63
|
+
const appendedNames = options.appendResponseChallenges === false ? RESPONSE_APPENDED_COOKIE_HEADER_NAMES : RESPONSE_APPENDED_HEADER_NAMES;
|
|
64
|
+
for (const name of appendedNames) {
|
|
65
|
+
if (!middlewareHeaders.has(name)) continue;
|
|
66
|
+
const value = target.get(name);
|
|
67
|
+
responseValues.push([name, name === "set-cookie" ? target.getSetCookie() : value === null ? [] : [value]]);
|
|
68
|
+
target.delete(name);
|
|
69
|
+
}
|
|
70
|
+
mergeMiddlewareResponseHeaders(target, middlewareHeaders);
|
|
71
|
+
for (const [name, values] of responseValues) {
|
|
72
|
+
if (!middlewareHeaders.get(name)) target.delete(name);
|
|
73
|
+
for (const value of values) target.append(name, value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the response for an external rewrite from the headers routing staged
|
|
78
|
+
* before proxying.
|
|
79
|
+
*
|
|
80
|
+
* Next.js writes the staged config and middleware headers to its outgoing
|
|
81
|
+
* response and then proxies, and the proxy sets each upstream header over
|
|
82
|
+
* them. Its Set-Cookie patch keeps middleware's cookies ahead of the
|
|
83
|
+
* upstream's, so an upstream cookie replaces a staged config cookie while
|
|
84
|
+
* middleware's are kept. Without an upstream Set-Cookie the staged cookies
|
|
85
|
+
* stay as they are.
|
|
86
|
+
*/
|
|
87
|
+
function withExternalRewriteResponseHeaders(upstream, staged, middlewareCookies) {
|
|
88
|
+
staged.delete("content-length");
|
|
89
|
+
for (const name of new Set(upstream.headers.keys())) if (name !== "set-cookie") staged.set(name, upstream.headers.get(name) ?? "");
|
|
90
|
+
const upstreamCookies = upstream.headers.getSetCookie();
|
|
91
|
+
if (upstreamCookies.length > 0) {
|
|
92
|
+
staged.delete("set-cookie");
|
|
93
|
+
for (const cookie of /* @__PURE__ */ new Set([...middlewareCookies, ...upstreamCookies])) staged.append("set-cookie", cookie);
|
|
94
|
+
}
|
|
95
|
+
return new Response(upstream.body, {
|
|
96
|
+
status: upstream.status,
|
|
97
|
+
statusText: upstream.statusText,
|
|
98
|
+
headers: staged
|
|
99
|
+
});
|
|
100
|
+
}
|
|
45
101
|
//#endregion
|
|
46
|
-
export { mergeMiddlewareResponseHeaders, mergeVaryHeader };
|
|
102
|
+
export { mergeMiddlewareResponseHeaders, mergeMiddlewareResponseHeadersOntoResponse, mergeVaryHeader, withExternalRewriteResponseHeaders };
|
|
@@ -2,18 +2,18 @@ import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
|
|
|
2
2
|
import "../utils/protocol-headers.js";
|
|
3
3
|
import { shouldKeepMiddlewareHeader } from "../utils/middleware-request-headers.js";
|
|
4
4
|
import { normalizeHost } from "../config/request-context.js";
|
|
5
|
-
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
6
5
|
import { addBasePathToPathname, hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
|
|
6
|
+
import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
|
|
7
|
+
import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
|
|
8
|
+
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
9
|
+
import { dropEmptyMiddlewareSetCookie, processMiddlewareHeaders } from "./request-pipeline.js";
|
|
10
|
+
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
7
11
|
import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
|
|
8
12
|
import "./server-globals.js";
|
|
9
|
-
import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
|
|
10
13
|
import { NextFetchEvent, NextRequest } from "../shims/server.js";
|
|
11
14
|
import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
|
|
12
15
|
import { normalizePath } from "./normalize-path.js";
|
|
13
16
|
import { matchesMiddleware, matchesMiddlewarePathname } from "./middleware-matcher.js";
|
|
14
|
-
import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
|
|
15
|
-
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
16
|
-
import { processMiddlewareHeaders } from "./request-pipeline.js";
|
|
17
17
|
import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
18
18
|
import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
|
|
19
19
|
import { reportRequestError } from "./instrumentation.js";
|
|
@@ -118,6 +118,7 @@ function dataRedirectResponse(target, originalResponse) {
|
|
|
118
118
|
function collectMiddlewareHeaders(response) {
|
|
119
119
|
const responseHeaders = new Headers();
|
|
120
120
|
for (const [key, value] of response.headers) if (!key.startsWith("x-middleware-") || shouldKeepMiddlewareHeader(key)) responseHeaders.append(key, value);
|
|
121
|
+
dropEmptyMiddlewareSetCookie(responseHeaders);
|
|
121
122
|
return responseHeaders;
|
|
122
123
|
}
|
|
123
124
|
function drainFetchEvent(fetchEvent) {
|
|
@@ -135,12 +136,19 @@ function resolveMiddlewarePathname(request) {
|
|
|
135
136
|
return badRequestResponse();
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
/** The same path and query on another origin. */
|
|
140
|
+
function withOrigin(url, origin) {
|
|
141
|
+
const result = new URL(origin);
|
|
142
|
+
if (result.origin === url.origin) return url;
|
|
143
|
+
result.pathname = url.pathname;
|
|
144
|
+
result.search = url.search;
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function createNextRequest(request, url, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false, skipTrailingSlashRedirect = false) {
|
|
140
148
|
let mwRequest = !requestBodyAlreadyIsolated && request.body && !request.bodyUsed ? request.clone() : request;
|
|
141
149
|
const requestPathname = url.pathname;
|
|
142
150
|
const mwPathname = basePath && hadBasePath && !hasBasePath(requestPathname, basePath) ? addBasePathToPathname(requestPathname, basePath) : requestPathname;
|
|
143
|
-
if (mwPathname !== url.pathname) {
|
|
151
|
+
if (mwPathname !== url.pathname || url.href !== request.url) {
|
|
144
152
|
const mwUrl = new URL(url);
|
|
145
153
|
mwUrl.pathname = mwPathname;
|
|
146
154
|
mwRequest = new Request(mwUrl, mwRequest);
|
|
@@ -151,7 +159,12 @@ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBase
|
|
|
151
159
|
trailingSlash: trailingSlash ?? void 0,
|
|
152
160
|
skipTrailingSlashRedirect
|
|
153
161
|
} : void 0;
|
|
154
|
-
const
|
|
162
|
+
const requestUrl = new URL(request.url);
|
|
163
|
+
const requestHost = requestUrl.origin === url.origin ? void 0 : requestUrl.host;
|
|
164
|
+
const nextRequest = mwRequest instanceof NextRequest && (!mwRequest.body || mwRequest.bodyUsed) ? mwRequest : new NextRequest(mwRequest, nextConfig ? {
|
|
165
|
+
nextConfig,
|
|
166
|
+
requestHost
|
|
167
|
+
} : void 0);
|
|
155
168
|
if (mwRequest !== request && mwRequest.body && !mwRequest.bodyUsed && !mwRequest.body.locked) mwRequest.body.cancel().catch(() => {});
|
|
156
169
|
return nextRequest;
|
|
157
170
|
}
|
|
@@ -219,7 +232,8 @@ async function executeMiddlewareModule(options) {
|
|
|
219
232
|
const decodedMatches = !encodedMatches && decodedMatchPathname !== null && decodedMatchPathname !== encodedMatchPathname && matchesMiddleware(decodedMatchPathname, matcher, options.request, options.i18nConfig, localeContext);
|
|
220
233
|
if (!encodedMatches && !decodedMatches) return { continue: true };
|
|
221
234
|
options.onMatch?.();
|
|
222
|
-
const
|
|
235
|
+
const middlewareUrl = withOrigin(requestUrl, getRequestExecutionContext()?.middlewareOrigin ?? requestUrl.origin);
|
|
236
|
+
const nextRequest = createNextRequest(options.request, middlewareUrl, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
|
|
223
237
|
if (options.isDataRequest) Object.defineProperty(nextRequest, "__isData", {
|
|
224
238
|
enumerable: false,
|
|
225
239
|
value: true
|
|
@@ -260,11 +274,11 @@ async function executeMiddlewareModule(options) {
|
|
|
260
274
|
if (response.status >= 300 && response.status < 400) {
|
|
261
275
|
const location = response.headers.get("Location") ?? response.headers.get("location");
|
|
262
276
|
if (location) {
|
|
263
|
-
const relativeLocation = relativizeLocation(location,
|
|
277
|
+
const relativeLocation = relativizeLocation(location, middlewareUrl.href);
|
|
264
278
|
let normalizedLocation = relativeLocation;
|
|
265
279
|
if (options.trailingSlash !== void 0 && !options.skipTrailingSlashRedirect) try {
|
|
266
|
-
const loc = new URL(relativeLocation,
|
|
267
|
-
if (loc.origin ===
|
|
280
|
+
const loc = new URL(relativeLocation, middlewareUrl);
|
|
281
|
+
if (loc.origin === middlewareUrl.origin) {
|
|
268
282
|
const p = loc.pathname;
|
|
269
283
|
let normalized = null;
|
|
270
284
|
if (p !== "" && p !== "/") {
|
|
@@ -273,7 +287,7 @@ async function executeMiddlewareModule(options) {
|
|
|
273
287
|
}
|
|
274
288
|
if (normalized !== null) {
|
|
275
289
|
loc.pathname = normalized;
|
|
276
|
-
normalizedLocation = relativizeLocation(loc.toString(),
|
|
290
|
+
normalizedLocation = relativizeLocation(loc.toString(), middlewareUrl.href);
|
|
277
291
|
}
|
|
278
292
|
}
|
|
279
293
|
} catch {}
|
|
@@ -311,9 +325,8 @@ async function executeMiddlewareModule(options) {
|
|
|
311
325
|
let rewritePath;
|
|
312
326
|
let rewriteHadBasePath;
|
|
313
327
|
try {
|
|
314
|
-
const rewriteParsed = new URL(rewriteUrl,
|
|
315
|
-
|
|
316
|
-
if (rewriteParsed.origin === requestOrigin) {
|
|
328
|
+
const rewriteParsed = new URL(rewriteUrl, middlewareUrl);
|
|
329
|
+
if (rewriteParsed.origin === middlewareUrl.origin) {
|
|
317
330
|
rewriteHadBasePath = !options.basePath || hasBasePath(rewriteParsed.pathname, options.basePath);
|
|
318
331
|
rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
|
|
319
332
|
} else rewritePath = rewriteParsed.href;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BrowserRouteManifest } from "../routing/browser-route-manifest.js";
|
|
2
2
|
import { CacheEntryReuseDecision, CacheEntryReuseProof } from "./cache-proof.js";
|
|
3
3
|
import { AppElementsSlotBinding } from "./app-elements-wire.js";
|
|
4
4
|
import "./app-elements.js";
|
|
@@ -230,7 +230,7 @@ type NavigationPrefetchProbeDecision = {
|
|
|
230
230
|
reason: "cacheBypassed" | "refresh" | "visitedResponseAvailable";
|
|
231
231
|
};
|
|
232
232
|
export type NavigationPlannerInput = {
|
|
233
|
-
routeManifest:
|
|
233
|
+
routeManifest: BrowserRouteManifest | null;
|
|
234
234
|
state: NavigationPlannerState;
|
|
235
235
|
event: NavigationEvent;
|
|
236
236
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
1
|
import { stripBasePath } from "../utils/base-path.js";
|
|
2
|
+
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
3
3
|
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
4
4
|
import { AppElementsWire, compareAppElementsSlotIds } from "./app-elements-wire.js";
|
|
5
5
|
import "./app-elements.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/server/nitro-public-files-plugin.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Nitro runtime plugin that lets vinext serve the files in `public/`.
|
|
4
|
+
*
|
|
5
|
+
* Nitro's static handler answers a request for a public file before any app
|
|
6
|
+
* handler runs, so middleware would never see it. The plugin sends those
|
|
7
|
+
* requests to vinext instead. vinext's router then decides which public file
|
|
8
|
+
* a request resolves to, for example the destination of a rewrite, but only
|
|
9
|
+
* the static handler can read the file from the server output. The plugin
|
|
10
|
+
* publishes an asset fetcher that sends a request back through the Nitro app,
|
|
11
|
+
* where the static handler answers it.
|
|
12
|
+
*
|
|
13
|
+
* This module runs in Nitro's server bundle, separate from vinext's request
|
|
14
|
+
* handlers, so it imports nothing and shares state through `globalThis`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const NITRO_ASSETS_KEY = "vinext.nitro.assets";
|
|
17
|
+
type NitroApp = {
|
|
18
|
+
fetch: (request: Request) => Response | Promise<Response>;
|
|
19
|
+
captureError?: (error: unknown, context: {
|
|
20
|
+
tags?: string[];
|
|
21
|
+
}) => void;
|
|
22
|
+
};
|
|
23
|
+
export type NitroAssets = {
|
|
24
|
+
fetch(request: Request): Promise<Response>;
|
|
25
|
+
/** Whether a request is an asset fetch that Nitro's static handler did not answer. */
|
|
26
|
+
isAssetRequest(request: Request): boolean;
|
|
27
|
+
};
|
|
28
|
+
type NitroPublicFilesPluginOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* The static files Nitro serves for the files in `public/`, including
|
|
31
|
+
* their compressed copies, keyed as Nitro's static handler keys them.
|
|
32
|
+
*/
|
|
33
|
+
publicAssets: string[];
|
|
34
|
+
/** Sends a request to vinext's request handler. */
|
|
35
|
+
fetchApp(request: Request): Promise<Response>;
|
|
36
|
+
};
|
|
37
|
+
export declare function createNitroPublicFilesPlugin(options: NitroPublicFilesPluginOptions): (nitroApp: NitroApp) => void;
|
|
38
|
+
//#endregion
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//#region src/server/nitro-public-files-plugin.ts
|
|
2
|
+
/**
|
|
3
|
+
* Nitro runtime plugin that lets vinext serve the files in `public/`.
|
|
4
|
+
*
|
|
5
|
+
* Nitro's static handler answers a request for a public file before any app
|
|
6
|
+
* handler runs, so middleware would never see it. The plugin sends those
|
|
7
|
+
* requests to vinext instead. vinext's router then decides which public file
|
|
8
|
+
* a request resolves to, for example the destination of a rewrite, but only
|
|
9
|
+
* the static handler can read the file from the server output. The plugin
|
|
10
|
+
* publishes an asset fetcher that sends a request back through the Nitro app,
|
|
11
|
+
* where the static handler answers it.
|
|
12
|
+
*
|
|
13
|
+
* This module runs in Nitro's server bundle, separate from vinext's request
|
|
14
|
+
* handlers, so it imports nothing and shares state through `globalThis`.
|
|
15
|
+
*/
|
|
16
|
+
const NITRO_ASSETS_KEY = "vinext.nitro.assets";
|
|
17
|
+
const ASSET_REQUEST_HEADER = "x-vinext-nitro-asset";
|
|
18
|
+
const STATIC_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
19
|
+
const ENCODING_EXTENSIONS = /* @__PURE__ */ new Map([
|
|
20
|
+
["br", ".br"],
|
|
21
|
+
["gzip", ".gz"],
|
|
22
|
+
["zstd", ".zst"]
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* The pathnames h3 can give Nitro's static handler for a request URL. srvx
|
|
26
|
+
* usually hands h3 the URL's path up to the query as it is, so a literal `#`
|
|
27
|
+
* stays in it, but parses the URL fully when the path needs normalizing.
|
|
28
|
+
*/
|
|
29
|
+
function getRequestPathnames(url) {
|
|
30
|
+
const parsed = new URL(url).pathname;
|
|
31
|
+
const start = url.indexOf("/", url.indexOf("://") + 3);
|
|
32
|
+
if (start === -1) return [parsed];
|
|
33
|
+
const query = url.indexOf("?", start);
|
|
34
|
+
const raw = url.slice(start, query === -1 ? void 0 : query);
|
|
35
|
+
return raw === parsed ? [parsed] : [parsed, raw];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The pathname h3 2.0.1-rc.22, which the pinned Nitro uses, gives Nitro's
|
|
39
|
+
* static handler. When it creates the request event, it decodes a pathname
|
|
40
|
+
* that contains `%` except for `%25`, and the URL parser then resolves the
|
|
41
|
+
* result again, for example `\` and `..` segments. Returns `undefined` when
|
|
42
|
+
* h3 fails the request instead. Newer h3 releases leave decoding to Nitro, so
|
|
43
|
+
* the plugin also looks up the pathname without this step.
|
|
44
|
+
*/
|
|
45
|
+
function toH3Pathname(pathname) {
|
|
46
|
+
if (!pathname.includes("%")) return pathname;
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL("http://localhost");
|
|
49
|
+
url.pathname = decodeURI(pathname.replace(/%25/g, "%2525"));
|
|
50
|
+
return url.pathname;
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether Nitro's static handler would answer a request for a pathname with
|
|
57
|
+
* a public file, following its lookup in `runtime/internal/static.mjs`. It
|
|
58
|
+
* looks h3's pathname up without one trailing slash and decoded except for
|
|
59
|
+
* `%2F`, and then as a directory with an `index.html`, each first with the
|
|
60
|
+
* extension of a compressed copy for every encoding the request accepts.
|
|
61
|
+
*/
|
|
62
|
+
function isPublicFilePathname(pathname, extensions, publicAssets) {
|
|
63
|
+
let id = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
64
|
+
try {
|
|
65
|
+
id = decodeURIComponent(id.replace(/%2f/gi, "%252F"));
|
|
66
|
+
} catch {}
|
|
67
|
+
const index = id.endsWith("/") ? `${id}index.html` : `${id}/index.html`;
|
|
68
|
+
return extensions.some((extension) => publicAssets.has(id + extension) || publicAssets.has(index + extension));
|
|
69
|
+
}
|
|
70
|
+
function isPublicFileRequest(request, publicAssets) {
|
|
71
|
+
if (!STATIC_METHODS.has(request.method)) return false;
|
|
72
|
+
const extensions = [""];
|
|
73
|
+
for (const encoding of request.headers.get("accept-encoding")?.split(",") ?? []) {
|
|
74
|
+
const [name, ...params] = encoding.split(";");
|
|
75
|
+
const quality = params.map((param) => param.trim().split("=")).find(([key]) => key.toLowerCase() === "q")?.[1];
|
|
76
|
+
if ((quality === void 0 ? 1 : Number.parseFloat(quality)) <= 0) continue;
|
|
77
|
+
const extension = ENCODING_EXTENSIONS.get(name.trim().toLowerCase());
|
|
78
|
+
if (extension) extensions.push(extension);
|
|
79
|
+
}
|
|
80
|
+
return getRequestPathnames(request.url).some((pathname) => [pathname, toH3Pathname(pathname)].some((candidate) => candidate !== void 0 && isPublicFilePathname(candidate, extensions, publicAssets)));
|
|
81
|
+
}
|
|
82
|
+
function createNitroPublicFilesPlugin(options) {
|
|
83
|
+
const publicAssets = new Set(options.publicAssets);
|
|
84
|
+
return (nitroApp) => {
|
|
85
|
+
const appFetch = nitroApp.fetch;
|
|
86
|
+
const token = crypto.randomUUID();
|
|
87
|
+
Reflect.set(globalThis, Symbol.for(NITRO_ASSETS_KEY), {
|
|
88
|
+
async fetch(request) {
|
|
89
|
+
const headers = new Headers(request.headers);
|
|
90
|
+
headers.set(ASSET_REQUEST_HEADER, token);
|
|
91
|
+
return appFetch(new Request(request, { headers }));
|
|
92
|
+
},
|
|
93
|
+
isAssetRequest: (request) => request.headers.get(ASSET_REQUEST_HEADER) === token
|
|
94
|
+
});
|
|
95
|
+
if (publicAssets.size > 0) nitroApp.fetch = (request) => {
|
|
96
|
+
if (!isPublicFileRequest(request, publicAssets)) return appFetch(request);
|
|
97
|
+
return Promise.resolve().then(() => options.fetchApp(request)).catch((error) => {
|
|
98
|
+
console.error("[vinext]", error);
|
|
99
|
+
nitroApp.captureError?.(error, { tags: ["vinext"] });
|
|
100
|
+
return new Response(null, { status: 500 });
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { NITRO_ASSETS_KEY, createNitroPublicFilesPlugin };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { VinextAssetFetcher } from "./multi-stage.js";
|
|
2
|
+
//#region src/server/nitro-public-files.d.ts
|
|
3
|
+
type NitroFetchEntry = {
|
|
4
|
+
fetch(request: Request): Promise<Response>;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Wrap the App Router SSR service that Nitro dispatches requests to, so a
|
|
8
|
+
* public file the router resolves is served by Nitro's static handler.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withNitroPublicFiles(entry: NitroFetchEntry): NitroFetchEntry;
|
|
11
|
+
type PagesWorkerEntry<Env, Context extends object> = {
|
|
12
|
+
fetch(request: Request, env: Env, ctx: Context & {
|
|
13
|
+
assets?: VinextAssetFetcher;
|
|
14
|
+
}): unknown;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Give the Pages Router worker that Nitro dispatches requests to an asset
|
|
18
|
+
* fetcher that reads files through Nitro's static handler.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withNitroPagesPublicFiles<Env, Context extends object>(worker: PagesWorkerEntry<Env, Context>): PagesWorkerEntry<Env, Context>;
|
|
21
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createStaticAssetRequest, resolveStaticAssetSignal } from "./worker-utils.js";
|
|
2
|
+
import { NITRO_ASSETS_KEY } from "./nitro-public-files-plugin.js";
|
|
3
|
+
//#region src/server/nitro-public-files.ts
|
|
4
|
+
function getNitroAssets() {
|
|
5
|
+
const assets = Reflect.get(globalThis, Symbol.for(NITRO_ASSETS_KEY));
|
|
6
|
+
if (!assets) throw new Error("[vinext] The Nitro public files plugin did not run.");
|
|
7
|
+
return assets;
|
|
8
|
+
}
|
|
9
|
+
function notFound() {
|
|
10
|
+
return new Response(null, { status: 404 });
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wrap the App Router SSR service that Nitro dispatches requests to, so a
|
|
14
|
+
* public file the router resolves is served by Nitro's static handler.
|
|
15
|
+
*/
|
|
16
|
+
function withNitroPublicFiles(entry) {
|
|
17
|
+
return { async fetch(request) {
|
|
18
|
+
const assets = getNitroAssets();
|
|
19
|
+
if (assets.isAssetRequest(request)) return notFound();
|
|
20
|
+
const response = await entry.fetch(request);
|
|
21
|
+
return await resolveStaticAssetSignal(response, { fetchAsset: (path, requestHeaders) => assets.fetch(createStaticAssetRequest(path, request, requestHeaders)) }) ?? response;
|
|
22
|
+
} };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Give the Pages Router worker that Nitro dispatches requests to an asset
|
|
26
|
+
* fetcher that reads files through Nitro's static handler.
|
|
27
|
+
*/
|
|
28
|
+
function withNitroPagesPublicFiles(worker) {
|
|
29
|
+
return { fetch(request, env, ctx) {
|
|
30
|
+
const assets = getNitroAssets();
|
|
31
|
+
if (assets.isAssetRequest(request)) return notFound();
|
|
32
|
+
return worker.fetch(request, env, {
|
|
33
|
+
...ctx,
|
|
34
|
+
assets
|
|
35
|
+
});
|
|
36
|
+
} };
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { withNitroPagesPublicFiles, withNitroPublicFiles };
|