@wular/pnext 0.0.4 → 0.0.7

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 (395) hide show
  1. package/README.md +22 -16
  2. package/bin/pnext +6 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +87 -22
  8. package/reference/config.md +40 -93
  9. package/reference/css.md +42 -33
  10. package/reference/dev.md +26 -33
  11. package/reference/env.md +18 -20
  12. package/reference/getting-started.md +132 -0
  13. package/reference/metadata.md +31 -46
  14. package/reference/navigation.md +65 -105
  15. package/reference/performance.md +21 -142
  16. package/reference/rendering.md +78 -75
  17. package/reference/routing.md +97 -107
  18. package/reference/typegen.md +27 -29
  19. package/src/api/cache.ts +36 -37
  20. package/src/api/client-cache.ts +2 -2
  21. package/src/api/client-navigation.ts +121 -114
  22. package/src/api/dynamic.tsx +58 -55
  23. package/src/api/link.tsx +52 -53
  24. package/src/api/navigation.ts +59 -58
  25. package/src/api/server.ts +152 -144
  26. package/src/api/suspense.ts +4 -4
  27. package/src/cli/adapters/vercel-warm.ts +126 -121
  28. package/src/cli/adapters/vercel.ts +437 -443
  29. package/src/cli/analyze.ts +333 -144
  30. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  31. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  32. package/src/cli/build.ts +976 -978
  33. package/src/cli/create.ts +72 -64
  34. package/src/cli/dev.ts +116 -116
  35. package/src/cli/index.ts +94 -85
  36. package/src/cli/migrate/package-json.ts +73 -71
  37. package/src/cli/migrate/report.ts +32 -33
  38. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  39. package/src/cli/migrate/scan.ts +46 -46
  40. package/src/cli/migrate/spinner.ts +9 -9
  41. package/src/cli/migrate/tsconfig.ts +35 -35
  42. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  43. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  44. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  45. package/src/cli/start.ts +65 -66
  46. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  47. package/src/client/build.ts +802 -768
  48. package/src/client/chunk-fold.ts +245 -240
  49. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  50. package/src/client/entry.ts +162 -147
  51. package/src/client/prebuilt.ts +231 -223
  52. package/src/client/profile.ts +29 -29
  53. package/src/client/react-compiler.ts +20 -15
  54. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  55. package/src/client/reference-stub.ts +51 -51
  56. package/src/client/reference.ts +19 -19
  57. package/src/{api → client}/router/events.ts +32 -17
  58. package/src/{api → client}/router/history.ts +16 -16
  59. package/src/{api → client}/router/hub.ts +52 -53
  60. package/src/{api/router.ts → client/router/index.ts} +61 -60
  61. package/src/{api → client}/router/policies.ts +24 -24
  62. package/src/{api → client}/router/runtime.ts +2022 -1995
  63. package/src/{api → client}/router/types.ts +98 -98
  64. package/src/compat/actions/client-plugin.ts +42 -43
  65. package/src/compat/actions/client-stub.ts +14 -14
  66. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  67. package/src/compat/actions/config.ts +63 -64
  68. package/src/compat/actions/detect.ts +68 -68
  69. package/src/compat/actions/discovery.ts +105 -105
  70. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  71. package/src/compat/actions/early-submit.ts +1 -1
  72. package/src/compat/actions/endpoint.ts +198 -198
  73. package/src/compat/actions/extensions.ts +811 -0
  74. package/src/compat/actions/flight.ts +23 -23
  75. package/src/compat/actions/form-state.ts +34 -34
  76. package/src/compat/actions/hoist.ts +382 -236
  77. package/src/compat/actions/ids.ts +7 -12
  78. package/src/compat/actions/index.ts +8 -8
  79. package/src/compat/actions/instances.ts +46 -46
  80. package/src/compat/actions/origin.ts +47 -48
  81. package/src/compat/actions/protocol.ts +22 -22
  82. package/src/compat/actions/registry.ts +19 -19
  83. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  84. package/src/compat/actions/rewrite.ts +125 -125
  85. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  86. package/src/compat/actions/serve.ts +132 -136
  87. package/src/compat/actions/server-tag.ts +4 -4
  88. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  89. package/src/compat/actions/unrecognized-error.ts +4 -4
  90. package/src/compat/{index.ts → aliases.ts} +112 -109
  91. package/src/compat/bundler/bun-externals.ts +18 -18
  92. package/src/compat/bundler/cjs-exports.ts +271 -223
  93. package/src/compat/bundler/config.ts +116 -111
  94. package/src/compat/bundler/externals.ts +12 -12
  95. package/src/compat/bundler/import-meta-url.ts +19 -19
  96. package/src/compat/bundler/modularize-imports.ts +36 -33
  97. package/src/compat/bundler/new-url-asset.ts +22 -24
  98. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  99. package/src/compat/bundler/polyfill.ts +28 -28
  100. package/src/compat/bundler/react-compiler.ts +35 -29
  101. package/src/compat/bundler/react-profiler.tsx +11 -11
  102. package/src/compat/bundler/relay-transform.ts +48 -47
  103. package/src/compat/bundler/require-context.ts +119 -113
  104. package/src/compat/bundler/resolve-extensions.ts +19 -19
  105. package/src/compat/bundler/source-cache.ts +25 -25
  106. package/src/compat/bundler/static-imports.ts +7 -7
  107. package/src/compat/bundler/symlink-imports.ts +46 -46
  108. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  109. package/src/compat/bundler/wasm.ts +58 -60
  110. package/src/compat/bundler/webpack-loaders.ts +254 -241
  111. package/src/compat/bundler/worker.ts +101 -104
  112. package/src/compat/cache/build-flags.ts +29 -29
  113. package/src/compat/cache/build-prerender-errors.ts +40 -44
  114. package/src/compat/cache/custom-handler.ts +60 -53
  115. package/src/compat/cache/fetch-patch.ts +240 -240
  116. package/src/compat/cache/handler.ts +27 -31
  117. package/src/compat/cache/modern-handler.ts +148 -126
  118. package/src/compat/cache/resume-data-cache.ts +47 -45
  119. package/src/compat/cache/revalidate.ts +233 -232
  120. package/src/compat/cache/runtime-error.ts +35 -35
  121. package/src/compat/cache/use-cache-transform.ts +442 -417
  122. package/src/compat/cache/use-cache.ts +650 -614
  123. package/src/compat/cache-control.ts +140 -142
  124. package/src/compat/client/base-path.ts +21 -20
  125. package/src/compat/client/css-order.ts +18 -18
  126. package/src/compat/client/errors/bare-boundary.ts +11 -11
  127. package/src/compat/client/errors/control-flow.ts +23 -23
  128. package/src/compat/client/errors/error-boundary.ts +61 -61
  129. package/src/compat/client/errors/global-error.ts +101 -91
  130. package/src/compat/client/errors/install.ts +71 -72
  131. package/src/compat/client/errors/lazy.ts +23 -23
  132. package/src/compat/client/errors/primitive-throw.ts +47 -45
  133. package/src/compat/client/errors/soft-refresh.ts +4 -4
  134. package/src/compat/client/link-status.ts +33 -33
  135. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  136. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  137. package/src/compat/client/navigation-scroll.ts +63 -63
  138. package/src/compat/client/optimistic-routing.ts +93 -88
  139. package/src/compat/client/prefetch-cache.ts +23 -24
  140. package/src/compat/client/route-announcer.ts +35 -35
  141. package/src/compat/client/segment-cache-policy.ts +20 -20
  142. package/src/compat/client/segment-cache.ts +309 -315
  143. package/src/compat/client/segment-prefetch.ts +127 -132
  144. package/src/compat/client/trailing-slash.ts +5 -4
  145. package/src/compat/css/chunking.ts +113 -116
  146. package/src/compat/css/inline-css.ts +21 -21
  147. package/src/compat/css/lightningcss.ts +37 -38
  148. package/src/compat/css/modules.ts +161 -175
  149. package/src/compat/css/nonce.ts +7 -7
  150. package/src/compat/css/sass-plugin.ts +18 -21
  151. package/src/compat/css/sass.ts +150 -152
  152. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  153. package/src/compat/css/styled-jsx.ts +21 -21
  154. package/src/compat/edge-runtime.ts +27 -27
  155. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  156. package/src/compat/export/client.ts +29 -31
  157. package/src/compat/export/{index.ts → emit.ts} +111 -110
  158. package/src/compat/export/standalone.ts +62 -54
  159. package/src/compat/image-optimizer/cache.ts +51 -49
  160. package/src/compat/image-optimizer/detect.ts +52 -52
  161. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  162. package/src/compat/image-optimizer/source.ts +77 -80
  163. package/src/compat/lifecycle/after-scope.ts +26 -26
  164. package/src/compat/lifecycle/after.ts +48 -45
  165. package/src/compat/lifecycle/error-funnel.ts +98 -102
  166. package/src/compat/lifecycle/error-serialize.ts +30 -26
  167. package/src/compat/lifecycle/error-ui.ts +67 -32
  168. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  169. package/src/compat/lifecycle/instrumentation.ts +85 -85
  170. package/src/compat/lifecycle/node-console.ts +11 -11
  171. package/src/compat/lifecycle/testmode.ts +160 -132
  172. package/src/compat/mdx/compile.ts +60 -57
  173. package/src/compat/mdx/plugin.ts +11 -11
  174. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  175. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  176. package/src/compat/metadata.ts +75 -81
  177. package/src/compat/next/cache.ts +68 -69
  178. package/src/compat/next/canonical-url.ts +9 -9
  179. package/src/compat/next/client-cache.ts +19 -19
  180. package/src/compat/next/client-navigation.ts +112 -116
  181. package/src/compat/next/client-only.ts +1 -1
  182. package/src/compat/next/client-script.tsx +99 -93
  183. package/src/compat/next/client-server.ts +10 -10
  184. package/src/compat/next/config-loader.ts +176 -173
  185. package/src/compat/next/config.ts +7 -7
  186. package/src/compat/next/constants.cjs +6 -6
  187. package/src/compat/next/constants.ts +6 -6
  188. package/src/compat/next/custom-server.ts +26 -24
  189. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  190. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  191. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  192. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  193. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  194. package/src/compat/next/dynamic.tsx +21 -18
  195. package/src/compat/next/error.tsx +52 -52
  196. package/src/compat/next/font/cache.ts +74 -74
  197. package/src/compat/next/font/google.ts +2 -2
  198. package/src/compat/next/font/index.ts +1 -1
  199. package/src/compat/next/font/local.ts +3 -3
  200. package/src/compat/next/font/runtime-client.ts +17 -15
  201. package/src/compat/next/font/runtime.ts +443 -408
  202. package/src/compat/next/font/shared.ts +120 -108
  203. package/src/compat/next/form.tsx +63 -63
  204. package/src/compat/next/head.tsx +2 -2
  205. package/src/compat/next/headers.ts +103 -95
  206. package/src/compat/next/image/client.tsx +220 -0
  207. package/src/compat/next/image/config.ts +56 -58
  208. package/src/compat/next/image/optimizer.ts +40 -36
  209. package/src/compat/next/image/patterns.ts +37 -40
  210. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  211. package/src/compat/next/image/shared.ts +65 -57
  212. package/src/compat/next/image/static-metadata.ts +98 -107
  213. package/src/compat/next/image/validate.ts +79 -88
  214. package/src/compat/next/image.tsx +19 -23
  215. package/src/compat/next/index.ts +1 -1
  216. package/src/compat/next/legacy-image.tsx +59 -60
  217. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  218. package/src/compat/next/link.tsx +158 -158
  219. package/src/compat/next/navigation.cjs +12 -3
  220. package/src/compat/next/navigation.ts +48 -50
  221. package/src/compat/next/offline.ts +27 -27
  222. package/src/compat/next/og.ts +121 -124
  223. package/src/compat/next/preferred-region.ts +13 -14
  224. package/src/compat/next/redirects.ts +58 -56
  225. package/src/compat/next/resource-hints.ts +73 -76
  226. package/src/compat/next/rewrites.ts +130 -133
  227. package/src/compat/next/root-params.ts +45 -45
  228. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  229. package/src/compat/next/router.cjs +4 -2
  230. package/src/compat/next/router.ts +58 -61
  231. package/src/compat/next/script.tsx +108 -108
  232. package/src/compat/next/server-only.ts +1 -1
  233. package/src/compat/next/server.ts +19 -19
  234. package/src/compat/next/svgr.ts +18 -17
  235. package/src/compat/next/telemetry.ts +24 -24
  236. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  237. package/src/compat/next/user-agent.ts +53 -49
  238. package/src/compat/next/web-vitals.ts +22 -24
  239. package/src/compat/otel/api.ts +41 -41
  240. package/src/compat/otel/client-trace-metadata.ts +25 -27
  241. package/src/compat/otel/fetch-span.ts +29 -29
  242. package/src/compat/otel/tracer.ts +331 -331
  243. package/src/compat/pages/api.ts +456 -0
  244. package/src/compat/pages/client-plugin.ts +36 -36
  245. package/src/compat/pages/router-state.ts +34 -34
  246. package/src/compat/pages/{index.ts → router.ts} +130 -135
  247. package/src/compat/ppr/io.ts +12 -12
  248. package/src/compat/ppr/missing-root-params.ts +34 -38
  249. package/src/compat/ppr/root-params-scan.ts +66 -66
  250. package/src/compat/ppr/root-params-transform.ts +24 -26
  251. package/src/compat/ppr/root-params.ts +30 -30
  252. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  253. package/src/compat/protocol.ts +71 -70
  254. package/src/compat/react/action-state.ts +47 -48
  255. package/src/compat/react/client-lite.ts +15 -15
  256. package/src/compat/react/client.ts +4 -4
  257. package/src/compat/react/compiler-runtime.ts +11 -11
  258. package/src/compat/react/dom-client.ts +44 -44
  259. package/src/compat/react/dom-react-server.ts +10 -16
  260. package/src/compat/react/dom-server.ts +10 -10
  261. package/src/compat/react/dom.ts +52 -52
  262. package/src/compat/react/hooks-extra.ts +34 -35
  263. package/src/compat/react/parity.ts +64 -61
  264. package/src/compat/react/preact.ts +81 -82
  265. package/src/compat/react/react-server.ts +28 -28
  266. package/src/compat/react/router-shim.ts +1 -1
  267. package/src/compat/react/server-component-use.ts +8 -8
  268. package/src/compat/react/server-inserted-html.ts +30 -31
  269. package/src/compat/react/server.ts +53 -55
  270. package/src/compat/react/use.ts +32 -32
  271. package/src/compat/react/view-transition.ts +20 -20
  272. package/src/compat/register/actions.ts +35 -824
  273. package/src/compat/register/boot.ts +41 -41
  274. package/src/compat/register/build-tier.ts +5 -5
  275. package/src/compat/register/build.ts +74 -70
  276. package/src/compat/register/bundler.ts +161 -156
  277. package/src/compat/register/cache.ts +20 -20
  278. package/src/compat/register/client-errors.ts +3 -3
  279. package/src/compat/register/config.ts +6 -6
  280. package/src/compat/register/css-extras.ts +30 -34
  281. package/src/compat/register/edge-runtime.ts +3 -3
  282. package/src/compat/register/errors.ts +10 -12
  283. package/src/compat/register/export.ts +16 -16
  284. package/src/compat/register/font.ts +11 -11
  285. package/src/compat/register/hooks.ts +2 -2
  286. package/src/compat/register/image.ts +40 -40
  287. package/src/compat/register/index.ts +59 -62
  288. package/src/compat/register/instrumentation-client.ts +10 -10
  289. package/src/compat/register/lifecycle.ts +25 -28
  290. package/src/compat/register/mdx.ts +10 -10
  291. package/src/compat/register/middleware.ts +226 -16
  292. package/src/compat/register/otel.ts +80 -88
  293. package/src/compat/register/pages-api.ts +10 -463
  294. package/src/compat/register/ppr.ts +16 -16
  295. package/src/compat/register/protocol.ts +17 -18
  296. package/src/compat/register/proxy.ts +49 -51
  297. package/src/compat/register/render.ts +79 -76
  298. package/src/compat/register/routing.ts +162 -159
  299. package/src/compat/register/segment.ts +24 -1897
  300. package/src/compat/register/static-image.ts +3 -3
  301. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  302. package/src/compat/register/typed-routes.ts +14 -14
  303. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  304. package/src/compat/register/validation.ts +18 -18
  305. package/src/compat/segment/loading-boundary.ts +43 -45
  306. package/src/compat/segment/page-slot.ts +69 -69
  307. package/src/compat/segment/serve.ts +1884 -0
  308. package/src/compat/segment/tree.ts +113 -112
  309. package/src/compat/segment/vary-key.ts +38 -38
  310. package/src/compat/segment/vary-params.ts +138 -142
  311. package/src/compat/static-params.ts +14 -12
  312. package/src/compat/tsconfig-defaults.ts +87 -91
  313. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  314. package/src/compat/typecheck/worker.ts +15 -12
  315. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  316. package/src/compat/typed-routes/manifest.ts +174 -170
  317. package/src/compat/typed-routes/typegen.ts +127 -110
  318. package/src/compat/validation/errors.ts +16 -19
  319. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  320. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  321. package/src/compat-bootstrap.ts +16 -16
  322. package/src/config.ts +86 -69
  323. package/src/css/build.ts +230 -227
  324. package/src/css/postcss.ts +79 -80
  325. package/src/css/worker.ts +14 -15
  326. package/src/dev/client-actions.ts +10 -10
  327. package/src/dev/client-chunk-store.ts +27 -27
  328. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  329. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  330. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  331. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  332. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  333. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  334. package/src/dev/server.ts +804 -820
  335. package/src/env.ts +46 -43
  336. package/src/extensions.ts +491 -478
  337. package/src/index.ts +8 -8
  338. package/src/internal.ts +20 -23
  339. package/src/{islands → render}/boundary-error.ts +3 -3
  340. package/src/render/hooks.ts +71 -71
  341. package/src/render/island-context.ts +14 -14
  342. package/src/render/metadata.ts +310 -310
  343. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  344. package/src/{ppr.ts → render/ppr.ts} +244 -245
  345. package/src/render/renderer.ts +2087 -2082
  346. package/src/render/resource-hints.ts +16 -17
  347. package/src/render/slots.tsx +224 -235
  348. package/src/{islands → render}/static-children.ts +9 -12
  349. package/src/{islands → render}/static-slots.ts +37 -37
  350. package/src/{cache/context.ts → request/cache.ts} +20 -20
  351. package/src/request/context.ts +107 -107
  352. package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
  353. package/src/resolve/engine.ts +90 -77
  354. package/src/resolve/imports.ts +475 -463
  355. package/src/resolve/scan-facts.ts +318 -296
  356. package/src/resolve/source-text.ts +37 -37
  357. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  358. package/src/routing/forwarded.ts +19 -19
  359. package/src/routing/handler.ts +84 -91
  360. package/src/routing/href.ts +87 -70
  361. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  362. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  363. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  364. package/src/routing/routes.ts +827 -815
  365. package/src/routing/slots.ts +164 -160
  366. package/src/runtime/loader.ts +954 -0
  367. package/src/{dev → runtime}/module-cache.ts +309 -287
  368. package/src/{dev → runtime}/module-generations.ts +9 -9
  369. package/src/{dev → runtime}/module-transform.ts +81 -72
  370. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  371. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  372. package/src/runtime/vendor.ts +428 -406
  373. package/src/styles.d.ts +9 -0
  374. package/src/types.ts +324 -335
  375. package/src/utils/ansi.ts +5 -5
  376. package/src/utils/{source.ts → code.ts} +15 -12
  377. package/src/utils/content-type.ts +3 -3
  378. package/src/utils/decode.ts +2 -2
  379. package/src/utils/dev-profile.ts +13 -13
  380. package/src/utils/error-log.ts +6 -6
  381. package/src/utils/esbuild.ts +18 -18
  382. package/src/utils/fs-cache.ts +13 -13
  383. package/src/utils/fs.ts +57 -49
  384. package/src/utils/html.ts +20 -24
  385. package/src/utils/native-require.ts +8 -8
  386. package/src/utils/serialize.ts +139 -146
  387. package/src/utils/verbose.ts +18 -18
  388. package/reference/overview.md +0 -35
  389. package/src/cli/analyze-print.ts +0 -181
  390. package/src/compat/middleware/manifest.ts +0 -210
  391. package/src/compat/next/image-client.tsx +0 -215
  392. package/src/compat/next/link-usage.ts +0 -29
  393. package/src/css/index.ts +0 -2
  394. package/src/render/index.ts +0 -1
  395. package/src/style-modules.d.ts +0 -9
