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.
Files changed (283) hide show
  1. package/README.md +13 -10
  2. package/dist/build/client-build-config.d.ts +64 -3
  3. package/dist/build/client-build-config.js +131 -4
  4. package/dist/build/client-reference-groups.d.ts +163 -0
  5. package/dist/build/client-reference-groups.js +534 -0
  6. package/dist/build/module-dependency-cache.d.ts +31 -0
  7. package/dist/build/module-dependency-cache.js +51 -1
  8. package/dist/build/next-client-runtime-manifests.d.ts +10 -1
  9. package/dist/build/next-client-runtime-manifests.js +19 -4
  10. package/dist/build/nitro-public-files.d.ts +24 -0
  11. package/dist/build/nitro-public-files.js +60 -0
  12. package/dist/build/output-root-externals.d.ts +21 -0
  13. package/dist/build/output-root-externals.js +40 -0
  14. package/dist/build/pages-client-assets-module.js +1 -1
  15. package/dist/build/prerender-paths.js +6 -3
  16. package/dist/build/prerender.js +1 -1
  17. package/dist/build/server-actions-encryption-key.d.ts +23 -0
  18. package/dist/build/server-actions-encryption-key.js +101 -0
  19. package/dist/build/ssr-manifest.d.ts +3 -0
  20. package/dist/build/ssr-manifest.js +10 -1
  21. package/dist/check.js +5 -6
  22. package/dist/cli-dev-config.js +3 -3
  23. package/dist/cli.js +1 -1
  24. package/dist/client/client-rewrite-params.d.ts +9 -0
  25. package/dist/client/client-rewrite-params.js +20 -0
  26. package/dist/client/dev-error-overlay.js +2 -1
  27. package/dist/client/navigation-runtime.d.ts +25 -2
  28. package/dist/client/navigation-runtime.js +42 -11
  29. package/dist/client/vinext-next-data.d.ts +0 -1
  30. package/dist/config/config-matchers.d.ts +15 -40
  31. package/dist/config/config-matchers.js +195 -262
  32. package/dist/config/config-source.d.ts +34 -0
  33. package/dist/config/config-source.js +141 -0
  34. package/dist/config/header-params.d.ts +29 -0
  35. package/dist/config/header-params.js +106 -0
  36. package/dist/config/next-config.d.ts +7 -5
  37. package/dist/config/next-config.js +120 -7
  38. package/dist/entries/app-browser-entry.d.ts +14 -1
  39. package/dist/entries/app-browser-entry.js +26 -36
  40. package/dist/entries/app-rsc-entry.d.ts +2 -0
  41. package/dist/entries/app-rsc-entry.js +92 -57
  42. package/dist/entries/app-rsc-manifest.d.ts +10 -0
  43. package/dist/entries/app-rsc-manifest.js +20 -1
  44. package/dist/entries/app-ssr-entry.d.ts +4 -1
  45. package/dist/entries/app-ssr-entry.js +9 -2
  46. package/dist/entries/pages-client-entry.d.ts +1 -0
  47. package/dist/entries/pages-client-entry.js +6 -6
  48. package/dist/entries/pages-server-entry.js +17 -14
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.js +350 -175
  51. package/dist/init.d.ts +1 -1
  52. package/dist/init.js +103 -6
  53. package/dist/plugins/ast-utils.d.ts +53 -2
  54. package/dist/plugins/ast-utils.js +90 -2
  55. package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
  56. package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
  57. package/dist/plugins/commonjs-esm-facade.js +2 -2
  58. package/dist/plugins/css-module-imports.js +13 -20
  59. package/dist/plugins/dynamic-preload-metadata.js +2 -3
  60. package/dist/plugins/extensionless-dynamic-import.js +2 -3
  61. package/dist/plugins/fonts.d.ts +1 -1
  62. package/dist/plugins/fonts.js +3 -3
  63. package/dist/plugins/ignore-dynamic-requests.js +2 -3
  64. package/dist/plugins/import-meta-url.js +3 -4
  65. package/dist/plugins/instrumentation-client.js +3 -2
  66. package/dist/plugins/middleware-export-validation.js +2 -3
  67. package/dist/plugins/og-harfbuzz.js +2 -3
  68. package/dist/plugins/optimize-imports.d.ts +25 -2
  69. package/dist/plugins/optimize-imports.js +109 -24
  70. package/dist/plugins/pages-node-externals.js +2 -3
  71. package/dist/plugins/remove-console.js +2 -18
  72. package/dist/plugins/require-condition-resolution.js +3 -3
  73. package/dist/plugins/require-context.js +2 -3
  74. package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
  75. package/dist/plugins/rsc-client-reference-loaders.js +336 -18
  76. package/dist/plugins/strip-server-exports.js +3 -4
  77. package/dist/plugins/styled-jsx.js +2 -3
  78. package/dist/plugins/use-cache-callable.d.ts +26 -2
  79. package/dist/plugins/use-cache-callable.js +101 -36
  80. package/dist/plugins/worker-image-imports.d.ts +1 -0
  81. package/dist/plugins/worker-image-imports.js +31 -34
  82. package/dist/plugins/worker-use-server.js +38 -14
  83. package/dist/routing/app-route-graph.d.ts +1 -1
  84. package/dist/routing/browser-route-manifest.d.ts +43 -0
  85. package/dist/routing/browser-route-manifest.js +195 -0
  86. package/dist/routing/hashed-route-patterns.d.ts +4 -0
  87. package/dist/routing/hashed-route-patterns.js +22 -0
  88. package/dist/routing/optimistic-route-trie.d.ts +27 -0
  89. package/dist/routing/optimistic-route-trie.js +102 -0
  90. package/dist/routing/route-validation.d.ts +6 -1
  91. package/dist/routing/route-validation.js +26 -10
  92. package/dist/server/api-handler.js +1 -1
  93. package/dist/server/app-action-forwarding.d.ts +8 -0
  94. package/dist/server/app-action-forwarding.js +17 -3
  95. package/dist/server/app-browser-entry.js +149 -62
  96. package/dist/server/app-browser-error.d.ts +3 -1
  97. package/dist/server/app-browser-error.js +7 -2
  98. package/dist/server/app-browser-interception-context.d.ts +2 -2
  99. package/dist/server/app-browser-interception-context.js +1 -1
  100. package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
  101. package/dist/server/app-browser-mpa-navigation.js +6 -5
  102. package/dist/server/app-browser-navigation-controller.d.ts +17 -6
  103. package/dist/server/app-browser-navigation-controller.js +88 -26
  104. package/dist/server/app-browser-prefetch-response.js +2 -2
  105. package/dist/server/app-browser-state.d.ts +2 -2
  106. package/dist/server/app-browser-state.js +1 -1
  107. package/dist/server/app-browser-supplemental-refresh.js +1 -1
  108. package/dist/server/app-browser-visible-commit.d.ts +4 -4
  109. package/dist/server/app-client-reference-preloader.d.ts +23 -0
  110. package/dist/server/app-client-reference-preloader.js +30 -7
  111. package/dist/server/app-fallback-renderer.js +2 -1
  112. package/dist/server/app-middleware.d.ts +6 -0
  113. package/dist/server/app-middleware.js +8 -10
  114. package/dist/server/app-optimistic-routing.d.ts +8 -10
  115. package/dist/server/app-optimistic-routing.js +4 -95
  116. package/dist/server/app-page-boundary-render.js +8 -1
  117. package/dist/server/app-page-cache-render.js +1 -0
  118. package/dist/server/app-page-dispatch.js +3 -1
  119. package/dist/server/app-page-render.js +4 -2
  120. package/dist/server/app-page-rsc-render-status.d.ts +1 -0
  121. package/dist/server/app-page-rsc-render-status.js +1 -0
  122. package/dist/server/app-page-search-params-observation.d.ts +1 -4
  123. package/dist/server/app-page-search-params-observation.js +8 -10
  124. package/dist/server/app-page-stream.d.ts +7 -0
  125. package/dist/server/app-page-stream.js +1 -0
  126. package/dist/server/app-pages-bridge.d.ts +2 -1
  127. package/dist/server/app-pages-bridge.js +12 -8
  128. package/dist/server/app-prerender-endpoints.js +1 -1
  129. package/dist/server/app-prerender-static-params.js +3 -2
  130. package/dist/server/app-render-abort-error.d.ts +19 -0
  131. package/dist/server/app-render-abort-error.js +31 -1
  132. package/dist/server/app-render-dependency.js +42 -2
  133. package/dist/server/app-request-stage-independent-entry.js +7 -7
  134. package/dist/server/app-response-stage-entry.js +5 -5
  135. package/dist/server/app-route-handler-dispatch.js +3 -1
  136. package/dist/server/app-route-handler-execution.d.ts +2 -0
  137. package/dist/server/app-route-handler-execution.js +9 -2
  138. package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
  139. package/dist/server/app-route-handler-middleware-context.js +3 -3
  140. package/dist/server/app-route-handler-response.d.ts +2 -1
  141. package/dist/server/app-route-handler-response.js +2 -2
  142. package/dist/server/app-route-handler-runtime.d.ts +2 -0
  143. package/dist/server/app-route-handler-runtime.js +21 -4
  144. package/dist/server/app-route-module-loader.js +7 -6
  145. package/dist/server/app-route-tree.d.ts +20 -0
  146. package/dist/server/app-route-tree.js +18 -0
  147. package/dist/server/app-router-entry.js +7 -7
  148. package/dist/server/app-rsc-cache-busting.d.ts +16 -0
  149. package/dist/server/app-rsc-cache-busting.js +29 -12
  150. package/dist/server/app-rsc-combined-handler.js +8 -1
  151. package/dist/server/app-rsc-errors.d.ts +7 -0
  152. package/dist/server/app-rsc-errors.js +9 -2
  153. package/dist/server/app-rsc-handler.d.ts +4 -1
  154. package/dist/server/app-rsc-handler.js +151 -135
  155. package/dist/server/app-rsc-request-normalization.js +3 -3
  156. package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
  157. package/dist/server/app-rsc-response-finalizer.js +4 -1
  158. package/dist/server/app-rsc-response-stage.js +5 -3
  159. package/dist/server/app-rsc-route-matching.d.ts +1 -0
  160. package/dist/server/app-rsc-route-matching.js +1 -1
  161. package/dist/server/app-server-action-execution.d.ts +23 -6
  162. package/dist/server/app-server-action-execution.js +51 -57
  163. package/dist/server/app-ssr-entry.d.ts +6 -0
  164. package/dist/server/app-ssr-entry.js +21 -11
  165. package/dist/server/app-ssr-stream.d.ts +7 -0
  166. package/dist/server/app-ssr-stream.js +13 -1
  167. package/dist/server/app-visited-response-cache.js +2 -2
  168. package/dist/server/config-headers.js +13 -4
  169. package/dist/server/defer-until-stream-consumed.d.ts +2 -1
  170. package/dist/server/defer-until-stream-consumed.js +3 -1
  171. package/dist/server/dev-server.js +1 -1
  172. package/dist/server/image-optimization.js +1 -0
  173. package/dist/server/isolate-shared-promise.d.ts +10 -0
  174. package/dist/server/isolate-shared-promise.js +33 -0
  175. package/dist/server/isr-cache.js +3 -2
  176. package/dist/server/metadata-route-response.js +2 -2
  177. package/dist/server/middleware-matcher-pattern.d.ts +0 -1
  178. package/dist/server/middleware-matcher-pattern.js +11 -140
  179. package/dist/server/middleware-matcher.js +5 -6
  180. package/dist/server/middleware-response-headers.d.ts +31 -0
  181. package/dist/server/middleware-response-headers.js +57 -1
  182. package/dist/server/middleware-runtime.js +30 -17
  183. package/dist/server/navigation-planner.d.ts +2 -2
  184. package/dist/server/navigation-planner.js +1 -1
  185. package/dist/server/nitro-public-files-plugin.d.ts +38 -0
  186. package/dist/server/nitro-public-files-plugin.js +106 -0
  187. package/dist/server/nitro-public-files.d.ts +21 -0
  188. package/dist/server/nitro-public-files.js +39 -0
  189. package/dist/server/open-redirect.d.ts +11 -4
  190. package/dist/server/open-redirect.js +11 -4
  191. package/dist/server/pages-api-route.js +2 -2
  192. package/dist/server/pages-asset-tags.d.ts +5 -0
  193. package/dist/server/pages-asset-tags.js +9 -2
  194. package/dist/server/pages-client-assets.d.ts +4 -1
  195. package/dist/server/pages-data-route.js +2 -1
  196. package/dist/server/pages-i18n.d.ts +18 -0
  197. package/dist/server/pages-i18n.js +18 -2
  198. package/dist/server/pages-node-compat.d.ts +5 -3
  199. package/dist/server/pages-node-compat.js +35 -16
  200. package/dist/server/pages-page-data.js +1 -1
  201. package/dist/server/pages-page-handler.js +1 -1
  202. package/dist/server/pages-page-response.js +1 -1
  203. package/dist/server/pages-request-pipeline.d.ts +6 -1
  204. package/dist/server/pages-request-pipeline.js +35 -39
  205. package/dist/server/pages-request-stage-entry.js +20 -10
  206. package/dist/server/pages-response-stage-entry.js +1 -1
  207. package/dist/server/pages-revalidate.js +3 -2
  208. package/dist/server/pages-router-entry.js +2 -2
  209. package/dist/server/prod-server.d.ts +2 -2
  210. package/dist/server/prod-server.js +74 -36
  211. package/dist/server/proxy-trust.d.ts +6 -0
  212. package/dist/server/proxy-trust.js +12 -6
  213. package/dist/server/request-pipeline.d.ts +5 -0
  214. package/dist/server/request-pipeline.js +12 -1
  215. package/dist/server/rsc-stream-hints.d.ts +1 -1
  216. package/dist/server/rsc-stream-hints.js +21 -3
  217. package/dist/server/server-reference-loader.js +10 -1
  218. package/dist/server/static-file-cache.js +15 -3
  219. package/dist/server/worker-image-optimization.js +19 -6
  220. package/dist/server/worker-utils.js +1 -1
  221. package/dist/shims/cache-handler.js +3 -2
  222. package/dist/shims/cache-request-state.d.ts +40 -3
  223. package/dist/shims/cache-request-state.js +70 -5
  224. package/dist/shims/cache-runtime.js +47 -19
  225. package/dist/shims/cache.d.ts +2 -2
  226. package/dist/shims/cache.js +17 -8
  227. package/dist/shims/cdn-purge-routes.d.ts +24 -0
  228. package/dist/shims/cdn-purge-routes.js +77 -0
  229. package/dist/shims/dynamic.js +17 -4
  230. package/dist/shims/form.js +8 -3
  231. package/dist/shims/head.js +1 -1
  232. package/dist/shims/headers.js +2 -2
  233. package/dist/shims/image-props.d.ts +93 -0
  234. package/dist/shims/image-props.js +323 -0
  235. package/dist/shims/image.d.ts +4 -18
  236. package/dist/shims/image.js +4 -319
  237. package/dist/shims/internal/headers-state.d.ts +7 -0
  238. package/dist/shims/internal/headers-state.js +14 -2
  239. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
  240. package/dist/shims/internal/hybrid-client-route-owner-direct.js +32 -15
  241. package/dist/shims/internal/hybrid-client-route-owner.js +9 -8
  242. package/dist/shims/link-prefetch.js +5 -4
  243. package/dist/shims/link.js +7 -5
  244. package/dist/shims/navigation.d.ts +1 -1
  245. package/dist/shims/navigation.js +21 -10
  246. package/dist/shims/og.js +35 -10
  247. package/dist/shims/request-context.d.ts +13 -0
  248. package/dist/shims/router.js +28 -52
  249. package/dist/shims/server.d.ts +42 -8
  250. package/dist/shims/server.js +99 -15
  251. package/dist/shims/thenable-params.d.ts +6 -1
  252. package/dist/shims/thenable-params.js +11 -2
  253. package/dist/shims/unified-request-context.d.ts +5 -0
  254. package/dist/shims/unified-request-context.js +2 -0
  255. package/dist/shims/url-utils.d.ts +7 -0
  256. package/dist/shims/url-utils.js +12 -3
  257. package/dist/utils/asset-prefix.d.ts +7 -0
  258. package/dist/utils/asset-prefix.js +10 -1
  259. package/dist/utils/client-build-manifest.js +12 -5
  260. package/dist/utils/client-runtime-metadata.d.ts +1 -0
  261. package/dist/utils/client-runtime-metadata.js +15 -2
  262. package/dist/utils/domain-locale.js +1 -1
  263. package/dist/utils/lazy-chunks.d.ts +4 -0
  264. package/dist/utils/manifest-paths.d.ts +14 -0
  265. package/dist/utils/manifest-paths.js +22 -1
  266. package/dist/utils/path-to-regexp.d.ts +21 -0
  267. package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
  268. package/dist/utils/plugin-react-version.d.ts +15 -0
  269. package/dist/utils/plugin-react-version.js +64 -0
  270. package/dist/utils/plugin-rsc-version.d.ts +2 -0
  271. package/dist/utils/plugin-rsc-version.js +28 -2
  272. package/dist/utils/public-routes.d.ts +2 -0
  273. package/dist/utils/public-routes.js +18 -7
  274. package/dist/utils/react-version.d.ts +7 -0
  275. package/dist/utils/react-version.js +39 -16
  276. package/package.json +6 -6
  277. package/dist/build/nitro-route-rules.d.ts +0 -47
  278. package/dist/build/nitro-route-rules.js +0 -79
  279. package/dist/plugins/cache-flight-codec.d.ts +0 -19
  280. package/dist/plugins/cache-flight-codec.js +0 -68
  281. package/dist/server/middleware-path-to-regexp.d.ts +0 -13
  282. package/dist/utils/regex-safety.d.ts +0 -17
  283. package/dist/utils/regex-safety.js +0 -789
