vinext 1.0.1 → 1.2.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 (334) hide show
  1. package/README.md +93 -10
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/client-build-config.d.ts +10 -0
  5. package/dist/build/client-build-config.js +18 -2
  6. package/dist/build/pages-client-assets-module.d.ts +8 -0
  7. package/dist/build/pages-client-assets-module.js +21 -1
  8. package/dist/build/prerender-paths.js +8 -81
  9. package/dist/build/prerender.js +33 -8
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/server-manifest.d.ts +7 -0
  12. package/dist/build/server-manifest.js +11 -1
  13. package/dist/build/ssr-manifest.d.ts +2 -0
  14. package/dist/build/ssr-manifest.js +6 -2
  15. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  16. package/dist/cache/cache-adapters-virtual.js +3 -3
  17. package/dist/check.js +7 -6
  18. package/dist/cli.js +2 -1
  19. package/dist/client/client-redirect-matcher.d.ts +14 -0
  20. package/dist/client/client-redirect-matcher.js +37 -0
  21. package/dist/client/client-redirects.d.ts +27 -0
  22. package/dist/client/client-redirects.js +28 -0
  23. package/dist/client/client-rewrites.d.ts +2 -1
  24. package/dist/client/client-rewrites.js +1 -1
  25. package/dist/client/navigation-runtime.d.ts +11 -0
  26. package/dist/client/navigation-runtime.js +1 -1
  27. package/dist/config/config-matchers.d.ts +26 -11
  28. package/dist/config/config-matchers.js +81 -82
  29. package/dist/config/destination-params.d.ts +47 -0
  30. package/dist/config/destination-params.js +116 -0
  31. package/dist/config/next-config.d.ts +14 -0
  32. package/dist/config/next-config.js +21 -3
  33. package/dist/entries/app-rsc-entry.d.ts +6 -0
  34. package/dist/entries/app-rsc-entry.js +84 -49
  35. package/dist/entries/pages-client-entry.js +3 -1
  36. package/dist/entries/pages-server-entry.js +13 -37
  37. package/dist/index.d.ts +7 -2
  38. package/dist/index.js +193 -73
  39. package/dist/init-cloudflare.d.ts +9 -0
  40. package/dist/init-cloudflare.js +107 -31
  41. package/dist/init-platform.d.ts +4 -0
  42. package/dist/init-platform.js +27 -2
  43. package/dist/init.d.ts +1 -0
  44. package/dist/init.js +67 -3
  45. package/dist/plugins/ast-scope.d.ts +7 -0
  46. package/dist/plugins/ast-scope.js +59 -3
  47. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  48. package/dist/plugins/cache-flight-codec.js +68 -0
  49. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  50. package/dist/plugins/commonjs-esm-facade.js +80 -0
  51. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  52. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  53. package/dist/plugins/fonts.d.ts +8 -11
  54. package/dist/plugins/fonts.js +71 -34
  55. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  56. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  57. package/dist/plugins/middleware-server-only.d.ts +5 -4
  58. package/dist/plugins/middleware-server-only.js +8 -5
  59. package/dist/plugins/pages-node-externals.js +1 -1
  60. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  61. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  62. package/dist/plugins/strip-server-exports.js +262 -98
  63. package/dist/plugins/transform-result.d.ts +9 -5
  64. package/dist/plugins/transitive-externals.js +1 -1
  65. package/dist/plugins/typeof-window.d.ts +2 -2
  66. package/dist/plugins/use-cache-callable.js +8 -2
  67. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  68. package/dist/plugins/worker-server-only-imports.js +31 -0
  69. package/dist/plugins/worker-use-server.d.ts +25 -0
  70. package/dist/plugins/worker-use-server.js +129 -0
  71. package/dist/routing/file-matcher.d.ts +1 -0
  72. package/dist/routing/file-matcher.js +1 -1
  73. package/dist/server/api-handler.d.ts +1 -0
  74. package/dist/server/api-handler.js +19 -8
  75. package/dist/server/app-browser-action-result.d.ts +1 -1
  76. package/dist/server/app-browser-action-result.js +9 -2
  77. package/dist/server/app-browser-entry.js +11 -7
  78. package/dist/server/app-browser-history-controller.d.ts +2 -0
  79. package/dist/server/app-browser-history-controller.js +8 -0
  80. package/dist/server/app-browser-interception-context.js +26 -2
  81. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  82. package/dist/server/app-browser-navigation-controller.js +94 -11
  83. package/dist/server/app-browser-server-action-client.js +1 -1
  84. package/dist/server/app-fallback-renderer.d.ts +2 -0
  85. package/dist/server/app-fallback-renderer.js +1 -0
  86. package/dist/server/app-history-state.d.ts +3 -0
  87. package/dist/server/app-history-state.js +7 -0
  88. package/dist/server/app-middleware.d.ts +11 -0
  89. package/dist/server/app-middleware.js +19 -3
  90. package/dist/server/app-optimistic-routing.js +1 -1
  91. package/dist/server/app-page-boundary-render.d.ts +5 -0
  92. package/dist/server/app-page-boundary-render.js +6 -4
  93. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  94. package/dist/server/app-page-cache-finalizer.js +41 -7
  95. package/dist/server/app-page-cache-render.d.ts +18 -0
  96. package/dist/server/app-page-cache-render.js +63 -43
  97. package/dist/server/app-page-cache.d.ts +6 -1
  98. package/dist/server/app-page-cache.js +28 -8
  99. package/dist/server/app-page-dispatch.d.ts +12 -2
  100. package/dist/server/app-page-dispatch.js +51 -33
  101. package/dist/server/app-page-element-builder.d.ts +7 -0
  102. package/dist/server/app-page-element-builder.js +27 -42
  103. package/dist/server/app-page-execution.d.ts +15 -12
  104. package/dist/server/app-page-execution.js +30 -40
  105. package/dist/server/app-page-head.d.ts +5 -0
  106. package/dist/server/app-page-head.js +9 -3
  107. package/dist/server/app-page-ppr-runtime.js +1 -1
  108. package/dist/server/app-page-probe.d.ts +0 -109
  109. package/dist/server/app-page-probe.js +3 -136
  110. package/dist/server/app-page-render-identity.d.ts +0 -1
  111. package/dist/server/app-page-render-identity.js +1 -1
  112. package/dist/server/app-page-render-observation.js +3 -3
  113. package/dist/server/app-page-render.d.ts +13 -5
  114. package/dist/server/app-page-render.js +219 -119
  115. package/dist/server/app-page-request.d.ts +0 -1
  116. package/dist/server/app-page-request.js +54 -17
  117. package/dist/server/app-page-response.d.ts +4 -3
  118. package/dist/server/app-page-response.js +4 -3
  119. package/dist/server/app-page-route-wiring.d.ts +17 -5
  120. package/dist/server/app-page-route-wiring.js +57 -35
  121. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  122. package/dist/server/app-page-rsc-render-status.js +54 -0
  123. package/dist/server/app-page-stream.d.ts +15 -8
  124. package/dist/server/app-page-stream.js +6 -0
  125. package/dist/server/app-pages-bridge.d.ts +1 -1
  126. package/dist/server/app-pages-bridge.js +2 -3
  127. package/dist/server/app-prerender-endpoints.js +9 -6
  128. package/dist/server/app-request-stage-dispatch.js +7 -3
  129. package/dist/server/app-request-stage-independent-entry.js +29 -9
  130. package/dist/server/app-response-header-provenance.d.ts +2 -0
  131. package/dist/server/app-response-header-provenance.js +10 -4
  132. package/dist/server/app-response-stage-entry.js +10 -2
  133. package/dist/server/app-route-handler-cache.d.ts +1 -0
  134. package/dist/server/app-route-handler-cache.js +1 -0
  135. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  136. package/dist/server/app-route-handler-dispatch.js +6 -3
  137. package/dist/server/app-route-handler-execution.d.ts +1 -0
  138. package/dist/server/app-route-handler-execution.js +1 -0
  139. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  140. package/dist/server/app-route-handler-runtime.js +45 -24
  141. package/dist/server/app-router-entry.js +27 -8
  142. package/dist/server/app-rsc-cache-busting.js +1 -1
  143. package/dist/server/app-rsc-errors.js +20 -16
  144. package/dist/server/app-rsc-handler.d.ts +11 -0
  145. package/dist/server/app-rsc-handler.js +291 -125
  146. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  147. package/dist/server/app-rsc-request-normalization.js +4 -2
  148. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  149. package/dist/server/app-rsc-response-stage.js +10 -6
  150. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  151. package/dist/server/app-rsc-route-matching.js +36 -19
  152. package/dist/server/app-segment-config.d.ts +2 -1
  153. package/dist/server/app-server-action-execution.d.ts +2 -1
  154. package/dist/server/app-server-action-execution.js +62 -23
  155. package/dist/server/app-ssr-entry.d.ts +3 -2
  156. package/dist/server/app-ssr-entry.js +8 -5
  157. package/dist/server/app-ssr-stream.d.ts +6 -1
  158. package/dist/server/app-ssr-stream.js +34 -15
  159. package/dist/server/app-worker-stages.d.ts +7 -2
  160. package/dist/server/app-worker-stages.js +4 -4
  161. package/dist/server/cacheability-request.js +11 -7
  162. package/dist/server/client-build-files.d.ts +16 -0
  163. package/dist/server/client-build-files.js +36 -0
  164. package/dist/server/client-reuse-manifest.js +1 -1
  165. package/dist/server/config-headers.js +2 -0
  166. package/dist/server/dev-server.js +50 -29
  167. package/dist/server/dev-stack-sourcemap.js +18 -23
  168. package/dist/server/document-initial-head.d.ts +0 -11
  169. package/dist/server/document-initial-head.js +1 -1
  170. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  171. package/dist/server/forwarded-middleware-context.js +22 -0
  172. package/dist/server/headers.d.ts +18 -1
  173. package/dist/server/headers.js +19 -2
  174. package/dist/server/http-conditional.d.ts +11 -0
  175. package/dist/server/http-conditional.js +10 -1
  176. package/dist/server/http-error-responses.d.ts +1 -1
  177. package/dist/server/image-optimization.d.ts +54 -0
  178. package/dist/server/image-optimization.js +118 -9
  179. package/dist/server/isr-cache.d.ts +10 -0
  180. package/dist/server/isr-cache.js +43 -8
  181. package/dist/server/metadata-route-response.js +9 -8
  182. package/dist/server/middleware-matcher-pattern.js +8 -4
  183. package/dist/server/middleware-matcher.d.ts +2 -0
  184. package/dist/server/middleware-matcher.js +23 -2
  185. package/dist/server/middleware-response-headers.js +2 -0
  186. package/dist/server/middleware-runtime.d.ts +13 -0
  187. package/dist/server/middleware-runtime.js +33 -20
  188. package/dist/server/middleware.d.ts +1 -1
  189. package/dist/server/middleware.js +3 -1
  190. package/dist/server/multi-stage.d.ts +19 -0
  191. package/dist/server/navigation-planner.js +1 -1
  192. package/dist/server/node-response-signal.d.ts +16 -0
  193. package/dist/server/node-response-signal.js +26 -0
  194. package/dist/server/normalize-path.d.ts +0 -13
  195. package/dist/server/normalize-path.js +1 -34
  196. package/dist/server/open-redirect.d.ts +47 -0
  197. package/dist/server/open-redirect.js +74 -1
  198. package/dist/server/pages-api-route.d.ts +3 -2
  199. package/dist/server/pages-api-route.js +4 -3
  200. package/dist/server/pages-cache-tag.d.ts +12 -0
  201. package/dist/server/pages-cache-tag.js +19 -0
  202. package/dist/server/pages-client-assets.d.ts +2 -0
  203. package/dist/server/pages-data-route.d.ts +10 -0
  204. package/dist/server/pages-data-route.js +19 -2
  205. package/dist/server/pages-document-asset-props.d.ts +5 -1
  206. package/dist/server/pages-document-asset-props.js +13 -5
  207. package/dist/server/pages-document-splice.d.ts +19 -0
  208. package/dist/server/pages-document-splice.js +55 -0
  209. package/dist/server/pages-i18n.d.ts +12 -1
  210. package/dist/server/pages-i18n.js +18 -4
  211. package/dist/server/pages-node-compat.d.ts +16 -5
  212. package/dist/server/pages-node-compat.js +20 -12
  213. package/dist/server/pages-page-data.d.ts +32 -24
  214. package/dist/server/pages-page-data.js +50 -120
  215. package/dist/server/pages-page-handler.d.ts +0 -6
  216. package/dist/server/pages-page-handler.js +209 -130
  217. package/dist/server/pages-page-response.d.ts +12 -6
  218. package/dist/server/pages-page-response.js +41 -34
  219. package/dist/server/pages-request-pipeline.d.ts +10 -2
  220. package/dist/server/pages-request-pipeline.js +37 -18
  221. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  222. package/dist/server/pages-request-stage-entry.js +79 -16
  223. package/dist/server/pages-response-stage-entry.js +4 -0
  224. package/dist/server/pages-revalidate.js +1 -1
  225. package/dist/server/pages-router-entry.js +4 -3
  226. package/dist/server/prerender-manifest.d.ts +3 -3
  227. package/dist/server/prerender-manifest.js +4 -3
  228. package/dist/server/prerender-work-unit-setup.js +4 -0
  229. package/dist/server/prod-server.d.ts +20 -7
  230. package/dist/server/prod-server.js +343 -129
  231. package/dist/server/proper-error.d.ts +4 -0
  232. package/dist/server/proper-error.js +31 -0
  233. package/dist/server/request-pipeline.d.ts +24 -9
  234. package/dist/server/request-pipeline.js +24 -9
  235. package/dist/server/response-compression.d.ts +43 -0
  236. package/dist/server/response-compression.js +111 -0
  237. package/dist/server/revalidation-request.d.ts +1 -0
  238. package/dist/server/revalidation-request.js +1 -1
  239. package/dist/server/rsc-stream-hints.js +31 -2
  240. package/dist/server/seed-cache.d.ts +1 -0
  241. package/dist/server/seed-cache.js +15 -11
  242. package/dist/server/server-reference-loader.d.ts +4 -0
  243. package/dist/server/server-reference-loader.js +29 -0
  244. package/dist/server/socket-error-backstop.d.ts +23 -1
  245. package/dist/server/socket-error-backstop.js +69 -17
  246. package/dist/server/static-file-signal.d.ts +15 -4
  247. package/dist/server/static-file-signal.js +46 -12
  248. package/dist/server/worker-image-optimization.d.ts +34 -0
  249. package/dist/server/worker-image-optimization.js +86 -0
  250. package/dist/server/worker-stages.d.ts +1 -1
  251. package/dist/server/worker-stages.js +2 -2
  252. package/dist/server/worker-utils.d.ts +8 -3
  253. package/dist/server/worker-utils.js +12 -7
  254. package/dist/shims/app-router-scroll.js +4 -1
  255. package/dist/shims/app-router-tree-context.d.ts +5 -0
  256. package/dist/shims/app-router-tree-context.js +27 -0
  257. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  258. package/dist/shims/cache-adapter-instantiate.js +118 -0
  259. package/dist/shims/cache-callable-runtime.d.ts +6 -42
  260. package/dist/shims/cache-callable-runtime.js +65 -174
  261. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  262. package/dist/shims/cache-flight-arguments.js +56 -0
  263. package/dist/shims/cache-handler.js +1 -9
  264. package/dist/shims/cache-request-state.d.ts +15 -0
  265. package/dist/shims/cache-request-state.js +31 -8
  266. package/dist/shims/cache-runtime.d.ts +12 -20
  267. package/dist/shims/cache-runtime.js +166 -225
  268. package/dist/shims/cache.d.ts +4 -4
  269. package/dist/shims/cache.js +44 -16
  270. package/dist/shims/cacheability-classification.d.ts +7 -0
  271. package/dist/shims/cdn-cache.d.ts +8 -0
  272. package/dist/shims/cdn-cache.js +17 -1
  273. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  274. package/dist/shims/disable-smooth-scroll.js +36 -0
  275. package/dist/shims/document-placeholders.d.ts +19 -0
  276. package/dist/shims/document-placeholders.js +45 -0
  277. package/dist/shims/document.js +21 -5
  278. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  279. package/dist/shims/dynamic-preload-chunks.js +13 -9
  280. package/dist/shims/dynamic.d.ts +1 -1
  281. package/dist/shims/dynamic.js +60 -8
  282. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  283. package/dist/shims/error-boundary-navigation.js +19 -1
  284. package/dist/shims/error-boundary.d.ts +10 -10
  285. package/dist/shims/error-boundary.js +26 -7
  286. package/dist/shims/fetch-cache-state.d.ts +66 -0
  287. package/dist/shims/fetch-cache-state.js +92 -0
  288. package/dist/shims/fetch-cache.d.ts +3 -61
  289. package/dist/shims/fetch-cache.js +161 -108
  290. package/dist/shims/form.js +1 -1
  291. package/dist/shims/head.d.ts +10 -6
  292. package/dist/shims/head.js +15 -13
  293. package/dist/shims/headers.d.ts +13 -0
  294. package/dist/shims/headers.js +47 -4
  295. package/dist/shims/image.js +2 -1
  296. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  297. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  298. package/dist/shims/internal/headers-state.d.ts +0 -1
  299. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  300. package/dist/shims/internal/work-store-scope.js +24 -0
  301. package/dist/shims/metadata.js +3 -3
  302. package/dist/shims/navigation-errors.d.ts +6 -0
  303. package/dist/shims/navigation-errors.js +11 -1
  304. package/dist/shims/navigation.d.ts +7 -0
  305. package/dist/shims/navigation.js +45 -9
  306. package/dist/shims/request-context.d.ts +11 -0
  307. package/dist/shims/request-state-types.d.ts +2 -1
  308. package/dist/shims/router.js +12 -20
  309. package/dist/shims/server.d.ts +16 -0
  310. package/dist/shims/server.js +24 -4
  311. package/dist/shims/thenable-params.d.ts +4 -0
  312. package/dist/shims/thenable-params.js +25 -3
  313. package/dist/shims/unified-request-context.d.ts +1 -1
  314. package/dist/shims/unified-request-context.js +1 -0
  315. package/dist/shims/url-utils.d.ts +2 -0
  316. package/dist/shims/url-utils.js +3 -1
  317. package/dist/utils/client-runtime-metadata.js +1 -1
  318. package/dist/utils/hash.d.ts +7 -0
  319. package/dist/utils/hash.js +11 -1
  320. package/dist/utils/mdx-scan.d.ts +5 -0
  321. package/dist/utils/mdx-scan.js +8 -1
  322. package/dist/utils/package-name.d.ts +6 -0
  323. package/dist/utils/package-name.js +12 -1
  324. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  325. package/dist/utils/plugin-rsc-version.js +15 -0
  326. package/dist/utils/prerender-output-paths.js +15 -4
  327. package/dist/utils/public-file-path.d.ts +10 -2
  328. package/dist/utils/public-file-path.js +13 -9
  329. package/dist/utils/public-routes.js +2 -2
  330. package/dist/utils/react-version.d.ts +3 -0
  331. package/dist/utils/react-version.js +1 -1
  332. package/dist/utils/regex-safety.d.ts +10 -0
  333. package/dist/utils/regex-safety.js +70 -18
  334. package/package.json +9 -5