@@ -1,5 +1,5 @@
1
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
- import path from 'node:path';
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
3
  import type {
4
4
  Metadata,
5
5
  MetadataImage,
@@ -9,9 +9,9 @@ import type {
9
9
  RouteManifestEntry,
10
10
  RouteParamValue,
11
11
  StaticMetadataFileEntry,
12
- } from '../types';
13
- import { escapeHtml, stringifyHtmlValue } from '../utils/html';
14
- import { toPosixPath } from '../utils/fs';
12
+ } from '../types'
13
+ import { escapeHtml, stringifyHtmlValue } from '../utils/html'
14
+ import { toPosixPath } from '../utils/fs'
15
15
 
16
16
  export type StaticMetadataKind =
17
17
  | 'favicon'
@@ -21,72 +21,72 @@ export type StaticMetadataKind =
21
21
  | 'twitter-image'
22
22
  | 'robots'
23
23
  | 'sitemap'
24
- | 'manifest';
24
+ | 'manifest'
25
25
 
26
26
  export interface StaticMetadataFile extends StaticMetadataFileEntry {
27
- kind: StaticMetadataKind;
27
+ kind: StaticMetadataKind
28
28
  }
29
29
 
30
30
  export interface StaticMetadataForPath {
31
- favicon?: StaticMetadataFile;
32
- manifest?: StaticMetadataFile;
33
- manifestLink?: MetadataLink;
34
- icons: StaticMetadataFile[];
35
- appleIcons: StaticMetadataFile[];
36
- rootIcons: StaticMetadataFile[];
37
- dynamicIcons?: MetadataLink[];
38
- dynamicAppleIcons?: MetadataLink[];
39
- openGraphImage?: MetadataImage;
40
- dynamicOpenGraphImages?: MetadataImage[];
41
- twitterImage?: MetadataImage;
42
- dynamicTwitterImages?: MetadataImage[];
43
- }
44
-
45
- const imageExtensions = ['ico', 'jpg', 'jpeg', 'png', 'svg', 'gif', 'webp'] as const;
46
- const imageExtensionPattern = imageExtensions.join('|');
47
- const iconPattern = new RegExp(`^icon\\d*\\.(${imageExtensionPattern})$`, 'i');
48
- const appleIconPattern = new RegExp(`^apple-icon\\d*\\.(${imageExtensionPattern})$`, 'i');
49
- const openGraphPattern = new RegExp(`^opengraph-image\\d*\\.(${imageExtensionPattern})$`, 'i');
50
- const twitterPattern = new RegExp(`^twitter-image\\d*\\.(${imageExtensionPattern})$`, 'i');
51
-
52
- export const staticMetadataCacheControl = 'public, max-age=0, must-revalidate';
31
+ favicon?: StaticMetadataFile
32
+ manifest?: StaticMetadataFile
33
+ manifestLink?: MetadataLink
34
+ icons: StaticMetadataFile[]
35
+ appleIcons: StaticMetadataFile[]
36
+ rootIcons: StaticMetadataFile[]
37
+ dynamicIcons?: MetadataLink[]
38
+ dynamicAppleIcons?: MetadataLink[]
39
+ openGraphImage?: MetadataImage
40
+ dynamicOpenGraphImages?: MetadataImage[]
41
+ twitterImage?: MetadataImage
42
+ dynamicTwitterImages?: MetadataImage[]
43
+ }
44
+
45
+ const imageExtensions = ['ico', 'jpg', 'jpeg', 'png', 'svg', 'gif', 'webp'] as const
46
+ const imageExtensionPattern = imageExtensions.join('|')
47
+ const iconPattern = new RegExp(`^icon\\d*\\.(${imageExtensionPattern})$`, 'i')
48
+ const appleIconPattern = new RegExp(`^apple-icon\\d*\\.(${imageExtensionPattern})$`, 'i')
49
+ const openGraphPattern = new RegExp(`^opengraph-image\\d*\\.(${imageExtensionPattern})$`, 'i')
50
+ const twitterPattern = new RegExp(`^twitter-image\\d*\\.(${imageExtensionPattern})$`, 'i')
51
+
52
+ export const staticMetadataCacheControl = 'public, max-age=0, must-revalidate'
53
53
 