@@ -1,5 +1,4 @@
1
- import { analyzeRegexSafety, mayHaveNamedGroups, regexAtomsMayOverlap } from "../utils/regex-safety.js";
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 patternMatches(pattern, value) {
56
- try {
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
- kind: "invalid",
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 = parseMiddlewarePath(source);
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: middlewarePathTokensToRegExp(tokens) };
67
+ return { regexp: middlewareRegExp(tokens) };
189
68
  } catch {
190
- const normalizedTokens = normalizeMiddlewarePathTokens(tokens);
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: middlewarePathTokensToRegExp(normalizedTokens) };
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 UNSAFE_MATCHER_PATTERN = Symbol("unsafe matcher pattern");
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) === UNSAFE_MATCHER_PATTERN) return true;
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 === UNSAFE_MATCHER_PATTERN) return true;
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
- const problem = result.kind === "unsafe" ? "potentially unsafe" : "invalid";
107
- console.warn(`[vinext] Rejecting ${problem} middleware matcher: ${pattern}\n ${result.error}.\n Middleware will run for all paths to avoid bypassing request guards.`);
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
- function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false, skipTrailingSlashRedirect = false) {
139
- const url = new URL(request.url);
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 nextRequest = mwRequest instanceof NextRequest && (!mwRequest.body || mwRequest.bodyUsed) ? mwRequest : new NextRequest(mwRequest, nextConfig ? { nextConfig } : void 0);
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 nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
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, options.request.url);
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, options.request.url);
267
- if (loc.origin === new URL(options.request.url).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(), options.request.url);
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, options.request.url);
315
- const requestOrigin = new URL(options.request.url).origin;
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 { RouteManifest } from "../routing/app-route-graph.js";
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: RouteManifest | null;
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 };