@@ -131,7 +131,8 @@ var NextRequest = class extends Request {
131
131
  basePath: _nextConfig.basePath,
132
132
  nextConfig: {
133
133
  i18n,
134
- trailingSlash: _nextConfig.trailingSlash
134
+ trailingSlash: _nextConfig.trailingSlash,
135
+ skipTrailingSlashRedirect: _nextConfig.skipTrailingSlashRedirect
135
136
  }
136
137
  } : void 0;
137
138
  this._nextUrl = new NextURL(url, void 0, urlConfig);
@@ -266,6 +267,8 @@ var NextURL = class NextURL {
266
267
  */
267
268
  _configBasePath;
268
269
  _basePath;
270
+ _configTrailingSlash;
271
+ _manualTrailingSlash;
269
272
  _trailingSlash;
270
273
  _locale;
271
274
  _configDefaultLocale;
@@ -277,7 +280,10 @@ var NextURL = class NextURL {
277
280
  this._url = new URL(input.toString(), base);
278
281
  this._configBasePath = config?.basePath ?? "";
279
282
  this._basePath = this._configBasePath;
280
- this._trailingSlash = config?.nextConfig?.trailingSlash ?? false;
283
+ this._configTrailingSlash = config?.nextConfig?.trailingSlash ?? false;
284
+ this._manualTrailingSlash = config?.nextConfig?.skipTrailingSlashRedirect === true;
285
+ this._trailingSlash = this._configTrailingSlash;
286
+ this._analyzeTrailingSlash();
281
287
  this._stripBasePath();
282
288
  const i18n = config?.nextConfig?.i18n;
283
289
  if (i18n) {
@@ -290,6 +296,15 @@ var NextURL = class NextURL {
290
296
  this._analyzeI18n();
291
297
  }
292
298
  }
299
+ /**
300
+ * With `skipTrailingSlashRedirect`, take the slash from the parsed pathname
301
+ * (before basePath and locale are stripped), as getNextPathnameInfo does.
302
+ */
303
+ _analyzeTrailingSlash() {
304
+ if (!this._manualTrailingSlash) return;
305
+ const { pathname } = this._url;
306
+ this._trailingSlash = pathname !== "/" ? pathname.endsWith("/") : this._configTrailingSlash;
307
+ }
293
308
  /** Strip basePath prefix from the internal pathname.
294
309
  * Mirrors Next.js's getNextPathnameInfo (re-run by NextURL.analyze() on
295
310
  * every parse, including `href` reassignment): basePath is only considered
@@ -348,7 +363,8 @@ var NextURL = class NextURL {
348
363
  */
349
364
  _applyTrailingSlash(pathname) {
350
365
  if (pathname === "" || pathname === "/") return pathname;
351
- if ((this._basePath ? stripBasePath(pathname, this._basePath) : pathname).startsWith("/_next/")) return removeTrailingSlash(pathname);
366
+ const pathnameWithoutBasePath = this._basePath ? stripBasePath(pathname, this._basePath) : pathname;
367
+ if (!this._manualTrailingSlash && pathnameWithoutBasePath.startsWith("/_next/")) return removeTrailingSlash(pathname);
352
368
  if (this._trailingSlash) return pathname.endsWith("/") ? pathname : pathname + "/";
353
369
  return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
354
370
  }
@@ -361,6 +377,7 @@ var NextURL = class NextURL {
361
377
  }
362
378
  set href(value) {
363
379
  this._url.href = value;
380
+ this._analyzeTrailingSlash();
364
381
  this._stripBasePath();
365
382
  this._analyzeI18n();
366
383
  }
@@ -467,7 +484,10 @@ var NextURL = class NextURL {
467
484
  locales: domain.locales ? [...domain.locales] : void 0
468
485
  }))