54
54
  // Both discoveries walk ALL of app/ (recursive readdirSync) and every render asked for both. The
55
55
  // answer changes only when files appear or disappear, so memoize per appPath; dev clears these on a
56
56
  // structural save.
57
- const staticMetadataFiles = new Map<string, StaticMetadataFile[]>();
58
- const dynamicMetadataFiles = new Map<string, DynamicMetadataFile[]>();
57
+ const staticMetadataFiles = new Map<string, StaticMetadataFile[]>()
58
+ const dynamicMetadataFiles = new Map<string, DynamicMetadataFile[]>()
59
59
 
60
60
  /** Drop the memoized app/ metadata-file walks (dev structural invalidation). */
61
61
  export function clearMetadataFileCaches() {
62
- staticMetadataFiles.clear();
63
- dynamicMetadataFiles.clear();
62
+ staticMetadataFiles.clear()
63
+ dynamicMetadataFiles.clear()
64
64
  }
65
65
 
66
66
  export function discoverStaticMetadataFiles(appPath: string): StaticMetadataFile[] {
67
- const cached = staticMetadataFiles.get(appPath);
68
- if (cached) return cached;
67
+ const cached = staticMetadataFiles.get(appPath)
68
+ if (cached) return cached
69
69
  const files = existsSync(appPath)
70
70
  ? listFilesSync(appPath)
71
71
  .flatMap(file => staticMetadataFile(appPath, file) ?? [])
72
72
  .sort((a, b) => a.outputPath.localeCompare(b.outputPath))
73
- : [];
74
- staticMetadataFiles.set(appPath, files);
75
- return files;
73
+ : []
74
+ staticMetadataFiles.set(appPath, files)
75
+ return files
76
76
  }
77
77
 
78
78
  export function staticMetadataForPath(appPath: string, pathname: string): StaticMetadataForPath {
79
79
  const files = discoverStaticMetadataFiles(appPath).filter(file =>
80
80
  metadataAppliesToPath(file, pathname),
81
- );
82
- return staticMetadataForFiles(files);
81
+ )
82
+ return staticMetadataForFiles(files)
83
83
  }
84
84
 
85
85
  export function staticMetadataForPathFromFiles(
86
86
  files: StaticMetadataFile[],
87
87
  pathname: string,
88
88
  ): StaticMetadataForPath {
89
- return staticMetadataForFiles(files.filter(file => metadataAppliesToPath(file, pathname)));
89
+ return staticMetadataForFiles(files.filter(file => metadataAppliesToPath(file, pathname)))
90
90
  }
91
91
 
92
92
  export function staticMetadataForRoute(
@@ -94,11 +94,11 @@ export function staticMetadataForRoute(
94
94
  routeFile: string,
95
95
  pathname: string,
96
96
  ): StaticMetadataForPath {
97
- const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)));
97
+ const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)))
98
98
  const files = discoverStaticMetadataFiles(appPath).filter(
99
99
  file => metadataAppliesToPath(file, pathname) || metadataAppliesToRoute(file, routeDir),
100
- );
101
- return staticMetadataForFiles(files);
100
+ )
101
+ return staticMetadataForFiles(files)
102
102
  }
103
103
 
104
104
  export function staticMetadataForRouteFromFiles(
@@ -107,34 +107,34 @@ export function staticMetadataForRouteFromFiles(
107
107
  routeFile: string,
108
108
  pathname: string,
109
109
  ): StaticMetadataForPath {
110
- const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)));
110
+ const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)))
111
111
  return staticMetadataForFiles(
112
112
  files.filter(
113
113
  file => metadataAppliesToPath(file, pathname) || metadataAppliesToRoute(file, routeDir),
114
114
  ),
115
- );
115
+ )
116
116
  }
117
117
 
118
118
  export function staticRouteMetadataKey(pathname: string) {
119
- const path = pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname;
120
- return path || '/';
119
+ const path = pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname
120
+ return path || '/'
121
121
  }
122
122
 
123
- export type MetadataModuleLoader = (file: string) => Promise<Record<string, unknown>>;
123
+ export type MetadataModuleLoader = (file: string) => Promise<Record<string, unknown>>
124
124
 
125
125
  interface DynamicMetadataFile {
126
- kind: MetadataRouteKind;
127
- file: string;
128
- relative: string;
129
- dir: string;
130
- routeSegments: string[];
131
- base: string;
126
+ kind: MetadataRouteKind
127
+ file: string
128
+ relative: string
129
+ dir: string
130
+ routeSegments: string[]
131
+ base: string
132
132
  }
133
133
 
134
134
  interface MatchedDynamicMetadataFile extends DynamicMetadataFile {
135
- params: Record<string, RouteParamValue>;
136
- routePath: string;
137
- specificity: number;
135
+ params: Record<string, RouteParamValue>
136
+ routePath: string
137
+ specificity: number
138
138
  }
139
139
 
140
140
  export async function withDynamicMetadataRoutes(
@@ -144,26 +144,26 @@ export async function withDynamicMetadataRoutes(
144
144
  pathname: string,
145
145
  loadModule: MetadataModuleLoader,
146
146
  ): Promise<StaticMetadataForPath> {
147
- const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)));
147
+ const routeDir = toPosixPath(path.relative(appPath, path.dirname(routeFile)))
148
148
  const files = discoverDynamicMetadataFiles(appPath)
149
149
  .map(file => matchDynamicMetadataFile(file, pathname, routeDir))
150
- .filter((file): file is MatchedDynamicMetadataFile => file !== undefined);
150
+ .filter((file): file is MatchedDynamicMetadataFile => file !== undefined)
151
151
 
152
- const manifest = bestDynamicFiles(files, 'manifest')[0];
153
- const icons = await dynamicImageLinks(bestDynamicFiles(files, 'icon'), loadModule, 'icon');
152
+ const manifest = bestDynamicFiles(files, 'manifest')[0]
153
+ const icons = await dynamicImageLinks(bestDynamicFiles(files, 'icon'), loadModule, 'icon')
154
154
  const appleIcons = await dynamicImageLinks(
155
155
  bestDynamicFiles(files, 'apple-icon'),
156
156
  loadModule,
157
157
  'apple-touch-icon',
158
- );
158
+ )
159
159
  const openGraphImages = await dynamicMetadataImages(
160
160
  bestDynamicFiles(files, 'opengraph-image'),
161
161
  loadModule,
162
- );
162
+ )
163
163
  const twitterImages = await dynamicMetadataImages(
164
164
  bestDynamicFiles(files, 'twitter-image'),
165
165
  loadModule,
166
- );
166
+ )
167
167
 
168
168
  return {
169
169
  ...metadata,
@@ -172,17 +172,17 @@ export async function withDynamicMetadataRoutes(
172
172
  ...(appleIcons.length > 0 ? { dynamicAppleIcons: appleIcons } : {}),
173
173
  ...(openGraphImages.length > 0 ? { dynamicOpenGraphImages: openGraphImages } : {}),
174
174
  ...(twitterImages.length > 0 ? { dynamicTwitterImages: twitterImages } : {}),
175
- };
175
+ }
176
176
  }
177
177
 
178
178
  function staticMetadataForFiles(files: StaticMetadataFile[]): StaticMetadataForPath {
179
- const favicon = files.find(file => file.kind === 'favicon' && file.routeSegments.length === 0);
180
- const manifest = files.find(file => file.kind === 'manifest' && file.routeSegments.length === 0);
181
- const icons = nearestFiles(files, 'icon').filter(file => file.routeSegments.length > 0);
182
- const appleIcons = nearestFiles(files, 'apple-icon');
183
- const rootIcons = nearestFiles(files, 'icon').filter(file => file.routeSegments.length === 0);
184
- const openGraphFile = nearestFile(files, 'opengraph-image');
185
- const twitterFile = nearestFile(files, 'twitter-image');
179
+ const favicon = files.find(file => file.kind === 'favicon' && file.routeSegments.length === 0)
180
+ const manifest = files.find(file => file.kind === 'manifest' && file.routeSegments.length === 0)
181
+ const icons = nearestFiles(files, 'icon').filter(file => file.routeSegments.length > 0)
182
+ const appleIcons = nearestFiles(files, 'apple-icon')
183
+ const rootIcons = nearestFiles(files, 'icon').filter(file => file.routeSegments.length === 0)
184
+ const openGraphFile = nearestFile(files, 'opengraph-image')
185
+ const twitterFile = nearestFile(files, 'twitter-image')
186
186
  return {
187
187
  favicon,
188
188
  manifest,
@@ -191,49 +191,49 @@ function staticMetadataForFiles(files: StaticMetadataFile[]): StaticMetadataForP
191
191
  rootIcons,
192
192
  openGraphImage: openGraphFile ? metadataImage(openGraphFile) : undefined,
193
193
  twitterImage: twitterFile ? metadataImage(twitterFile) : undefined,
194
- };
194
+ }
195
195
  }
196
196
 
197
197
  export function staticMetadataLink(file: StaticMetadataFile): MetadataLink {
198
- const url = staticMetadataUrl(file);
198
+ const url = staticMetadataUrl(file)
199
199
  if (file.kind === 'manifest') {
200
- return { rel: 'manifest', url };
200
+ return { rel: 'manifest', url }
201
201
  }
202
202
  return {
203
203
  rel: file.kind === 'apple-icon' ? 'apple-touch-icon' : 'icon',
204
204
  url,
205
205
  type: file.contentType,
206
206
  ...(file.sizes ? { sizes: file.sizes } : {}),
207
- };
207
+ }
208
208
  }
209
209
 
210
210
  export function staticMetadataOutputFile(outPath: string, file: StaticMetadataFile) {
211
- return path.join(outPath, 'public', file.outputPath);
211
+ return path.join(outPath, 'public', file.outputPath)
212
212
  }
213
213
 
214
214
  export function metadataHasOwnImages(metadata: Metadata, field: 'openGraph' | 'twitter') {
215
- const value = metadata[field];
215
+ const value = metadata[field]
216
216
  return Boolean(
217
217
  value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'images'),
218
- );
218
+ )
219
219
  }
220
220
 
221
221
  function staticMetadataFile(appPath: string, file: string): StaticMetadataFile | undefined {
222
- const relative = toPosixPath(path.relative(appPath, file));
223
- const name = path.basename(relative);
224
- if (name.endsWith('.alt.txt')) return undefined;
225
- const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative);
226
- const root = dir === '';
227
- const routeSegments = routeSegmentsForDir(dir);
228
- const outputDir = outputDirForDir(dir);
229
- const kind = metadataKind(name, root);
230
- if (!kind) return undefined;
231
- const info = imageInfo(file);
222
+ const relative = toPosixPath(path.relative(appPath, file))
223
+ const name = path.basename(relative)
224
+ if (name.endsWith('.alt.txt')) return undefined
225
+ const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative)
226
+ const root = dir === ''
227
+ const routeSegments = routeSegmentsForDir(dir)
228
+ const outputDir = outputDirForDir(dir)
229
+ const kind = metadataKind(name, root)
230
+ if (!kind) return undefined
231
+ const info = imageInfo(file)
232
232
  if (kind === 'favicon' && !info) {
233
- throw new Error(`Error: Process image "/${relative}" failed: unable to decode image data`);
233
+ throw new Error(`Error: Process image "/${relative}" failed: unable to decode image data`)
234
234
  }
235
- const alt = metadataAltText(file);
236
- const outputName = metadataOutputName(name, kind, dir);
235
+ const alt = metadataAltText(file)
236
+ const outputName = metadataOutputName(name, kind, dir)
237
237
  return {
238
238
  kind,
239
239
  file,
@@ -245,28 +245,28 @@ function staticMetadataFile(appPath: string, file: string): StaticMetadataFile |
245
245
  ...(info?.height ? { height: info.height } : {}),
246
246
  ...(info?.sizes ? { sizes: info.sizes } : {}),
247
247
  ...(alt ? { alt } : {}),
248
- };
248
+ }
249
249
  }
250
250
 
251
251
  function metadataKind(name: string, root: boolean): StaticMetadataKind | undefined {
252
- if (name === 'favicon.ico') return root ? 'favicon' : undefined;
253
- if (iconPattern.test(name)) return 'icon';
254
- if (appleIconPattern.test(name)) return 'apple-icon';
255
- if (openGraphPattern.test(name)) return 'opengraph-image';
256
- if (twitterPattern.test(name)) return 'twitter-image';
257
- if (name === 'robots.txt') return root ? 'robots' : undefined;
258
- if (name === 'sitemap.xml') return 'sitemap';
259
- if (name === 'manifest.webmanifest') return root ? 'manifest' : undefined;
260
- if (name === 'manifest.json') return root ? 'manifest' : undefined;
261
- return undefined;
252
+ if (name === 'favicon.ico') return root ? 'favicon' : undefined
253
+ if (iconPattern.test(name)) return 'icon'
254
+ if (appleIconPattern.test(name)) return 'apple-icon'
255
+ if (openGraphPattern.test(name)) return 'opengraph-image'
256
+ if (twitterPattern.test(name)) return 'twitter-image'
257
+ if (name === 'robots.txt') return root ? 'robots' : undefined
258
+ if (name === 'sitemap.xml') return 'sitemap'
259
+ if (name === 'manifest.webmanifest') return root ? 'manifest' : undefined
260
+ if (name === 'manifest.json') return root ? 'manifest' : undefined
261
+ return undefined
262
262
  }
263
263
 
264
264
  function metadataOutputName(name: string, kind: StaticMetadataKind, relativeDir: string) {
265
- if (!metadataUsesSegmentSuffix(kind)) return name;
266
- const suffix = metadataRouteSuffix(relativeDir);
267
- if (!suffix) return name;
268
- const extension = path.extname(name);
269
- return `${name.slice(0, -extension.length)}-${suffix}${extension}`;
265
+ if (!metadataUsesSegmentSuffix(kind)) return name
266
+ const suffix = metadataRouteSuffix(relativeDir)
267
+ if (!suffix) return name
268
+ const extension = path.extname(name)
269
+ return `${name.slice(0, -extension.length)}-${suffix}${extension}`
270
270
  }
271
271
 
272
272
  function metadataUsesSegmentSuffix(kind: StaticMetadataKind) {
@@ -275,14 +275,14 @@ function metadataUsesSegmentSuffix(kind: StaticMetadataKind) {
275
275
  kind === 'apple-icon' ||
276
276
  kind === 'opengraph-image' ||
277
277
  kind === 'twitter-image'
278
- );
278
+ )
279
279
  }
280
280
 
281
281
  function metadataRouteSuffix(relativeDir: string) {
282
- if (!relativeDir) return '';
283
- const segments = relativeDir.split('/').filter(Boolean);
284
- if (!segments.some(segment => isRouteGroup(segment) || isSlotSegment(segment))) return '';
285
- return djb2Hash(`/${relativeDir}`).toString(36).slice(0, 6);
282
+ if (!relativeDir) return ''
283
+ const segments = relativeDir.split('/').filter(Boolean)
284
+ if (!segments.some(segment => isRouteGroup(segment) || isSlotSegment(segment))) return ''
285
+ return djb2Hash(`/${relativeDir}`).toString(36).slice(0, 6)
286
286
  }
287
287
 
288
288
  function metadataImage(file: StaticMetadataFile): MetadataImage {
@@ -292,66 +292,66 @@ function metadataImage(file: StaticMetadataFile): MetadataImage {
292
292
  ...(file.height ? { height: file.height } : {}),
293
293
  ...(file.alt ? { alt: file.alt } : {}),
294
294
  type: file.contentType,
295
- };
295
+ }
296
296
  }
297
297
 
298
298
  function staticMetadataUrl(file: StaticMetadataFile) {
299
- const suffix = file.kind === 'favicon' ? 'favicon' : 'metadata';
300
- return `/${file.outputPath}?${suffix}`;
299
+ const suffix = file.kind === 'favicon' ? 'favicon' : 'metadata'
300
+ return `/${file.outputPath}?${suffix}`
301
301
  }
302
302
 
303
303
  function nearestFile(files: StaticMetadataFile[], kind: StaticMetadataKind) {
304
- return nearestFiles(files, kind)[0];
304
+ return nearestFiles(files, kind)[0]
305
305
  }
306
306
 
307
307
  function nearestFiles(files: StaticMetadataFile[], kind: StaticMetadataKind) {
308
- const matches = files.filter(file => file.kind === kind);
309
- const maxDepth = Math.max(-1, ...matches.map(file => file.routeSegments.length));
310
- return matches.filter(file => file.routeSegments.length === maxDepth);
308
+ const matches = files.filter(file => file.kind === kind)
309
+ const maxDepth = Math.max(-1, ...matches.map(file => file.routeSegments.length))
310
+ return matches.filter(file => file.routeSegments.length === maxDepth)
311
311
  }
312
312
 
313
313
  function metadataAppliesToPath(file: StaticMetadataFile, pathname: string) {
314
- if (file.kind === 'favicon') return file.routeSegments.length === 0;
314
+ if (file.kind === 'favicon') return file.routeSegments.length === 0
315
315
  const segments = pathname
316
316
  .replace(/^\/+|\/+$/g, '')
317
317
  .split('/')
318
- .filter(Boolean);
319
- if (file.routeSegments.length > segments.length) return false;
318
+ .filter(Boolean)
319
+ if (file.routeSegments.length > segments.length) return false
320
320
  return file.routeSegments.every((segment, index) => {
321
- if (segment === '*') return true;
322
- if (segment.startsWith(':')) return Boolean(segments[index]);
323
- return segment === segments[index];
324
- });
321
+ if (segment === '*') return true
322
+ if (segment.startsWith(':')) return Boolean(segments[index])
323
+ return segment === segments[index]
324
+ })
325
325
  }
326
326
 
327
327
  function metadataAppliesToRoute(file: StaticMetadataFile, routeDir: string) {
328
- const dir = path.dirname(file.relative) === '.' ? '' : path.dirname(file.relative);
329
- if (!dir) return false;
330
- return routeDir === dir || routeDir.startsWith(`${dir}/`);
328
+ const dir = path.dirname(file.relative) === '.' ? '' : path.dirname(file.relative)
329
+ if (!dir) return false
330
+ return routeDir === dir || routeDir.startsWith(`${dir}/`)
331
331
  }
332
332
 
333
333
  function discoverDynamicMetadataFiles(appPath: string): DynamicMetadataFile[] {
334
- const cached = dynamicMetadataFiles.get(appPath);
335
- if (cached) return cached;
334
+ const cached = dynamicMetadataFiles.get(appPath)
335
+ if (cached) return cached
336
336
  const files = existsSync(appPath)
337
337
  ? listFilesSync(appPath)
338
338
  .flatMap(file => dynamicMetadataFile(appPath, file) ?? [])
339
339
  .sort((a, b) => a.relative.localeCompare(b.relative))
340
- : [];
341
- dynamicMetadataFiles.set(appPath, files);
342
- return files;
340
+ : []
341
+ dynamicMetadataFiles.set(appPath, files)
342
+ return files
343
343
  }
344
344
 