469
486
  };
470
- if (this._trailingSlash) nextConfig.trailingSlash = true;
487
+ if (this._manualTrailingSlash) {
488
+ nextConfig.skipTrailingSlashRedirect = true;
489
+ if (this._configTrailingSlash) nextConfig.trailingSlash = true;
490
+ } else if (this._trailingSlash) nextConfig.trailingSlash = true;
471
491
  const config = {
472
492
  basePath: this._configBasePath,
473
493
  nextConfig: Object.keys(nextConfig).length > 0 ? nextConfig : void 0
@@ -5,5 +5,9 @@ export type ThenableParamsObserver = Readonly<{
5
5
  observeParamAccess: (keys: readonly string[]) => void;
6
6
  observeReactPromiseStatus?: boolean;
7
7
  }>;
8
+ /** Serialize backing values without reading a framework fallback-key proxy. */
9
+ export declare function getFallbackParamsSnapshot(value: unknown): Promise<Record<string, unknown>> | undefined;
10
+ /** Reattach only framework metadata; argument values always come from the decoder. */
11
+ export declare function restoreThenableParams<T extends Record<string, unknown>>(decoded: T, original: unknown): ThenableParams<T>;
8
12
  export declare function makeThenableParams<T extends Record<string, unknown>>(obj: T, observer?: ThenableParamsObserver): ThenableParams<T>;
9
13
  //#endregion
@@ -4,6 +4,20 @@ import { isOwnPropertyCheck, isWellKnownProperty } from "./internal/thenable-wel
4
4
  function hasParamProperty(obj, prop) {
5
5
  return Object.prototype.hasOwnProperty.call(obj, prop);
6
6
  }
7
+ const paramsState = /* @__PURE__ */ new WeakMap();
8
+ function getParamsState(value) {
9
+ return value !== null && typeof value === "object" ? paramsState.get(value) : void 0;
10
+ }
11
+ /** Serialize backing values without reading a framework fallback-key proxy. */
12
+ function getFallbackParamsSnapshot(value) {
13
+ const state = getParamsState(value);
14
+ return state?.fallbackShellState ? Promise.resolve(state.plain) : void 0;
15
+ }
16
+ /** Reattach only framework metadata; argument values always come from the decoder. */
17
+ function restoreThenableParams(decoded, original) {
18
+ const state = getParamsState(original);
19
+ return createThenableParams(decoded, state?.observer, state?.fallbackShellState ?? null);
20
+ }
7
21
  function observeParamKeys(observer, keys) {
8
22
  if (observer) observer.observeParamAccess(keys);
9
23
  }
@@ -81,8 +95,10 @@ function createThenableParamsProxy(promise, handler) {
81
95
  return new Proxy(promise, handler);
82
96
  }
83
97
  function makeThenableParams(obj, observer) {
98
+ return createThenableParams(obj, observer, getPprFallbackShellState());
99
+ }
100
+ function createThenableParams(obj, observer, fallbackShellState) {
84
101
  const plain = { ...obj };
85
- const fallbackShellState = getPprFallbackShellState();
86
102
  const fallbackParamNames = fallbackShellState && Object.keys(plain).some((key) => fallbackShellState.fallbackParamNames.has(key)) ? fallbackShellState.fallbackParamNames : null;
87
103
  let fallbackShellPromise = null;
88
104
  let fallbackShellPromiseController = null;
@@ -98,7 +114,7 @@ function makeThenableParams(obj, observer) {
98
114
  function isFallbackParam(prop) {
99
115
  return typeof prop === "string" && (fallbackParamNames?.has(prop) ?? false);
100
116
  }
101
- return createThenableParamsProxy(promise, {
117
+ const params = createThenableParamsProxy(promise, {
102
118
  get(target, prop, receiver) {
103
119
  if (isPromiseContinuation(prop)) {
104
120
  const value = Reflect.get(target, prop, receiver);
@@ -150,6 +166,12 @@ function makeThenableParams(obj, observer) {
150
166
  return Reflect.ownKeys(plain).filter((prop) => !isWellKnownProperty(prop));
151
167
  }
152
168
  });
169
+ paramsState.set(params, {
170
+ plain,
171
+ observer,
172
+ fallbackShellState: fallbackParamNames ? fallbackShellState : null
173
+ });
174
+ return params;
153
175
  }
154
176
  //#endregion
155
- export { makeThenableParams };
177
+ export { getFallbackParamsSnapshot, makeThenableParams, restoreThenableParams };
@@ -1,7 +1,7 @@
1
1
  import { ExecutionContextLike } from "./request-context.js";
2
2
  import { CacheState } from "./cache-request-state.js";
3
3
  import { RootParamsState } from "./root-params.js";
4
- import { FetchCacheState } from "./fetch-cache.js";
4
+ import { FetchCacheState } from "./fetch-cache-state.js";
5
5
  import { VinextHeadersShimState } from "./internal/headers-state.js";
6
6
  import { RouterState } from "./router-state.js";
7
7
  import { markFullyBufferedBody, preserveFullyBufferedBodyMetadata } from "../server/fully-buffered-response.js";
@@ -17,6 +17,7 @@ function createRequestContext(opts) {
17
17
  return {
18
18
  headersContext: null,
19
19
  actionRevalidationKind: 0,
20
+ cacheLifeSink: null,
20
21
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
21
22
  pendingRevalidations: /* @__PURE__ */ new Set(),
22
23
  dynamicUsageDetected: false,
@@ -30,6 +30,8 @@ export declare function toSameOriginAppPath(url: string, basePath: string): stri
30
30
  * `.well-known`-aware redirect rule shipped in `routes-manifest.json`.
31
31
  * - Query strings and hash fragments are preserved verbatim.
32
32
  * - Idempotent: already-canonical paths round-trip unchanged.
33
+ * - With `skipTrailingSlashRedirect`, every path is returned as written, like
34
+ * Next.js's `__NEXT_MANUAL_TRAILING_SLASH` short-circuit.
33
35
  */
34
36
  export declare function normalizePathTrailingSlash(path: string, trailingSlash: boolean): string;
35
37
  /**
@@ -99,9 +99,11 @@ function removeRouteTrailingSlash(route) {
99
99
  * `.well-known`-aware redirect rule shipped in `routes-manifest.json`.
100
100
  * - Query strings and hash fragments are preserved verbatim.
101
101
  * - Idempotent: already-canonical paths round-trip unchanged.
102
+ * - With `skipTrailingSlashRedirect`, every path is returned as written, like
103
+ * Next.js's `__NEXT_MANUAL_TRAILING_SLASH` short-circuit.
102
104
  */
103
105
  function normalizePathTrailingSlash(path, trailingSlash) {
104
- if (!path.startsWith("/") || path.startsWith("//")) return path;
106
+ if (!path.startsWith("/") || path.startsWith("//") || process.env.__VINEXT_MANUAL_TRAILING_SLASH === "true") return path;
105
107
  const { pathname, query, hash } = parsePath(path);
106
108
  if (trailingSlash) {
107
109
  if (/\.[^/]+\/?$/.test(pathname)) return `${removeRouteTrailingSlash(pathname)}${query}${hash}`;
@@ -1,8 +1,8 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { isAbsoluteAssetPrefix, resolveAssetsDir } from "./asset-prefix.js";
3
3
  import { manifestFileWithAssetPrefix, manifestFileWithBase } from "./manifest-paths.js";
4
- import { findClientEntryFile, findPagesClientEntryFile, readClientBuildManifest } from "./client-build-manifest.js";
5
4
  import { findClientEntryFileFromVinextManifest, findPagesClientEntryFileFromVinextManifest, readClientEntryManifest } from "./client-entry-manifest.js";
5
+ import { findClientEntryFile, findPagesClientEntryFile, readClientBuildManifest } from "./client-build-manifest.js";
6
6
  import { computeDynamicImportPreloads, computeLazyChunks, dynamicImportPreloadsWithBase } from "./lazy-chunks.js";
7
7
  //#region src/utils/client-runtime-metadata.ts
8
8
  function collectCssGraph(buildManifest, applyBase) {
@@ -20,4 +20,11 @@ export declare function fnv1a64(input: string): string;
20
20
  * key format and produces different values. Do not swap one for the other.
21
21
  */
22
22
  export declare function fnv1a52(str: string): number;
23
+ /**
24
+ * Hex-encoded SHA-256 digest of a string's UTF-8 bytes, via Web Crypto so it
25
+ * runs unchanged in Node.js, Workers and browsers. Used for cache keys built
26
+ * from inputs a request can influence, where crafted inputs could collide
27
+ * under a non-cryptographic hash like `fnv1a64`.
28
+ */
29
+ export declare function sha256Hex(input: string): Promise<string>;
23
30
  //#endregion
@@ -51,5 +51,15 @@ function fnv1a52(str) {
51
51
  }
52
52
  return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ v3 >> 4);
53
53
  }
54
+ /**
55
+ * Hex-encoded SHA-256 digest of a string's UTF-8 bytes, via Web Crypto so it
56
+ * runs unchanged in Node.js, Workers and browsers. Used for cache keys built
57
+ * from inputs a request can influence, where crafted inputs could collide
58
+ * under a non-cryptographic hash like `fnv1a64`.
59
+ */
60
+ async function sha256Hex(input) {
61
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
62
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
63
+ }
54
64
  //#endregion
55
- export { fnv1a52, fnv1a64 };
65
+ export { fnv1a52, fnv1a64, sha256Hex };
@@ -5,6 +5,11 @@ export declare const mdxScanCache: Map<string, boolean>;
5
5
  * Check if the project has .mdx files in app/ or pages/ directories.
6
6
  */
7
7
  export declare function hasMdxFiles(root: string, appDir: string | null, pagesDir: string | null): boolean;
8
+ /**
9
+ * Whether page extensions discover MDX sources, including compound extensions
10
+ * such as `page.mdx`.
11
+ */
12
+ export declare function pageExtensionsIncludeMdx(pageExtensions: readonly string[]): boolean;
8
13
  /**
9
14
  * Load a reader that keeps only the ESM of an MDX module, so the JavaScript
10
15
  * export helpers can read it. It takes the ESM nodes from the MDX parser that
@@ -33,6 +33,13 @@ function scanDirForMdx(dir) {
33
33
  return false;
34
34
  }
35
35
  /**
36
+ * Whether page extensions discover MDX sources, including compound extensions
37
+ * such as `page.mdx`.
38
+ */
39
+ function pageExtensionsIncludeMdx(pageExtensions) {
40
+ return pageExtensions.some((extension) => extension.toLowerCase().split(".").at(-1) === "mdx");
41
+ }
42
+ /**
36
43
  * Load a reader that keeps only the ESM of an MDX module, so the JavaScript
37
44
  * export helpers can read it. It takes the ESM nodes from the MDX parser that
38
45
  * `@mdx-js/rollup` compiles with, resolved beside the plugin vinext
@@ -57,4 +64,4 @@ async function loadMdxEsmReader(root) {
57
64
  return (source) => processor.parse(source).children.flatMap((node) => node.type === "mdxjsEsm" && typeof node.value === "string" ? [node.value] : []).join("\n\n");
58
65
  }
59
66
  //#endregion
60
- export { hasMdxFiles, loadMdxEsmReader, mdxScanCache };
67
+ export { hasMdxFiles, loadMdxEsmReader, mdxScanCache, pageExtensionsIncludeMdx };
@@ -7,4 +7,10 @@ export declare const BARE_PACKAGE_SPECIFIER_RE: RegExp;
7
7
  * scoped names, and Node builtins are not npm package references.
8
8
  */
9
9
  export declare function packageNameFromSpecifier(specifier: string): string | null;
10
+ /**
11
+ * Turbopack's "foreign code" condition: a module under node_modules whose
12
+ * package is not in the (Turbopack) transpile list. Next.js skips its
13
+ * app-code transforms, such as next/dynamic, for foreign code.
14
+ */
15
+ export declare function isForeignNodeModule(id: string, transpiledPackages: readonly string[]): boolean;
10
16
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
1
2
  import { builtinModules } from "node:module";
2
3
  //#region src/utils/package-name.ts
3
4
  const BUILTIN_MODULES = new Set(builtinModules.flatMap((name) => name.startsWith("node:") ? [name, name.slice(5)] : [name, `node:${name}`]));
@@ -14,5 +15,15 @@ function packageNameFromSpecifier(specifier) {
14
15
  if (!packageName || BUILTIN_MODULES.has(packageName)) return null;
15
16
  return packageName;
16
17
  }
18
+ /**
19
+ * Turbopack's "foreign code" condition: a module under node_modules whose
20
+ * package is not in the (Turbopack) transpile list. Next.js skips its
21
+ * app-code transforms, such as next/dynamic, for foreign code.
22
+ */
23
+ function isForeignNodeModule(id, transpiledPackages) {
24
+ const normalizedId = toSlash(id);
25
+ if (!normalizedId.includes("/node_modules/")) return false;
26
+ return !transpiledPackages.some((packageName) => normalizedId.includes(`/node_modules/${packageName}/`));
27
+ }
17
28
  //#endregion
18
- export { BARE_PACKAGE_SPECIFIER_RE, packageNameFromSpecifier };
29
+ export { BARE_PACKAGE_SPECIFIER_RE, isForeignNodeModule, packageNameFromSpecifier };
@@ -0,0 +1,4 @@
1
+ //#region src/utils/plugin-rsc-version.d.ts
2
+ /** Return the installed @vitejs/plugin-rsc version, or throw if it is unsupported. */
3
+ export declare function assertPluginRscVersion(resolvedRscPath: string): string;
4
+ //#endregion
@@ -0,0 +1,15 @@
1
+ import { findPackageVersion, isVersionBelow } from "./react-version.js";
2
+ //#region src/utils/plugin-rsc-version.ts
3
+ const MINIMUM_PLUGIN_RSC_VERSION = [
4
+ 0,
5
+ 5,
6
+ 37
7
+ ];
8
+ /** Return the installed @vitejs/plugin-rsc version, or throw if it is unsupported. */
9
+ function assertPluginRscVersion(resolvedRscPath) {
10
+ const version = findPackageVersion(resolvedRscPath, "@vitejs/plugin-rsc");
11
+ if (!version || isVersionBelow(version, MINIMUM_PLUGIN_RSC_VERSION)) throw new Error(`vinext: App Router requires @vitejs/plugin-rsc ${MINIMUM_PLUGIN_RSC_VERSION.join(".")} or newer (found ${version ?? "an unknown version"}). Upgrade @vitejs/plugin-rsc.`);
12
+ return version;
13
+ }
14
+ //#endregion
15
+ export { assertPluginRscVersion };
@@ -1,11 +1,22 @@
1
1
  //#region src/utils/prerender-output-paths.ts
2
2
  /**
3
+ * Map a prerendered URL to the file path its artifacts are written under, as
4
+ * Next.js's `normalizePagePath` does. `/index` becomes `/index/index` (and
5
+ * `/index/foo` becomes `/index/index/foo`) so it never shares `/`'s files.
6
+ * Unlike Next.js, dynamic fallback-shell paths such as `/index/[slug]` are
7
+ * also prefixed; build and seed both use this mapping, so they stay in step.
8
+ * Source: https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/page-path/normalize-page-path.ts
9
+ */
10
+ function toOutputUrlPath(urlPath) {
11
+ return /^\/index(?:\/|$)/.test(urlPath) ? `/index${urlPath}` : urlPath;
12
+ }
13
+ /**
3
14
  * Determine the HTML output file path for a prerendered URL.
4
15
  * Respects trailingSlash config.
5
16
  */
6
17
  function getOutputPath(urlPath, trailingSlash, basePath = "") {
7
18
  if (urlPath === "/" && basePath === "") return "index.html";
8
- const clean = (basePath === "" ? urlPath : urlPath === "/" ? trailingSlash ? `${basePath}/` : basePath : `${basePath}${urlPath}`).replace(/^\//, "").replace(/\/$/, "");
19
+ const clean = (basePath === "" ? toOutputUrlPath(urlPath) : urlPath === "/" ? trailingSlash ? `${basePath}/` : basePath : `${basePath}${toOutputUrlPath(urlPath)}`).replace(/^\//, "").replace(/\/$/, "");
9
20
  if (trailingSlash) return `${clean}/index.html`;
10
21
  return `${clean}.html`;
11
22
  }
@@ -19,16 +30,16 @@ function getRscOutputPath(urlPath, options = {
19
30
  const cleanBasePath = options.basePath?.replace(/^\//, "");
20
31
  return cleanBasePath ? `${cleanBasePath}/index.txt` : "index.txt";
21
32
  }
22
- const clean = (options.basePath ? `${options.basePath}${urlPath}` : urlPath).replace(/^\//, "").replace(/\/$/, "");
33
+ const clean = `${options.basePath ?? ""}${toOutputUrlPath(urlPath)}`.replace(/^\//, "").replace(/\/$/, "");
23
34
  return options.trailingSlash ? `${clean}/index.txt` : `${clean}.txt`;
24
35
  }
25
36
  if (urlPath === "/") return "index.rsc";
26
- return urlPath.replace(/^\//, "") + ".rsc";
37
+ return toOutputUrlPath(urlPath).replace(/^\//, "") + ".rsc";
27
38
  }
28
39
  /** Determine the binary artifact path for a prerendered App Route response. */
29
40
  function getAppRouteOutputPath(urlPath) {
30
41
  if (urlPath === "/") return "index.route";
31
- return urlPath.replace(/^\//, "") + ".route";
42
+ return toOutputUrlPath(urlPath).replace(/^\//, "") + ".route";
32
43
  }
33
44
  //#endregion
34
45
  export { getAppRouteOutputPath, getOutputPath, getRscOutputPath };
@@ -1,4 +1,12 @@
1
1
  //#region src/utils/public-file-path.d.ts
2
- /** Precompute keys for both encoded requests and decoded proxy/dev variants. */
3
- export declare function publicFilePathVariants(pathname: string): string[];
2
+ /**
3
+ * Key a public file by the normalized pathname a request for it matches with.
4
+ *
5
+ * Request pathnames are decoded once per segment with path delimiters kept
6
+ * encoded, so `public/a b.txt` is `/a b.txt` and `public/C#.txt` is
7
+ * `/C%23.txt`. Keying only this form keeps a double-encoded alias such as
8
+ * `/a%2520b.txt`, which middleware matchers see as `/a%20b.txt`, from
9
+ * reaching `public/a b.txt`.
10
+ */
11
+ export declare function publicFileRouteKey(pathname: string): string;
4
12
  //#endregion
@@ -1,12 +1,16 @@
1
+ import { normalizePathnameForRouteMatch } from "../routing/utils.js";
1
2
  //#region src/utils/public-file-path.ts
2
- /** Encode each pathname segment without encoding `/` separators. */
3
- function encodePublicFilePath(pathname) {
4
- return pathname.split("/").map((segment) => encodeURIComponent(segment)).join("/");
5
- }
6
- /** Precompute keys for both encoded requests and decoded proxy/dev variants. */
7
- function publicFilePathVariants(pathname) {
8
- const encoded = encodePublicFilePath(pathname);
9
- return encoded === pathname ? [pathname] : [pathname, encoded];
3
+ /**
4
+ * Key a public file by the normalized pathname a request for it matches with.
5
+ *
6
+ * Request pathnames are decoded once per segment with path delimiters kept
7
+ * encoded, so `public/a b.txt` is `/a b.txt` and `public/C#.txt` is
8
+ * `/C%23.txt`. Keying only this form keeps a double-encoded alias such as
9
+ * `/a%2520b.txt`, which middleware matchers see as `/a%20b.txt`, from
10
+ * reaching `public/a b.txt`.
11
+ */
12
+ function publicFileRouteKey(pathname) {
13
+ return normalizePathnameForRouteMatch(pathname.split("/").map((segment) => encodeURIComponent(segment)).join("/"));
10
14
  }
11
15
  //#endregion
12
- export { publicFilePathVariants };
16
+ export { publicFileRouteKey };
@@ -1,5 +1,5 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
- import { publicFilePathVariants } from "./public-file-path.js";
2
+ import { publicFileRouteKey } from "./public-file-path.js";
3
3
  import fs from "node:fs";
4
4
  //#region src/utils/public-routes.ts
5
5
  function scanPublicFileRoutes(root, configuredPublicDir = "public") {
@@ -37,7 +37,7 @@ function scanPublicFileRoutes(root, configuredPublicDir = "public") {
37
37
  if (!stat.isFile()) continue;
38
38
  } else if (!entry.isFile()) continue;
39
39
  const relativePath = path.relative(publicDir, fullPath);
40
- routes.push(...publicFilePathVariants("/" + relativePath));
40
+ routes.push(publicFileRouteKey("/" + relativePath));
41
41
  }
42
42
  }
43
43
  if (fs.existsSync(publicDir)) try {
@@ -2,4 +2,7 @@
2
2
  export declare function getReactUpgradeDeps(root: string, options?: {
3
3
  fromManifest?: boolean;
4
4
  }): string[];
5
+ export declare function isVersionBelow(version: string, minimum: [number, number, number]): boolean;
6
+ /** Walk up from a resolved module entry to find its package version. */
7
+ export declare function findPackageVersion(resolvedEntry: string, packageName: string): string | null;
5
8
  //#endregion
@@ -59,4 +59,4 @@ function findPackageVersion(resolvedEntry, packageName) {
59
59
  return null;
60
60
  }
61
61
  //#endregion
62
- export { getReactUpgradeDeps };
62
+ export { findPackageVersion, getReactUpgradeDeps, isVersionBelow };
@@ -1,7 +1,17 @@
1
1
  //#region src/utils/regex-safety.d.ts
2
2
  export type RegexSafetyIssue = "nested repetition" | "ambiguous alternatives under repetition" | "ambiguous sequence expansion" | "overlapping sequential repetition" | "analysis budget exceeded";
3
+ /**
4
+ * Whether a RegExp source declares a named group, which turns `\k<name>` into
5
+ * a backreference. Escaped characters and character classes cannot open one.
6
+ */
7
+ export declare function mayHaveNamedGroups(pattern: string): boolean;
8
+ /**
9
+ * `namedGroups` must describe the whole compiled RegExp when `pattern` is only
10
+ * a fragment of it. It defaults to scanning `pattern` itself.
11
+ */
3
12
  export declare function analyzeRegexSafety(pattern: string, options?: {
4
13
  ignoreCase?: boolean;
14
+ namedGroups?: boolean;
5
15
  }): RegexSafetyIssue | null;
6
16
  export declare function regexAtomsMayOverlap(left: string, right: string, ignoreCase?: boolean): boolean;
7
17
  //#endregion
@@ -88,13 +88,15 @@ function simpleClassSymbol(raw, ignoreCase) {
88
88
  var RegexParser = class {
89
89
  pattern;
90
90
  ignoreCase;
91
+ namedGroups;
91
92
  index = 0;
92
93
  nodes = 0;
93
94
  depth = 0;
94
95
  exceededBudget = false;
95
- constructor(pattern, ignoreCase) {
96
+ constructor(pattern, ignoreCase, namedGroups) {
96
97
  this.pattern = pattern;
97
98
  this.ignoreCase = ignoreCase;
99
+ this.namedGroups = namedGroups;
98
100
  }
99
101
  parse() {
100
102
  return this.parseAlternation();
@@ -157,7 +159,7 @@ var RegexParser = class {
157
159
  kind: "atom",
158
160
  symbol: {
159
161
  kind: "opaque",
160
- key: ".",
162
+ key: "opaque:.",
161
163
  pattern: ".",
162
164
  ignoreCase: this.ignoreCase
163
165
  },
@@ -240,7 +242,7 @@ var RegexParser = class {
240
242
  kind: "atom",
241
243
  symbol: simpleClassSymbol(raw, this.ignoreCase) ?? {
242
244
  kind: "opaque",
243
- key: raw,
245
+ key: `opaque:${raw}`,
244
246
  pattern: raw,
245
247
  ignoreCase: this.ignoreCase
246
248
  },
@@ -267,6 +269,15 @@ var RegexParser = class {
267
269
  symbol: shorthand,
268
270
  fixedWidth: true
269
271
  });
272
+ if (escaped === "k" && this.namedGroups && this.pattern[this.index] === "<") {
273
+ const nameEnd = this.pattern.indexOf(">", this.index);
274
+ this.index = nameEnd === -1 ? this.pattern.length : nameEnd + 1;
275
+ return this.node({
276
+ kind: "atom",
277
+ symbol: null,
278
+ fixedWidth: false
279
+ });
280
+ }
270
281
  if (/\d/.test(escaped)) return this.node({
271
282
  kind: "atom",
272
283
  symbol: null,
@@ -279,6 +290,15 @@ var RegexParser = class {
279
290
  } else if (escaped === "u" && /^[\da-fA-F]{4}/.test(this.pattern.slice(this.index, this.index + 4))) {
280
291
  literal = String.fromCharCode(Number.parseInt(this.pattern.slice(this.index, this.index + 4), 16));
281
292
  this.index += 4;
293
+ } else if (escaped === "c") {
294
+ const letter = this.pattern[this.index];
295
+ if (letter !== void 0 && /[A-Za-z]/.test(letter)) {
296
+ literal = String.fromCharCode(letter.charCodeAt(0) % 32);
297
+ this.index++;
298
+ } else {
299
+ literal = "\\";
300
+ this.index--;
301
+ }
282
302
  } else if ("nrtvf0".includes(escaped)) literal = {
283
303
  n: "\n",
284
304
  r: "\r",
@@ -298,7 +318,7 @@ var RegexParser = class {
298
318
  kind: "atom",
299
319
  symbol: {
300
320
  kind: "opaque",
301
- key: raw,
321
+ key: `opaque:${raw}`,
302
322
  pattern: raw,
303
323
  ignoreCase: this.ignoreCase
304
324
  },
@@ -444,11 +464,12 @@ function fixedWords(node, budget) {
444
464
  return words;
445
465
  }
446
466
  case "repeat": {
447
- if (node.min !== node.max || !Number.isFinite(node.max)) return null;
467
+ if (!Number.isFinite(node.max) || node.min > node.max) return null;
448
468
  let words = [[]];
449
469
  const childWords = fixedWords(node.child, budget);
450
470
  if (!childWords) return null;
451
- for (let count = 0; count < node.min; count++) {
471
+ const repeated = node.min === 0 ? [[]] : [];
472
+ for (let count = 1; count <= node.max; count++) {
452
473
  const next = [];
453
474
  for (const prefix of words) for (const suffix of childWords) {
454
475
  if (++budget.words > MAX_WORDS) {
@@ -464,8 +485,9 @@ function fixedWords(node, budget) {
464
485
  next.push(word);
465
486
  }
466
487
  words = next;
488
+ if (count >= node.min) repeated.push(...words);
467
489
  }
468
- return words;
490
+ return repeated;
469
491
  }
470
492
  }
471
493
  }
@@ -508,17 +530,27 @@ function symbolsMayOverlap(left, right) {
508
530
  if (left.kind === "literal" && right.kind === "opaque") return opaqueMatchesLiteral(right, left);
509
531
  return true;
510
532
  }
533
+ function wordMayCollide(node, word, index, comparisons, branched) {
534
+ if (node.terminal) return true;
535
+ if (index === word.length) return node.edges.size > 0;
536
+ if (branched && ++comparisons.count > MAX_OPAQUE_COMPARISONS) return true;
537
+ const symbol = word[index];
538
+ const exact = node.edges.get(symbol.key);
539
+ if (exact && wordMayCollide(exact.node, word, index + 1, comparisons, branched)) return true;
540
+ const candidates = symbol.kind === "literal" ? node.complexEdges : node.edges.values();
541
+ for (const candidate of candidates) {
542
+ if (candidate === exact) continue;
543
+ if (++comparisons.count > MAX_OPAQUE_COMPARISONS) return true;
544
+ if (symbolsMayOverlap(candidate.symbol, symbol) && wordMayCollide(candidate.node, word, index + 1, comparisons, true)) return true;
545
+ }
546
+ return false;
547
+ }
511
548
  function insertPrefixFreeWord(root, word, comparisons) {
549
+ if (wordMayCollide(root, word, 0, comparisons, false)) return false;
512
550
  let node = root;
513
551
  for (const symbol of word) {
514
- if (node.terminal) return false;
515
552
  let edge = node.edges.get(symbol.key);
516
553
  if (!edge) {
517
- const candidates = symbol.kind === "literal" ? node.complexEdges : node.edges.values();
518
- for (const candidate of candidates) {
519
- if (++comparisons.count > MAX_OPAQUE_COMPARISONS) return false;
520
- if (symbolsMayOverlap(candidate.symbol, symbol)) return false;
521
- }
522
554
  edge = {
523
555
  symbol,
524
556
  node: createTrieNode()
@@ -528,7 +560,6 @@ function insertPrefixFreeWord(root, word, comparisons) {
528
560
  }
529
561
  node = edge.node;
530
562
  }
531
- if (node.terminal || node.edges.size > 0) return false;
532
563
  node.terminal = true;
533
564
  return true;
534
565
  }
@@ -578,6 +609,7 @@ function ambiguousExpansionFactor(node) {
578
609
  case "repeat": {
579
610
  if (node.min !== node.max || !Number.isFinite(node.max)) return 1;
580
611
  const childFactor = ambiguousExpansionFactor(node.child);
612
+ if (childFactor === 1) return 1;
581
613
  let factor = 1;
582
614
  for (let count = 0; count < node.max; count++) {
583
615
  factor *= childFactor;
@@ -705,16 +737,36 @@ function findSafetyIssue(node) {
705
737
  }
706
738
  }
707
739
  }
740
+ /**
741
+ * Whether a RegExp source declares a named group, which turns `\k<name>` into
742
+ * a backreference. Escaped characters and character classes cannot open one.
743
+ */
744
+ function mayHaveNamedGroups(pattern) {
745
+ let inClass = false;
746
+ for (let index = 0; index < pattern.length; index++) {
747
+ const character = pattern[index];
748
+ if (character === "\\") index++;
749
+ else if (inClass) {
750
+ if (character === "]") inClass = false;
751
+ } else if (character === "[") inClass = true;
752
+ else if (character === "(" && pattern.startsWith("?<", index + 1) && pattern[index + 3] !== "=" && pattern[index + 3] !== "!") return true;
753
+ }
754
+ return false;
755
+ }
756
+ /**
757
+ * `namedGroups` must describe the whole compiled RegExp when `pattern` is only
758
+ * a fragment of it. It defaults to scanning `pattern` itself.
759
+ */
708
760
  function analyzeRegexSafety(pattern, options = {}) {
709
761
  if (pattern.length > MAX_PATTERN_LENGTH) return "analysis budget exceeded";
710
- const parser = new RegexParser(pattern, options.ignoreCase === true);
762
+ const parser = new RegexParser(pattern, options.ignoreCase === true, options.namedGroups ?? mayHaveNamedGroups(pattern));
711
763
  const node = parser.parse();
712
764
  if (parser.exceededBudget) return "analysis budget exceeded";
713
765
  return findSafetyIssue(node);
714
766
  }
715
767
  function regexAtomsMayOverlap(left, right, ignoreCase = false) {
716
- const leftParser = new RegexParser(left, ignoreCase);
717
- const rightParser = new RegexParser(right, ignoreCase);
768
+ const leftParser = new RegexParser(left, ignoreCase, true);
769
+ const rightParser = new RegexParser(right, ignoreCase, true);
718
770
  const leftNode = leftParser.parse();
719
771
  const rightNode = rightParser.parse();
720
772
  const leftWords = fixedWords(leftNode, {
@@ -734,4 +786,4 @@ function regexAtomsMayOverlap(left, right, ignoreCase = false) {
734
786
  return symbolsMayOverlap(leftSymbol, rightSymbol);
735
787
  }
736
788
  //#endregion
737
- export { analyzeRegexSafety, regexAtomsMayOverlap };
789
+ export { analyzeRegexSafety, mayHaveNamedGroups, regexAtomsMayOverlap };