345
345
  function dynamicMetadataFile(appPath: string, file: string): DynamicMetadataFile | undefined {
346
- const relative = toPosixPath(path.relative(appPath, file));
347
- const name = path.basename(relative);
348
- const extension = path.extname(name).slice(1);
349
- if (!(metadataCodeExtensions as readonly string[]).includes(extension)) return undefined;
350
- const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative);
351
- const root = dir === '';
352
- const base = name.slice(0, -(extension.length + 1));
353
- const kind = dynamicMetadataKind(base, root);
354
- if (!kind) return undefined;
346
+ const relative = toPosixPath(path.relative(appPath, file))
347
+ const name = path.basename(relative)
348
+ const extension = path.extname(name).slice(1)
349
+ if (!(metadataCodeExtensions as readonly string[]).includes(extension)) return undefined
350
+ const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative)
351
+ const root = dir === ''
352
+ const base = name.slice(0, -(extension.length + 1))
353
+ const kind = dynamicMetadataKind(base, root)
354
+ if (!kind) return undefined
355
355
  return {
356
356
  kind,
357
357
  file,
@@ -359,20 +359,20 @@ function dynamicMetadataFile(appPath: string, file: string): DynamicMetadataFile
359
359
  dir,
360
360
  routeSegments: routeSegmentsForDir(dir),
361
361
  base,
362
- };
362
+ }
363
363
  }
364
364
 
365
- const metadataCodeExtensions = ['tsx', 'ts', 'jsx', 'js', 'mjs'] as const;
365
+ const metadataCodeExtensions = ['tsx', 'ts', 'jsx', 'js', 'mjs'] as const
366
366
 
367
367
  function dynamicMetadataKind(base: string, root: boolean): MetadataRouteKind | undefined {
368
- if (base === 'robots') return root ? 'robots' : undefined;
369
- if (base === 'sitemap') return 'sitemap';
370
- if (base === 'manifest') return root ? 'manifest' : undefined;
371
- if (/^icon\d*$/.test(base)) return 'icon';
372
- if (/^apple-icon\d*$/.test(base)) return 'apple-icon';
373
- if (/^opengraph-image\d*$/.test(base)) return 'opengraph-image';
374
- if (/^twitter-image\d*$/.test(base)) return 'twitter-image';
375
- return undefined;
368
+ if (base === 'robots') return root ? 'robots' : undefined
369
+ if (base === 'sitemap') return 'sitemap'
370
+ if (base === 'manifest') return root ? 'manifest' : undefined
371
+ if (/^icon\d*$/.test(base)) return 'icon'
372
+ if (/^apple-icon\d*$/.test(base)) return 'apple-icon'
373
+ if (/^opengraph-image\d*$/.test(base)) return 'opengraph-image'
374
+ if (/^twitter-image\d*$/.test(base)) return 'twitter-image'
375
+ return undefined
376
376
  }
377
377
 
378
378
  function metadataImageKind(kind: MetadataRouteKind) {
@@ -381,7 +381,7 @@ function metadataImageKind(kind: MetadataRouteKind) {
381
381
  kind === 'apple-icon' ||
382
382
  kind === 'opengraph-image' ||
383
383
  kind === 'twitter-image'
384
- );
384
+ )
385
385
  }
386
386
 
387
387
  function matchDynamicMetadataFile(
@@ -389,18 +389,18 @@ function matchDynamicMetadataFile(
389
389
  pathname: string,
390
390
  routeDir: string,
391
391
  ): MatchedDynamicMetadataFile | undefined {
392
- const match = matchDynamicMetadataDir(file.dir, pathname);
393
- const routeMatch = dynamicMetadataAppliesToRoute(file.dir, routeDir);
394
- if (!match) return undefined;
392
+ const match = matchDynamicMetadataDir(file.dir, pathname)
393
+ const routeMatch = dynamicMetadataAppliesToRoute(file.dir, routeDir)
394
+ if (!match) return undefined
395
395
  if (hasHiddenSegments(file.dir) && !routeMatch && file.routeSegments.length === 0) {
396
- return undefined;
396
+ return undefined
397
397
  }
398
398
  return {
399
399
  ...file,
400
400
  params: match.params,
401
401
  routePath: dynamicMetadataRoutePath(file, match.params),
402
402
  specificity: file.dir.split('/').filter(Boolean).length + (routeMatch ? 1000 : 0),
403
- };
403
+ }
404
404
  }
405
405
 
406
406
  function matchDynamicMetadataDir(
@@ -409,56 +409,60 @@ function matchDynamicMetadataDir(
409
409
  ): { params: Record<string, RouteParamValue> } | undefined {
410
410
  const routeSegments = relativeDir
411
411
  .split('/')
412
- .filter(segment => segment && !isRouteGroup(segment) && !isSlotSegment(segment));
413
- const pathSegments = pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
414
- if (routeSegments.length > pathSegments.length) return undefined;
412
+ .filter(segment => segment && !isRouteGroup(segment) && !isSlotSegment(segment))
413
+ const pathSegments = pathname
414
+ .replace(/^\/+|\/+$/g, '')
415
+ .split('/')
416
+ .filter(Boolean)
417
+ if (routeSegments.length > pathSegments.length) return undefined
415
418
 
416
- const params: Record<string, RouteParamValue> = {};
419
+ const params: Record<string, RouteParamValue> = {}
417
420
  for (let index = 0; index < routeSegments.length; index += 1) {
418
- const routeSegment = routeSegments[index]!;
419
- const pathSegment = decodeURIComponent(pathSegments[index] ?? '');
420
- const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(routeSegment);
421
+ const routeSegment = routeSegments[index]!
422
+ const pathSegment = decodeURIComponent(pathSegments[index] ?? '')
423
+ const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(routeSegment)
421
424
  if (catchAll) {
422
- params[catchAll[1]!] = pathSegments.slice(index).map(decodeURIComponent);
423
- return { params };
425
+ params[catchAll[1]!] = pathSegments.slice(index).map(decodeURIComponent)
426
+ return { params }
424
427
  }
425
- const optionalCatchAll = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(routeSegment);
428
+ const optionalCatchAll = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(routeSegment)
426
429
  if (optionalCatchAll) {
427
- const values = pathSegments.slice(index).map(decodeURIComponent);
428
- if (values.length > 0) params[optionalCatchAll[1]!] = values;
429
- return { params };
430
+ const values = pathSegments.slice(index).map(decodeURIComponent)
431
+ if (values.length > 0) params[optionalCatchAll[1]!] = values
432
+ return { params }
430
433
  }
431
- const dynamic = /^\[([^\]]+)\]$/.exec(routeSegment);
434
+ const dynamic = /^\[([^\]]+)\]$/.exec(routeSegment)
432
435
  if (dynamic) {
433
- if (!pathSegment) return undefined;
434
- params[dynamic[1]!] = pathSegment;
435
- continue;
436
+ if (!pathSegment) return undefined
437
+ params[dynamic[1]!] = pathSegment
438
+ continue
436
439
  }
437
- if (routeSegment !== pathSegment) return undefined;
440
+ if (routeSegment !== pathSegment) return undefined
438
441
  }
439
- return { params };
442
+ return { params }
440
443
  }
441
444
 
442
445
  function dynamicMetadataAppliesToRoute(relativeDir: string, routeDir: string) {
443
- if (!relativeDir) return routeDir === '';
444
- return routeDir === relativeDir || routeDir.startsWith(`${relativeDir}/`);
446
+ if (!relativeDir) return routeDir === ''
447
+ return routeDir === relativeDir || routeDir.startsWith(`${relativeDir}/`)
445
448
  }
446
449
 
447
450
  function hasHiddenSegments(relativeDir: string) {
448
- return relativeDir.split('/').some(segment => isRouteGroup(segment) || isSlotSegment(segment));
451
+ return relativeDir.split('/').some(segment => isRouteGroup(segment) || isSlotSegment(segment))
449
452
  }
450
453
 
451
454
  function dynamicMetadataRoutePath(
452
455
  file: DynamicMetadataFile,
453
456
  params: Record<string, RouteParamValue>,
454
457
  ) {
455
- if (file.kind === 'robots') return '/robots.txt';
456
- if (file.kind === 'manifest') return '/manifest.webmanifest';
457
- if (file.kind === 'sitemap') return `/${joinUrlPath(dynamicMetadataOutputDir(file.dir, params), 'sitemap.xml').replace(/^\/+/, '')}`;
458
+ if (file.kind === 'robots') return '/robots.txt'
459
+ if (file.kind === 'manifest') return '/manifest.webmanifest'
460
+ if (file.kind === 'sitemap')
461
+ return `/${joinUrlPath(dynamicMetadataOutputDir(file.dir, params), 'sitemap.xml').replace(/^\/+/, '')}`
458
462
 
459
- const suffix = metadataRouteSuffix(file.dir);
460
- const base = suffix ? `${file.base}-${suffix}` : file.base;
461
- return joinUrlPath(dynamicMetadataOutputDir(file.dir, params), base);
463
+ const suffix = metadataRouteSuffix(file.dir)
464
+ const base = suffix ? `${file.base}-${suffix}` : file.base
465
+ return joinUrlPath(dynamicMetadataOutputDir(file.dir, params), base)
462
466
  }
463
467
 
464
468
  function dynamicMetadataOutputDir(relativeDir: string, params: Record<string, RouteParamValue>) {
@@ -466,22 +470,22 @@ function dynamicMetadataOutputDir(relativeDir: string, params: Record<string, Ro
466
470
  .split('/')
467
471
  .filter(segment => segment && !isRouteGroup(segment) && !isSlotSegment(segment))
468
472
  .flatMap(segment => {
469
- const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(segment);
470
- const optionalCatchAll = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(segment);
471
- const dynamic = /^\[([^\]]+)\]$/.exec(segment);
472
- const key = catchAll?.[1] ?? optionalCatchAll?.[1] ?? dynamic?.[1];
473
- if (!key) return [segment];
474
- const value = params[key];
475
- if (value === undefined) return [];
476
- return Array.isArray(value) ? value : [value];
473
+ const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(segment)
474
+ const optionalCatchAll = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(segment)
475
+ const dynamic = /^\[([^\]]+)\]$/.exec(segment)
476
+ const key = catchAll?.[1] ?? optionalCatchAll?.[1] ?? dynamic?.[1]
477
+ if (!key) return [segment]
478
+ const value = params[key]
479
+ if (value === undefined) return []
480
+ return Array.isArray(value) ? value : [value]
477
481
  })
478
- .join('/');
482
+ .join('/')
479
483
  }
480
484
 
481
485
  function bestDynamicFiles(files: MatchedDynamicMetadataFile[], kind: MetadataRouteKind) {
482
- const matches = files.filter(file => file.kind === kind);
483
- const specificity = Math.max(-1, ...matches.map(file => file.specificity));
484
- return matches.filter(file => file.specificity === specificity);
486
+ const matches = files.filter(file => file.kind === kind)
487
+ const specificity = Math.max(-1, ...matches.map(file => file.specificity))
488
+ return matches.filter(file => file.specificity === specificity)
485
489
  }
486
490
 
487
491
  async function dynamicImageLinks(
@@ -489,28 +493,28 @@ async function dynamicImageLinks(
489
493
  loadModule: MetadataModuleLoader,
490
494
  rel: string,
491
495
  ): Promise<MetadataLink[]> {
492
- const links: MetadataLink[] = [];
496
+ const links: MetadataLink[] = []
493
497
  for (const file of files) {
494
- const module = (await loadModule(file.file)) as DynamicMetadataModule;
498
+ const module = (await loadModule(file.file)) as DynamicMetadataModule
495
499
  for (const image of await dynamicImageEntries(file, module)) {
496
500
  links.push({
497
501
  rel,
498
502
  url: image.url,
499
503
  type: image.type,
500
504
  ...(image.sizes ? { sizes: image.sizes } : {}),
501
- });
505
+ })
502
506
  }
503
507
  }
504
- return links;
508
+ return links
505
509
  }
506
510
 
507
511
  async function dynamicMetadataImages(
508
512
  files: MatchedDynamicMetadataFile[],
509
513
  loadModule: MetadataModuleLoader,
510
514
  ): Promise<MetadataImage[]> {
511
- const images: MetadataImage[] = [];
515
+ const images: MetadataImage[] = []
512
516
  for (const file of files) {
513
- const module = (await loadModule(file.file)) as DynamicMetadataModule;
517
+ const module = (await loadModule(file.file)) as DynamicMetadataModule
514
518
  for (const image of await dynamicImageEntries(file, module)) {
515
519
  images.push({
516
520
  url: image.url,
@@ -518,19 +522,19 @@ async function dynamicMetadataImages(
518
522
  ...(image.width ? { width: image.width } : {}),
519
523
  ...(image.height ? { height: image.height } : {}),
520
524
  ...(image.alt ? { alt: image.alt } : {}),
521
- });
525
+ })
522
526
  }
523
527
  }
524
- return images;
528
+ return images
525
529
  }
526
530
 
527
531
  interface DynamicImageEntry {
528
- url: string;
529
- type: string;
530
- width?: string;
531
- height?: string;
532
- sizes?: string;
533
- alt?: string;
532
+ url: string
533
+ type: string
534
+ width?: string
535
+ height?: string
536
+ sizes?: string
537
+ alt?: string
534
538
  }
535
539
 
536
540
  async function dynamicImageEntries(
@@ -540,16 +544,16 @@ async function dynamicImageEntries(
540
544
  const generated =
541
545
  metadataImageKind(file.kind) && module.generateImageMetadata
542
546
  ? await metadataGeneratedEntries(module, { kind: file.kind }, file.params)
543
- : [];
544
- const entries = generated.length > 0 ? generated : [undefined];
547
+ : []
548
+ const entries = generated.length > 0 ? generated : [undefined]
545
549
  return entries.map(entry => {
546
- const id = entry ? coerceString(entry.id) : undefined;
547
- const size = dynamicImageSize(entry?.size) ?? dynamicImageSize(module.size);
550
+ const id = entry ? coerceString(entry.id) : undefined
551
+ const size = dynamicImageSize(entry?.size) ?? dynamicImageSize(module.size)
548
552
  const type =
549
553
  (entry && typeof entry.contentType === 'string' ? entry.contentType : undefined) ??
550
554
  module.contentType ??
551
- 'image/png';
552
- const routePath = id ? joinUrlPath(file.routePath, encodeURIComponent(id)) : file.routePath;
555
+ 'image/png'
556
+ const routePath = id ? joinUrlPath(file.routePath, encodeURIComponent(id)) : file.routePath
553
557
  return {
554
558
  url: `${routePath}?metadata`,
555
559
  type,
@@ -561,69 +565,69 @@ async function dynamicImageEntries(
561
565
  : typeof module.alt === 'string'
562
566
  ? { alt: module.alt }
563
567
  : {}),
564
- };
565
- });
568
+ }
569
+ })
566
570
  }
567
571
 
568
572
  function dynamicImageSize(value: unknown) {
569
- if (!isRecord(value)) return undefined;
570
- const width = value.width === undefined ? undefined : coerceString(value.width);
571
- const height = value.height === undefined ? undefined : coerceString(value.height);
573
+ if (!isRecord(value)) return undefined
574
+ const width = value.width === undefined ? undefined : coerceString(value.width)
575
+ const height = value.height === undefined ? undefined : coerceString(value.height)
572
576
  return {
573
577
  width,
574
578
  height,
575
579
  ...(width && height ? { sizes: `${width}x${height}` } : {}),
576
- };
580
+ }
577
581
  }
578
582
 
579
583
  function routeSegmentsForDir(relativeDir: string) {
580
- if (!relativeDir) return [];
584
+ if (!relativeDir) return []
581
585
  return relativeDir
582
586
  .split('/')
583
587
  .filter(segment => segment && !isRouteGroup(segment) && !isSlotSegment(segment))
584
588
  .map(segment => {
585
- if (/^\[\[\.\.\.[^\]]+\]\]$/.test(segment)) return '*';
586
- if (/^\[\.\.\.[^\]]+\]$/.test(segment)) return '*';
587
- if (/^\[[^\]]+\]$/.test(segment)) return `:${segment.slice(1, -1)}`;
588
- return segment;
589
- });
589
+ if (/^\[\[\.\.\.[^\]]+\]\]$/.test(segment)) return '*'
590
+ if (/^\[\.\.\.[^\]]+\]$/.test(segment)) return '*'
591
+ if (/^\[[^\]]+\]$/.test(segment)) return `:${segment.slice(1, -1)}`
592
+ return segment
593
+ })
590
594
  }
591
595
 
592
596
  function outputDirForDir(relativeDir: string) {
593
- if (!relativeDir) return '';
597
+ if (!relativeDir) return ''
594
598
  return toPosixPath(
595
599
  relativeDir
596
600
  .split('/')
597
601
  .filter(segment => segment && !isRouteGroup(segment) && !isSlotSegment(segment))
598
602
  .map(segment => (segment.startsWith('[') && segment.endsWith(']') ? '-' : segment))
599
603
  .join('/'),
600
- );
604
+ )
601
605
  }
602
606
 
603
607
  function isRouteGroup(segment: string) {
604
- return segment.startsWith('(') && segment.endsWith(')');
608
+ return segment.startsWith('(') && segment.endsWith(')')
605
609
  }
606
610
 
607
611
  function isSlotSegment(segment: string) {
608
- return segment.startsWith('@');
612
+ return segment.startsWith('@')
609
613
  }
610
614
 
611
615
  function metadataAltText(file: string) {
612
- const extension = path.extname(file);
613
- const altFile = `${file.slice(0, -extension.length)}.alt.txt`;
614
- if (!existsSync(altFile)) return undefined;
615
- return readFileSync(altFile, 'utf8').trim() || undefined;
616
+ const extension = path.extname(file)
617
+ const altFile = `${file.slice(0, -extension.length)}.alt.txt`
618
+ if (!existsSync(altFile)) return undefined
619
+ return readFileSync(altFile, 'utf8').trim() || undefined
616
620
  }
617
621
 
618
622
  function imageInfo(file: string): { width?: number; height?: number; sizes?: string } | undefined {
619
- const extension = path.extname(file).toLowerCase();
620
- if (extension === '.svg') return { sizes: 'any' };
621
- const bytes = readFileSync(file);
622
- const png = pngInfo(bytes);
623
- if (png) return png;
624
- if (extension === '.png') return pngInfo(bytes);
625
- if (extension === '.ico') return icoInfo(bytes);
626
- return undefined;
623
+ const extension = path.extname(file).toLowerCase()
624
+ if (extension === '.svg') return { sizes: 'any' }
625
+ const bytes = readFileSync(file)
626
+ const png = pngInfo(bytes)
627
+ if (png) return png
628
+ if (extension === '.png') return pngInfo(bytes)
629
+ if (extension === '.ico') return icoInfo(bytes)
630
+ return undefined
627
631
  }
628
632
 
629
633
  function pngInfo(bytes: Buffer): { width?: number; height?: number; sizes?: string } | undefined {
@@ -634,107 +638,107 @@ function pngInfo(bytes: Buffer): { width?: number; height?: number; sizes?: stri
634
638
  bytes[2] !== 0x4e ||
635
639
  bytes[3] !== 0x47
636
640
  ) {
637
- return undefined;
641
+ return undefined
638
642
  }
639
- const width = bytes.readUInt32BE(16);
640
- const height = bytes.readUInt32BE(20);
641
- return { width, height, sizes: `${width}x${height}` };
643
+ const width = bytes.readUInt32BE(16)
644
+ const height = bytes.readUInt32BE(20)
645
+ return { width, height, sizes: `${width}x${height}` }
642
646
  }
643
647
 
644
648
  function icoInfo(bytes: Buffer): { width?: number; height?: number; sizes?: string } | undefined {
645
- if (bytes.length < 22) return undefined;
646
- if (bytes.readUInt16LE(0) !== 0) return undefined;
647
- const type = bytes.readUInt16LE(2);
648
- if (type !== 1 && type !== 2) return undefined;
649
- if (bytes.readUInt16LE(4) < 1) return undefined;
650
- const width = bytes[6] === 0 ? 256 : bytes[6];
651
- const height = bytes[7] === 0 ? 256 : bytes[7];
652
- return { width, height, sizes: `${width}x${height}` };
649
+ if (bytes.length < 22) return undefined
650
+ if (bytes.readUInt16LE(0) !== 0) return undefined
651
+ const type = bytes.readUInt16LE(2)
652
+ if (type !== 1 && type !== 2) return undefined
653
+ if (bytes.readUInt16LE(4) < 1) return undefined
654
+ const width = bytes[6] === 0 ? 256 : bytes[6]
655
+ const height = bytes[7] === 0 ? 256 : bytes[7]
656
+ return { width, height, sizes: `${width}x${height}` }
653
657
  }
654
658
 
655
659
  function contentTypeForFile(file: string) {
656
- const extension = path.extname(file).toLowerCase();
657
- if (file === 'manifest.json') return 'application/manifest+json';
658
- if (extension === '.ico') return 'image/x-icon';
659
- if (extension === '.jpg' || extension === '.jpeg') return 'image/jpeg';
660
- if (extension === '.png') return 'image/png';
661
- if (extension === '.svg') return 'image/svg+xml';
662
- if (extension === '.gif') return 'image/gif';
663
- if (extension === '.webp') return 'image/webp';
664
- if (extension === '.txt') return 'text/plain';
665
- if (extension === '.xml') return 'application/xml';
666
- if (extension === '.webmanifest') return 'application/manifest+json';
667
- return 'application/octet-stream';
660
+ const extension = path.extname(file).toLowerCase()
661
+ if (file === 'manifest.json') return 'application/manifest+json'
662
+ if (extension === '.ico') return 'image/x-icon'
663
+ if (extension === '.jpg' || extension === '.jpeg') return 'image/jpeg'
664
+ if (extension === '.png') return 'image/png'
665
+ if (extension === '.svg') return 'image/svg+xml'
666
+ if (extension === '.gif') return 'image/gif'
667
+ if (extension === '.webp') return 'image/webp'
668
+ if (extension === '.txt') return 'text/plain'
669
+ if (extension === '.xml') return 'application/xml'
670
+ if (extension === '.webmanifest') return 'application/manifest+json'
671
+ return 'application/octet-stream'
668
672
  }
669
673
 
670
674
  function listFilesSync(root: string): string[] {
671
- const entries = readdirSync(root, { withFileTypes: true });
675
+ const entries = readdirSync(root, { withFileTypes: true })
672
676
  return entries.flatMap(entry => {
673
- const file = path.join(root, entry.name);
674
- if (entry.isDirectory()) return listFilesSync(file);
675
- return entry.isFile() ? [file] : [];
676
- });
677
+ const file = path.join(root, entry.name)
678
+ if (entry.isDirectory()) return listFilesSync(file)
679
+ return entry.isFile() ? [file] : []
680
+ })
677
681
  }
678
682
 
679
683
  function djb2Hash(value: string) {
680
- let hash = 5381;
684
+ let hash = 5381
681
685
  for (let i = 0; i < value.length; i++) {
682
- hash = ((hash << 5) + hash + value.charCodeAt(i)) & 0xffffffff;
686
+ hash = ((hash << 5) + hash + value.charCodeAt(i)) & 0xffffffff
683
687
  }
684
- return hash >>> 0;
688
+ return hash >>> 0
685
689
  }
686
690
 
687
691
  interface DynamicMetadataModule {
688
- default?: (props?: MetadataRouteProps) => unknown;
689
- generateImageMetadata?: (props: { params: MetadataRouteParams }) => unknown;
690
- generateSitemaps?: (props: { params: MetadataRouteParams }) => unknown;
691
- alt?: string;
692
- size?: { width?: number; height?: number };
693
- contentType?: string;
692
+ default?: (props?: MetadataRouteProps) => unknown
693
+ generateImageMetadata?: (props: { params: MetadataRouteParams }) => unknown
694
+ generateSitemaps?: (props: { params: MetadataRouteParams }) => unknown
695
+ alt?: string
696
+ size?: { width?: number; height?: number }
697
+ contentType?: string
694
698
  }
695
699
 
696
700
  type MetadataRouteParams = Promise<Record<string, RouteParamValue>> &
697
- Record<string, RouteParamValue>;
701
+ Record<string, RouteParamValue>
698
702
 
699
703
  interface MetadataRouteProps {
700
- params: MetadataRouteParams;
701
- id?: Promise<string | number> & { valueOf(): string | number; toString(): string };
704
+ params: MetadataRouteParams
705
+ id?: Promise<string | number> & { valueOf(): string | number; toString(): string }
702
706
  }
703
707
 
704
708
  interface DynamicMetadataHandlerModule {
705
- params?: () => Promise<Record<string, RouteParamValue>[]>;
709
+ params?: () => Promise<Record<string, RouteParamValue>[]>
706
710
  GET: (
707
711
  request: Request,
708
712
  context: { params: Promise<Record<string, RouteParamValue>> },
709
- ) => Promise<Response>;
713
+ ) => Promise<Response>
710
714
  }
711
715
 
712
716
  export function metadataRouteHandlerModule(
713
717
  module: DynamicMetadataModule,
714
718
  route: RouteManifestEntry,
715
719
  ): DynamicMetadataHandlerModule | undefined {
716
- const metadata = route.metadataRoute;
717
- if (!metadata) return undefined;
720
+ const metadata = route.metadataRoute
721
+ if (!metadata) return undefined
718
722
 
719
723
  const handler: DynamicMetadataHandlerModule = {
720
724
  GET: async (_request, context) => metadataRouteResponse(module, metadata, await context.params),
721
- };
725
+ }
722
726
  if (metadata.generatedParam) {
723
- handler.params = () => metadataRouteStaticParams(module, route);
727
+ handler.params = () => metadataRouteStaticParams(module, route)
724
728
  }
725
- return handler;
729
+ return handler
726
730
  }
727
731
 
728
732
  async function metadataRouteStaticParams(
729
733
  module: DynamicMetadataModule,
730
734
  route: RouteManifestEntry,
731
735
  ): Promise<Record<string, RouteParamValue>[]> {
732
- const metadata = route.metadataRoute;
733
- const generatedParam = metadata?.generatedParam;
734
- if (!metadata || !generatedParam) return [{}];
735
- if (route.params.some(param => param !== generatedParam)) return [];
736
- const entries = await metadataGeneratedEntries(module, metadata, {});
737
- return entries.map(entry => ({ [generatedParam]: String(entry.id) }));
736
+ const metadata = route.metadataRoute
737
+ const generatedParam = metadata?.generatedParam
738
+ if (!metadata || !generatedParam) return [{}]
739
+ if (route.params.some(param => param !== generatedParam)) return []
740
+ const entries = await metadataGeneratedEntries(module, metadata, {})
741
+ return entries.map(entry => ({ [generatedParam]: String(entry.id) }))
738
742
  }
739
743
 
740
744
  async function metadataRouteResponse(
@@ -742,22 +746,22 @@ async function metadataRouteResponse(
742
746
  metadata: MetadataRouteEntry,
743
747
  params: Record<string, RouteParamValue>,
744
748
  ) {
745
- const generatedParam = metadata.generatedParam;
746
- const metadataId = generatedParam ? coerceRouteParam(params[generatedParam]) : undefined;
747
- const routeParams = generatedParam ? omitKey(params, generatedParam) : params;
749
+ const generatedParam = metadata.generatedParam
750
+ const metadataId = generatedParam ? coerceRouteParam(params[generatedParam]) : undefined
751
+ const routeParams = generatedParam ? omitKey(params, generatedParam) : params
748
752
  const generated = generatedParam
749
753
  ? await metadataGeneratedEntries(module, metadata, routeParams)
750
- : undefined;
754
+ : undefined
751
755
  const generatedEntry = metadataId
752
756
  ? generated?.find(entry => String(entry.id) === metadataId)
753
- : undefined;
754
- if (generatedParam && !generatedEntry) return new Response('Not Found', { status: 404 });
757
+ : undefined
758
+ if (generatedParam && !generatedEntry) return new Response('Not Found', { status: 404 })
755
759
 
756
- if (typeof module.default !== 'function') return new Response('Not Found', { status: 404 });
760
+ if (typeof module.default !== 'function') return new Response('Not Found', { status: 404 })
757
761
  const result = await module.default(
758
762
  metadataRouteProps(routeParams, metadataRouteId(metadata, metadataId, generatedEntry)),
759
- );
760
- return metadataResultResponse(result, metadata, module, generatedEntry);
763
+ )
764
+ return metadataResultResponse(result, metadata, module, generatedEntry)
761
765
  }
762
766
 
763
767
  async function metadataGeneratedEntries(
@@ -768,12 +772,12 @@ async function metadataGeneratedEntries(
768
772
  const result =
769
773
  metadata.kind === 'sitemap'
770
774
  ? await module.generateSitemaps?.({ params: paramsPromise(params) })
771
- : await module.generateImageMetadata?.({ params: paramsPromise(params) });
775
+ : await module.generateImageMetadata?.({ params: paramsPromise(params) })
772
776
  return Array.isArray(result)
773
777
  ? result.filter(
774
778
  (entry): entry is Record<string, unknown> => isRecord(entry) && entry.id !== undefined,
775
779
  )
776
- : [];
780
+ : []
777
781
  }
778
782
 
779
783
  function metadataRouteProps(
@@ -783,7 +787,7 @@ function metadataRouteProps(
783
787
  return {
784
788
  params: paramsPromise(params),
785
789
  ...(id !== undefined ? { id: idPromise(id) } : {}),
786
- };
790
+ }
787
791
  }
788
792
 
789
793
  function metadataRouteId(
@@ -791,20 +795,20 @@ function metadataRouteId(
791
795
  metadataId: string | undefined,
792
796
  generatedEntry: Record<string, unknown> | undefined,
793
797
  ) {
794
- if (metadata.kind === 'sitemap') return metadataId;
795
- const id = generatedEntry?.id;
796
- return typeof id === 'string' || typeof id === 'number' ? id : metadataId;
798
+ if (metadata.kind === 'sitemap') return metadataId
799
+ const id = generatedEntry?.id
800
+ return typeof id === 'string' || typeof id === 'number' ? id : metadataId
797
801
  }
798
802
 
799
803
  function paramsPromise(params: Record<string, RouteParamValue>) {
800
- return Object.assign(Promise.resolve(params), params);
804
+ return Object.assign(Promise.resolve(params), params)
801
805
  }
802
806
 
803
807
  function idPromise(id: string | number) {
804
808
  return Object.assign(Promise.resolve(id), {
805
809
  valueOf: () => id,
806
810
  toString: () => String(id),
807
- });
811
+ })
808
812
  }
809
813
 
810
814
  function metadataResultResponse(
@@ -817,45 +821,45 @@ function metadataResultResponse(
817
821
  return withMetadataHeaders(
818
822
  result,
819
823
  metadataContentType(metadata, module, generatedEntry, result.headers.get('content-type')),
820
- );
824
+ )
821
825
  }
822
826
  if (metadata.kind === 'robots') {
823
- return metadataTextResponse(serializeRobots(result), 'text/plain');
827
+ return metadataTextResponse(serializeRobots(result), 'text/plain')
824
828
  }
825
829
  if (metadata.kind === 'sitemap') {
826
- return metadataTextResponse(serializeSitemap(result), 'application/xml');
830
+ return metadataTextResponse(serializeSitemap(result), 'application/xml')
827
831
  }
828
832
  if (metadata.kind === 'manifest') {
829
- return metadataTextResponse(JSON.stringify(result), 'application/manifest+json');
833
+ return metadataTextResponse(JSON.stringify(result), 'application/manifest+json')
830
834
  }
831
835
 
832
- const body = metadataBinaryBody(result);
836
+ const body = metadataBinaryBody(result)
833
837
  return new Response(body, {
834
838
  headers: metadataHeaders(metadataContentType(metadata, module, generatedEntry)),
835
- });
839
+ })
836
840
  }
837
841
 
838
842
  function withMetadataHeaders(response: Response, contentType: string) {
839
- const headers = metadataHeaders(contentType, response.headers);
843
+ const headers = metadataHeaders(contentType, response.headers)
840
844
  return new Response(response.body, {
841
845
  status: response.status,
842
846
  statusText: response.statusText,
843
847
  headers,
844
- });
848
+ })
845
849
  }
846
850
 
847
851
  function metadataTextResponse(body: string, contentType: string) {
848
- return new Response(body, { headers: metadataHeaders(contentType) });
852
+ return new Response(body, { headers: metadataHeaders(contentType) })
849
853
  }
850
854
 
851
855
  function metadataHeaders(contentType: string, base?: Headers) {
852
- const headers = new Headers(base);
853
- if (!headers.has('content-type')) headers.set('content-type', contentType);
856
+ const headers = new Headers(base)
857
+ if (!headers.has('content-type')) headers.set('content-type', contentType)
854
858
  // Metadata routes always serve with the revalidating cache-control, even when
855
859
  // the underlying result (e.g. an ImageResponse) defaults to an immutable
856
860
  // long-lived value — Next overrides it for generated metadata routes.
857
- headers.set('cache-control', staticMetadataCacheControl);
858
- return headers;
861
+ headers.set('cache-control', staticMetadataCacheControl)
862
+ return headers
859
863
  }
860
864
 
861
865
  function metadataContentType(
@@ -864,155 +868,153 @@ function metadataContentType(
864
868
  generatedEntry?: Record<string, unknown>,
865
869
  responseType?: string | null,
866
870
  ) {
867
- if (responseType) return responseType;
868
- if (metadata.kind === 'robots') return 'text/plain';
869
- if (metadata.kind === 'sitemap') return 'application/xml';
870
- if (metadata.kind === 'manifest') return 'application/manifest+json';
871
- const generatedType = generatedEntry?.contentType;
872
- if (typeof generatedType === 'string') return generatedType;
873
- return module.contentType ?? 'image/png';
871
+ if (responseType) return responseType
872
+ if (metadata.kind === 'robots') return 'text/plain'
873
+ if (metadata.kind === 'sitemap') return 'application/xml'
874
+ if (metadata.kind === 'manifest') return 'application/manifest+json'
875
+ const generatedType = generatedEntry?.contentType
876
+ if (typeof generatedType === 'string') return generatedType
877
+ return module.contentType ?? 'image/png'
874
878
  }
875
879
 
876
880
  function metadataBinaryBody(result: unknown): BodyInit {
877
- if (result instanceof Blob) return result;
878
- if (result instanceof ArrayBuffer) return result;
879
- if (ArrayBuffer.isView(result)) return result as BodyInit;
880
- if (result instanceof ReadableStream) return result;
881
- if (typeof result === 'string') return result;
882
- if (result == null) return new Uint8Array();
883
- return JSON.stringify(result);
881
+ if (result instanceof Blob) return result
882
+ if (result instanceof ArrayBuffer) return result
883
+ if (ArrayBuffer.isView(result)) return result as BodyInit
884
+ if (result instanceof ReadableStream) return result
885
+ if (typeof result === 'string') return result
886
+ if (result == null) return new Uint8Array()
887
+ return JSON.stringify(result)
884
888
  }
885
889
 
886
890
  function serializeRobots(value: unknown) {
887
- if (!isRecord(value)) return '';
888
- const blocks = arrayOf(value.rules).flatMap(rule => (isRecord(rule) ? [robotRule(rule)] : []));
891
+ if (!isRecord(value)) return ''
892
+ const blocks = arrayOf(value.rules).flatMap(rule => (isRecord(rule) ? [robotRule(rule)] : []))
889
893
  const footer = [
890
894
  ...list(value.host).map(host => `Host: ${host}`),
891
895
  ...list(value.sitemap).map(sitemap => `Sitemap: ${sitemap}`),
892
- ];
893
- const body = [...blocks, footer.join('\n')].filter(Boolean).join('\n\n');
894
- if (!body) return '';
895
- return footer.length > 0 ? `${body}\n` : `${body}\n\n`;
896
+ ]
897
+ const body = [...blocks, footer.join('\n')].filter(Boolean).join('\n\n')
898
+ if (!body) return ''
899
+ return footer.length > 0 ? `${body}\n` : `${body}\n\n`
896
900
  }
897
901
 
898
902
  function robotRule(rule: Record<string, unknown>) {
899
- const agents = list(rule.userAgent ?? '*');
900
- const directives: string[] = [];
901
- for (const item of list(rule.allow)) directives.push(`Allow: ${item}`);
902
- for (const item of list(rule.disallow)) directives.push(`Disallow: ${item}`);
903
+ const agents = list(rule.userAgent ?? '*')
904
+ const directives: string[] = []
905
+ for (const item of list(rule.allow)) directives.push(`Allow: ${item}`)
906
+ for (const item of list(rule.disallow)) directives.push(`Disallow: ${item}`)
903
907
  if (rule.crawlDelay !== undefined)
904
- directives.push(`Crawl-delay: ${coerceString(rule.crawlDelay)}`);
905
- return [...agents.map(agent => `User-Agent: ${agent}`), ...directives].join('\n');
908
+ directives.push(`Crawl-delay: ${coerceString(rule.crawlDelay)}`)
909
+ return [...agents.map(agent => `User-Agent: ${agent}`), ...directives].join('\n')
906
910
  }
907
911
 
908
912
  function serializeSitemap(value: unknown) {
909
- const entries = Array.isArray(value) ? value : isRecord(value) ? [value] : [];
910
- const usesAlternates = entries.some(entry => isRecord(entry) && isRecord(entry.alternates));
911
- const usesImages = entries.some(entry => isRecord(entry) && arrayOf(entry.images).length > 0);
912
- const usesVideos = entries.some(entry => isRecord(entry) && arrayOf(entry.videos).length > 0);
913
+ const entries = Array.isArray(value) ? value : isRecord(value) ? [value] : []
914
+ const usesAlternates = entries.some(entry => isRecord(entry) && isRecord(entry.alternates))
915
+ const usesImages = entries.some(entry => isRecord(entry) && arrayOf(entry.images).length > 0)
916
+ const usesVideos = entries.some(entry => isRecord(entry) && arrayOf(entry.videos).length > 0)
913
917
  const attrs = [
914
918
  'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"',
915
919
  ...(usesAlternates ? ['xmlns:xhtml="http://www.w3.org/1999/xhtml"'] : []),
916
920
  ...(usesImages ? ['xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'] : []),
917
921
  ...(usesVideos ? ['xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"'] : []),
918
- ];
922
+ ]
919
923
  return `<?xml version="1.0" encoding="UTF-8"?>
920
924
  <urlset ${attrs.join(' ')}>
921
925
  ${entries.flatMap(entry => (isRecord(entry) ? [sitemapUrl(entry)] : [])).join('\n')}
922
926
  </urlset>
923
- `;
927
+ `
924
928
  }
925
929
 
926
930
  function sitemapUrl(entry: Record<string, unknown>) {
927
- const lines = ['<url>', `<loc>${escapeXml(entry.url)}</loc>`];
931
+ const lines = ['<url>', `<loc>${escapeXml(entry.url)}</loc>`]
928
932
  if (entry.lastModified)
929
- lines.push(`<lastmod>${escapeXml(sitemapDate(entry.lastModified))}</lastmod>`);
933
+ lines.push(`<lastmod>${escapeXml(sitemapDate(entry.lastModified))}</lastmod>`)
930
934
  if (entry.changeFrequency)
931
- lines.push(`<changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`);
932
- if (entry.priority !== undefined) lines.push(`<priority>${escapeXml(entry.priority)}</priority>`);
933
- const languages = isRecord(entry.alternates)
934
- ? entry.alternates.languages
935
- : undefined;
935
+ lines.push(`<changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`)
936
+ if (entry.priority !== undefined) lines.push(`<priority>${escapeXml(entry.priority)}</priority>`)
937
+ const languages = isRecord(entry.alternates) ? entry.alternates.languages : undefined
936
938
  if (isRecord(languages)) {
937
939
  for (const [language, href] of Object.entries(languages)) {
938
940
  lines.push(
939
941
  `<xhtml:link rel="alternate" hreflang="${escapeXml(language)}" href="${escapeXml(href)}" />`,
940
- );
942
+ )
941
943
  }
942
944
  }
943
945
  for (const image of arrayOf(entry.images)) {
944
- lines.push(sitemapImage(image));
946
+ lines.push(sitemapImage(image))
945
947
  }
946
948
  for (const video of arrayOf(entry.videos)) {
947
- if (isRecord(video)) lines.push(sitemapVideo(video));
949
+ if (isRecord(video)) lines.push(sitemapVideo(video))
948
950
  }
949
- lines.push('</url>');
950
- return lines.join('\n');
951
+ lines.push('</url>')
952
+ return lines.join('\n')
951
953
  }
952
954
 
953
955
  function sitemapImage(image: unknown) {
954
- const loc = isRecord(image) ? (image.url ?? image.loc ?? image.location) : image;
955
- return `<image:image>\n<image:loc>${escapeXml(loc)}</image:loc>\n</image:image>`;
956
+ const loc = isRecord(image) ? (image.url ?? image.loc ?? image.location) : image
957
+ return `<image:image>\n<image:loc>${escapeXml(loc)}</image:loc>\n</image:image>`
956
958
  }
957
959
 
958
960
  function sitemapVideo(video: Record<string, unknown>) {
959
- const lines = ['<video:video>'];
961
+ const lines = ['<video:video>']
960
962
  for (const [key, value] of Object.entries(video)) {
961
963
  if (isRecord(value) && 'content' in value) {
962
964
  const attrs = Object.entries(value)
963
965
  .filter(([name]) => name !== 'content')
964
966
  .map(([name, attr]) => ` ${name}="${escapeXml(attr)}"`)
965
- .join('');
966
- lines.push(`<video:${key}${attrs}>${escapeXml(value.content)}</video:${key}>`);
967
+ .join('')
968
+ lines.push(`<video:${key}${attrs}>${escapeXml(value.content)}</video:${key}>`)
967
969
  } else {
968
- lines.push(`<video:${key}>${escapeXml(value)}</video:${key}>`);
970
+ lines.push(`<video:${key}>${escapeXml(value)}</video:${key}>`)
969
971
  }
970
972
  }
971
- lines.push('</video:video>');
972
- return lines.join('\n');
973
+ lines.push('</video:video>')
974
+ return lines.join('\n')
973
975
  }
974
976
 
975
977
  function sitemapDate(value: unknown) {
976
- if (value instanceof Date) return value.toISOString();
977
- return coerceString(value);
978
+ if (value instanceof Date) return value.toISOString()
979
+ return coerceString(value)
978
980
  }
979
981
 
980
982
  function escapeXml(value: unknown) {
981
- return escapeHtml(coerceString(value));
983
+ return escapeHtml(coerceString(value))
982
984
  }
983
985
 
984
986
  function coerceString(value: unknown) {
985
- return stringifyHtmlValue(value);
987
+ return stringifyHtmlValue(value)
986
988
  }
987
989
 
988
990
  function list(value: unknown): string[] {
989
- if (value === undefined || value === null) return [];
990
- return (Array.isArray(value) ? value : [value]).map(coerceString).filter(Boolean);
991
+ if (value === undefined || value === null) return []
992
+ return (Array.isArray(value) ? value : [value]).map(coerceString).filter(Boolean)
991
993
  }
992
994
 
993
995
  function arrayOf(value: unknown): unknown[] {
994
- if (value === undefined) return [];
995
- return Array.isArray(value) ? value : [value];
996
+ if (value === undefined) return []
997
+ return Array.isArray(value) ? value : [value]
996
998
  }
997
999
 
998
1000
  function isRecord(value: unknown): value is Record<string, unknown> {
999
- return typeof value === 'object' && value !== null && !Array.isArray(value);
1001
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
1000
1002
  }
1001
1003
 
1002
1004
  function joinUrlPath(...parts: string[]) {
1003
- const path = parts.filter(Boolean).join('/').replace(/\/+/g, '/');
1004
- return path.startsWith('/') ? path : `/${path}`;
1005
+ const path = parts.filter(Boolean).join('/').replace(/\/+/g, '/')
1006
+ return path.startsWith('/') ? path : `/${path}`
1005
1007
  }
1006
1008
 
1007
1009
  function coerceRouteParam(value: RouteParamValue | undefined) {
1008
- if (value === undefined) return undefined;
1009
- return Array.isArray(value) ? value.join('/') : String(value);
1010
+ if (value === undefined) return undefined
1011
+ return Array.isArray(value) ? value.join('/') : String(value)
1010
1012
  }
1011
1013
 
1012
1014
  function omitKey<T>(value: Record<string, T>, key: string) {
1013
- const next: Record<string, T> = {};
1015
+ const next: Record<string, T> = {}
1014
1016
  for (const [name, item] of Object.entries(value)) {
1015
- if (name !== key) next[name] = item;
1017
+ if (name !== key) next[name] = item
1016
1018
  }
1017
- return next;
1019
+ return next
1018
1020
  }