astro 7.0.0-alpha.0 → 7.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/components/Code.astro +1 -1
  2. package/dist/actions/handler.d.ts +32 -0
  3. package/dist/actions/handler.js +45 -0
  4. package/dist/actions/runtime/server.js +1 -1
  5. package/dist/assets/build/generate.js +1 -1
  6. package/dist/assets/build/remote.d.ts +3 -2
  7. package/dist/assets/build/remote.js +16 -9
  8. package/dist/assets/endpoint/dev.js +1 -1
  9. package/dist/assets/endpoint/generic.js +8 -20
  10. package/dist/assets/endpoint/loadImage.d.ts +11 -0
  11. package/dist/assets/endpoint/loadImage.js +19 -0
  12. package/dist/assets/endpoint/shared.js +7 -2
  13. package/dist/assets/fonts/config.d.ts +4 -4
  14. package/dist/assets/fonts/core/optimize-fallbacks.js +38 -13
  15. package/dist/assets/fonts/definitions.d.ts +2 -2
  16. package/dist/assets/fonts/infra/system-fallbacks-provider.d.ts +2 -2
  17. package/dist/assets/fonts/infra/system-fallbacks-provider.js +46 -9
  18. package/dist/assets/fonts/types.d.ts +1 -0
  19. package/dist/assets/index.d.ts +1 -0
  20. package/dist/assets/index.js +2 -0
  21. package/dist/assets/internal.js +22 -3
  22. package/dist/assets/services/service.d.ts +1 -1
  23. package/dist/assets/services/service.js +9 -9
  24. package/dist/assets/services/sharp.js +53 -18
  25. package/dist/assets/utils/generateImageStylesCSS.js +26 -6
  26. package/dist/assets/utils/index.d.ts +1 -0
  27. package/dist/assets/utils/index.js +2 -0
  28. package/dist/assets/utils/inferSourceFormat.d.ts +8 -3
  29. package/dist/assets/utils/inferSourceFormat.js +15 -4
  30. package/dist/assets/utils/metadata.js +1 -1
  31. package/dist/assets/utils/redirectValidation.d.ts +48 -0
  32. package/dist/assets/utils/redirectValidation.js +48 -0
  33. package/dist/assets/utils/remoteProbe.js +25 -2
  34. package/dist/assets/utils/vendor/image-size/types/svg.js +1 -1
  35. package/dist/cli/add/index.js +7 -58
  36. package/dist/cli/dev/background.d.ts +16 -0
  37. package/dist/cli/dev/background.js +116 -0
  38. package/dist/cli/dev/index.js +82 -3
  39. package/dist/cli/dev/logs.d.ts +6 -0
  40. package/dist/cli/dev/logs.js +72 -0
  41. package/dist/cli/dev/status.d.ts +15 -0
  42. package/dist/cli/dev/status.js +27 -0
  43. package/dist/cli/dev/stop.d.ts +12 -0
  44. package/dist/cli/dev/stop.js +43 -0
  45. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  46. package/dist/container/index.js +18 -14
  47. package/dist/content/content-layer.js +16 -11
  48. package/dist/content/data-store.d.ts +1 -1
  49. package/dist/content/loaders/types.d.ts +1 -1
  50. package/dist/content/runtime-assets.d.ts +2 -2
  51. package/dist/content/runtime.d.ts +1 -1
  52. package/dist/content/runtime.js +9 -4
  53. package/dist/content/server-listeners.js +0 -4
  54. package/dist/content/types-generator.js +5 -1
  55. package/dist/content/utils.d.ts +1 -1
  56. package/dist/content/utils.js +1 -1
  57. package/dist/content/vite-plugin-content-assets.js +1 -0
  58. package/dist/content/vite-plugin-content-virtual-mod.js +9 -1
  59. package/dist/core/app/base.d.ts +42 -15
  60. package/dist/core/app/base.js +151 -375
  61. package/dist/core/app/dev/app.d.ts +3 -2
  62. package/dist/core/app/dev/app.js +4 -60
  63. package/dist/core/app/entrypoints/node.d.ts +1 -1
  64. package/dist/core/app/entrypoints/node.js +2 -0
  65. package/dist/core/app/entrypoints/virtual/dev.js +2 -0
  66. package/dist/core/app/entrypoints/virtual/prod.js +4 -1
  67. package/dist/core/app/node.d.ts +16 -0
  68. package/dist/core/app/node.js +59 -13
  69. package/dist/core/app/prepare-response.d.ts +11 -0
  70. package/dist/core/app/prepare-response.js +18 -0
  71. package/dist/core/app/render-options.d.ts +11 -0
  72. package/dist/core/app/render-options.js +11 -0
  73. package/dist/core/base-pipeline.d.ts +48 -1
  74. package/dist/core/base-pipeline.js +63 -8
  75. package/dist/core/build/app.d.ts +3 -4
  76. package/dist/core/build/app.js +3 -17
  77. package/dist/core/build/generate.js +8 -1
  78. package/dist/core/build/index.d.ts +0 -11
  79. package/dist/core/build/index.js +0 -3
  80. package/dist/core/build/internal.d.ts +7 -0
  81. package/dist/core/build/plugins/plugin-chunk-imports.d.ts +6 -0
  82. package/dist/core/build/plugins/plugin-chunk-imports.js +29 -17
  83. package/dist/core/build/plugins/plugin-css.js +40 -1
  84. package/dist/core/build/plugins/plugin-internals.js +1 -1
  85. package/dist/core/build/plugins/plugin-manifest.js +11 -1
  86. package/dist/core/build/static-build.js +22 -141
  87. package/dist/core/build/types.d.ts +0 -1
  88. package/dist/core/build/util.js +8 -1
  89. package/dist/core/build/vite-build-config.d.ts +28 -0
  90. package/dist/core/build/vite-build-config.js +165 -0
  91. package/dist/core/cache/handler.d.ts +29 -0
  92. package/dist/core/cache/handler.js +81 -0
  93. package/dist/core/compile/style.js +18 -1
  94. package/dist/core/config/index.d.ts +1 -1
  95. package/dist/core/config/index.js +4 -1
  96. package/dist/core/config/merge.js +4 -0
  97. package/dist/core/config/schemas/base.d.ts +25 -13
  98. package/dist/core/config/schemas/base.js +39 -10
  99. package/dist/core/config/schemas/relative.d.ts +76 -49
  100. package/dist/core/config/schemas/relative.js +2 -3
  101. package/dist/core/config/settings.js +2 -4
  102. package/dist/core/config/tsconfig.d.ts +24 -9
  103. package/dist/core/config/tsconfig.js +54 -45
  104. package/dist/core/config/validate.js +59 -0
  105. package/dist/core/constants.d.ts +27 -1
  106. package/dist/core/constants.js +14 -1
  107. package/dist/core/cookies/cookies.d.ts +7 -2
  108. package/dist/core/cookies/cookies.js +11 -4
  109. package/dist/core/cookies/response.d.ts +1 -1
  110. package/dist/core/cookies/response.js +1 -2
  111. package/dist/core/create-vite.js +18 -1
  112. package/dist/core/csp/config.js +17 -5
  113. package/dist/core/csp/runtime.js +6 -4
  114. package/dist/core/dev/dev.d.ts +1 -0
  115. package/dist/core/dev/dev.js +4 -13
  116. package/dist/core/dev/lockfile.d.ts +54 -0
  117. package/dist/core/dev/lockfile.js +93 -0
  118. package/dist/core/errors/build-handler.d.ts +17 -0
  119. package/dist/core/errors/build-handler.js +22 -0
  120. package/dist/core/errors/default-handler.d.ts +14 -0
  121. package/dist/core/errors/default-handler.js +144 -0
  122. package/dist/core/errors/dev-handler.d.ts +21 -0
  123. package/dist/core/errors/dev-handler.js +82 -0
  124. package/dist/core/errors/errors-data.d.ts +43 -38
  125. package/dist/core/errors/errors-data.js +79 -73
  126. package/dist/core/errors/handler.d.ts +9 -0
  127. package/dist/core/errors/handler.js +0 -0
  128. package/dist/core/errors/zod-error-map.js +30 -1
  129. package/dist/core/fetch/default-handler.d.ts +17 -0
  130. package/dist/core/fetch/default-handler.js +45 -0
  131. package/dist/core/fetch/fetch-state.d.ts +230 -0
  132. package/dist/core/fetch/fetch-state.js +896 -0
  133. package/dist/core/fetch/index.d.ts +61 -0
  134. package/dist/core/fetch/index.js +121 -0
  135. package/dist/core/fetch/types.d.ts +25 -0
  136. package/dist/core/fetch/types.js +0 -0
  137. package/dist/core/fetch/vite-plugin.d.ts +5 -0
  138. package/dist/core/fetch/vite-plugin.js +76 -0
  139. package/dist/core/hono/index.d.ts +21 -0
  140. package/dist/core/hono/index.js +101 -0
  141. package/dist/core/i18n/domain.d.ts +12 -0
  142. package/dist/core/i18n/domain.js +66 -0
  143. package/dist/core/i18n/handler.d.ts +18 -0
  144. package/dist/core/i18n/handler.js +122 -0
  145. package/dist/core/logger/core.d.ts +9 -1
  146. package/dist/core/logger/core.js +17 -1
  147. package/dist/core/messages/runtime.d.ts +0 -3
  148. package/dist/core/messages/runtime.js +1 -9
  149. package/dist/core/middleware/astro-middleware.d.ts +27 -0
  150. package/dist/core/middleware/astro-middleware.js +51 -0
  151. package/dist/core/module-loader/vite.js +1 -2
  152. package/dist/core/pages/handler.d.ts +20 -0
  153. package/dist/core/pages/handler.js +75 -0
  154. package/dist/core/preview/index.js +6 -5
  155. package/dist/core/preview/static-preview-server.js +5 -2
  156. package/dist/core/redirects/render.d.ts +2 -2
  157. package/dist/core/redirects/render.js +7 -8
  158. package/dist/core/render/params-and-props.js +2 -2
  159. package/dist/core/render/route-cache.d.ts +1 -0
  160. package/dist/core/render/route-cache.js +4 -4
  161. package/dist/core/render/slots.js +9 -2
  162. package/dist/core/rewrites/handler.d.ts +37 -0
  163. package/dist/core/rewrites/handler.js +67 -0
  164. package/dist/core/routing/3xx.js +8 -4
  165. package/dist/core/routing/create-manifest.js +11 -1
  166. package/dist/core/routing/handler.d.ts +17 -0
  167. package/dist/core/routing/handler.js +171 -0
  168. package/dist/core/routing/match.d.ts +0 -7
  169. package/dist/core/routing/match.js +0 -5
  170. package/dist/core/routing/parse-route.js +1 -1
  171. package/dist/core/routing/pattern.js +1 -1
  172. package/dist/core/routing/rewrite.js +2 -5
  173. package/dist/core/routing/router.d.ts +8 -0
  174. package/dist/core/routing/router.js +28 -0
  175. package/dist/core/routing/trailing-slash-handler.d.ts +18 -0
  176. package/dist/core/routing/trailing-slash-handler.js +67 -0
  177. package/dist/core/routing/validation.js +1 -1
  178. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +6 -1
  179. package/dist/core/server-islands/vite-plugin-server-islands.js +13 -3
  180. package/dist/core/session/config.d.ts +1 -1
  181. package/dist/core/session/drivers.d.ts +1 -1
  182. package/dist/core/session/handler.d.ts +11 -0
  183. package/dist/core/session/handler.js +33 -0
  184. package/dist/core/session/runtime.js +7 -2
  185. package/dist/core/util/normalized-url.d.ts +10 -0
  186. package/dist/core/util/normalized-url.js +24 -0
  187. package/dist/core/util/pathname.d.ts +10 -1
  188. package/dist/core/util/pathname.js +13 -4
  189. package/dist/environments.js +1 -1
  190. package/dist/events/session.d.ts +8 -0
  191. package/dist/events/session.js +11 -0
  192. package/dist/i18n/middleware.d.ts +10 -0
  193. package/dist/i18n/middleware.js +4 -88
  194. package/dist/i18n/utils.js +2 -2
  195. package/dist/jsx/rehype.d.ts +1 -1
  196. package/dist/manifest/virtual-module.js +3 -1
  197. package/dist/markdown/index.d.ts +4 -0
  198. package/dist/markdown/index.js +14 -0
  199. package/dist/prefetch/index.js +12 -7
  200. package/dist/prerender/utils.js +5 -1
  201. package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +9 -0
  202. package/dist/runtime/server/astro-island.js +57 -20
  203. package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
  204. package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
  205. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  206. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  207. package/dist/runtime/server/jsx.js +1 -1
  208. package/dist/runtime/server/render/common.js +10 -4
  209. package/dist/runtime/server/render/component.js +8 -6
  210. package/dist/runtime/server/render/head.js +1 -5
  211. package/dist/runtime/server/render/server-islands.js +2 -1
  212. package/dist/runtime/server/render/util.js +2 -2
  213. package/dist/runtime/server/scripts.js +6 -0
  214. package/dist/runtime/server/transition.d.ts +1 -6
  215. package/dist/runtime/server/transition.js +0 -8
  216. package/dist/transitions/events.d.ts +0 -14
  217. package/dist/transitions/events.js +0 -14
  218. package/dist/transitions/index.d.ts +0 -1
  219. package/dist/transitions/index.js +0 -2
  220. package/dist/transitions/vite-plugin-transitions.js +2 -4
  221. package/dist/types/public/config.d.ts +96 -14
  222. package/dist/types/public/content.d.ts +5 -5
  223. package/dist/types/public/index.d.ts +2 -1
  224. package/dist/types/public/integrations.d.ts +11 -3
  225. package/dist/types/public/internal.d.ts +1 -1
  226. package/dist/types/public/manifest.d.ts +1 -1
  227. package/dist/virtual-modules/i18n.d.ts +2 -2
  228. package/dist/virtual-modules/i18n.js +1 -1
  229. package/dist/vite-plugin-app/app.d.ts +13 -6
  230. package/dist/vite-plugin-app/app.js +51 -89
  231. package/dist/vite-plugin-app/createAstroServerApp.d.ts +3 -1
  232. package/dist/vite-plugin-app/createAstroServerApp.js +4 -3
  233. package/dist/vite-plugin-astro-server/plugin.js +11 -5
  234. package/dist/vite-plugin-astro-server/route-guard.d.ts +33 -0
  235. package/dist/vite-plugin-astro-server/route-guard.js +42 -23
  236. package/dist/vite-plugin-dev-status/index.d.ts +2 -0
  237. package/dist/vite-plugin-dev-status/index.js +15 -0
  238. package/dist/vite-plugin-head/index.js +36 -19
  239. package/dist/vite-plugin-hmr-reload/index.d.ts +1 -1
  240. package/dist/vite-plugin-hmr-reload/index.js +23 -1
  241. package/dist/vite-plugin-integrations-container/index.js +15 -6
  242. package/dist/vite-plugin-markdown/content-entry-type.js +7 -4
  243. package/dist/vite-plugin-markdown/images.js +9 -11
  244. package/dist/vite-plugin-markdown/index.js +12 -11
  245. package/dist/vite-plugin-utils/index.d.ts +1 -0
  246. package/dist/vite-plugin-utils/index.js +10 -1
  247. package/package.json +23 -16
  248. package/templates/content/types.d.ts +1 -0
  249. package/types/transitions.d.ts +0 -7
  250. package/dist/core/render-context.d.ts +0 -77
  251. package/dist/core/render-context.js +0 -826
@@ -1,48 +1,45 @@
1
1
  import {
2
- appendForwardSlash,
3
2
  collapseDuplicateLeadingSlashes,
4
- collapseDuplicateTrailingSlashes,
5
- hasFileExtension,
6
- isInternalPath,
7
- joinPaths,
8
3
  prependForwardSlash,
9
4
  removeTrailingForwardSlash
10
5
  } from "@astrojs/internal-helpers/path";
11
6
  import { matchPattern } from "@astrojs/internal-helpers/remote";
12
- import { normalizeTheLocale } from "../../i18n/index.js";
13
- import {
14
- clientAddressSymbol,
15
- DEFAULT_404_COMPONENT,
16
- NOOP_MIDDLEWARE_HEADER,
17
- REROUTABLE_STATUS_CODES,
18
- REROUTE_DIRECTIVE_HEADER,
19
- responseSentSymbol,
20
- REWRITE_DIRECTIVE_HEADER_KEY,
21
- ROUTE_TYPE_HEADER
22
- } from "../constants.js";
23
- import {
24
- AstroCookies,
25
- attachCookiesToResponse,
26
- getSetCookiesFromResponse
27
- } from "../cookies/index.js";
28
- import { getCookiesFromResponse } from "../cookies/response.js";
7
+ import { computePathnameFromDomain } from "../i18n/domain.js";
8
+ import { PipelineFeatures } from "../base-pipeline.js";
9
+ import { ASTRO_ERROR_HEADER, clientAddressSymbol } from "../constants.js";
10
+ import { getSetCookiesFromResponse } from "../cookies/index.js";
29
11
  import { AstroError, AstroErrorData } from "../errors/index.js";
30
12
  import { AstroIntegrationLogger } from "../logger/core.js";
31
- import { RenderContext } from "../render-context.js";
32
- import { redirectTemplate } from "../routing/3xx.js";
33
- import { ensure404Route } from "../routing/astro-designed-error-pages.js";
34
- import { routeHasHtmlExtension } from "../routing/helpers.js";
35
- import { matchRoute } from "../routing/match.js";
36
- import { applyCacheHeaders } from "../cache/runtime/cache.js";
37
- import { Router } from "../routing/router.js";
38
- import { PERSIST_SYMBOL } from "../session/runtime.js";
13
+ import { DefaultFetchHandler } from "../fetch/default-handler.js";
14
+ import { appSymbol } from "../constants.js";
15
+ import { DefaultErrorHandler } from "../errors/default-handler.js";
16
+ import { setRenderOptions } from "./render-options.js";
17
+ import { MultiLevelEncodingError } from "../util/pathname.js";
39
18
  class BaseApp {
40
19
  manifest;
41
20
  manifestData;
42
21
  pipeline;
43
22
  #adapterLogger;
44
23
  baseWithoutTrailingSlash;
45
- #router;
24
+ /**
25
+ * The handler that turns incoming `Request` objects into `Response`s.
26
+ * Defaults to a `DefaultFetchHandler` pinned to this app and can be
27
+ * overridden via `setFetchHandler` — typically by the bundled
28
+ * entrypoint after importing `virtual:astro:fetchable`.
29
+ */
30
+ #fetchHandler;
31
+ #errorHandler;
32
+ /**
33
+ * Whether a custom fetch handler (from `src/app.ts`) has been set
34
+ * via `setFetchHandler`. When false, the `DefaultFetchHandler` is
35
+ * in use and all features are implicitly active.
36
+ */
37
+ #hasCustomFetchHandler = false;
38
+ /**
39
+ * Whether the missing-feature check has already run. We only want
40
+ * to warn once — after the first request in dev, or at build end.
41
+ */
42
+ #featureCheckDone = false;
46
43
  get logger() {
47
44
  return this.pipeline.logger;
48
45
  }
@@ -57,17 +54,27 @@ class BaseApp {
57
54
  }
58
55
  constructor(manifest, streaming = true, ...args) {
59
56
  this.manifest = manifest;
60
- this.manifestData = { routes: manifest.routes.map((route) => route.routeData) };
61
57
  this.baseWithoutTrailingSlash = removeTrailingForwardSlash(manifest.base);
62
58
  this.pipeline = this.createPipeline(streaming, manifest, ...args);
63
- ensure404Route(this.manifestData);
64
- this.#router = this.createRouter(this.manifestData);
59
+ this.manifestData = this.pipeline.manifestData;
60
+ this.#fetchHandler = new DefaultFetchHandler(this);
61
+ this.#errorHandler = this.createErrorHandler();
65
62
  }
66
- async createRenderContext(payload) {
67
- return RenderContext.create(payload);
63
+ /**
64
+ * Override the fetch handler used to dispatch requests. Entrypoints
65
+ * call this with the default export of `virtual:astro:fetchable` to
66
+ * plug in a user-authored handler from `src/app.ts`.
67
+ */
68
+ setFetchHandler(handler) {
69
+ this.#fetchHandler = handler;
70
+ this.#hasCustomFetchHandler = !(handler instanceof DefaultFetchHandler);
68
71
  }
69
- getAdapterLogger() {
70
- return this.adapterLogger;
72
+ /**
73
+ * Returns the error handler strategy used by this app. Override to
74
+ * provide environment-specific behavior (dev overlay, build-time throws, etc.).
75
+ */
76
+ createErrorHandler() {
77
+ return new DefaultErrorHandler(this);
71
78
  }
72
79
  /**
73
80
  * Resets the cached adapter logger so it picks up a new logger instance.
@@ -97,7 +104,8 @@ class BaseApp {
97
104
  }
98
105
  set setManifestData(newManifestData) {
99
106
  this.manifestData = newManifestData;
100
- this.#router = this.createRouter(this.manifestData);
107
+ this.pipeline.manifestData = newManifestData;
108
+ this.pipeline.rebuildRouter();
101
109
  }
102
110
  removeBase(pathname) {
103
111
  pathname = collapseDuplicateLeadingSlashes(pathname);
@@ -107,21 +115,30 @@ class BaseApp {
107
115
  return pathname;
108
116
  }
109
117
  /**
110
- * It removes the base from the request URL, prepends it with a forward slash and attempts to decoded it.
111
- *
112
- * If the decoding fails, it logs the error and return the pathname as is.
113
- * @param request
118
+ * Decodes a pathname with `decodeURI`, falling back to the raw pathname when it
119
+ * contains an invalid percent-sequence (e.g. `%C0%AF`, an overlong-UTF-8 encoding of
120
+ * `/` commonly sent by path-traversal scanners). A raw `decodeURI()` would throw
121
+ * `URIError: URI malformed`, and because `match()` runs before `render()` that error
122
+ * escapes the adapter's request handler as an uncaught exception (HTTP 500) that user
123
+ * middleware can't catch.
114
124
  */
115
- getPathnameFromRequest(request) {
116
- const url = new URL(request.url);
117
- const pathname = prependForwardSlash(this.removeBase(url.pathname));
125
+ safeDecodeURI(pathname) {
118
126
  try {
119
127
  return decodeURI(pathname);
120
128
  } catch (e) {
121
- this.getAdapterLogger().error(e.toString());
129
+ this.adapterLogger.debug(e.toString());
122
130
  return pathname;
123
131
  }
124
132
  }
133
+ /**
134
+ * Extracts the base-stripped, decoded pathname from a request.
135
+ * Used by adapters to compute the pathname for dev-mode route matching.
136
+ */
137
+ getPathnameFromRequest(request) {
138
+ const url = new URL(request.url);
139
+ const pathname = prependForwardSlash(this.removeBase(url.pathname));
140
+ return this.safeDecodeURI(pathname);
141
+ }
125
142
  /**
126
143
  * Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
127
144
  * routes aren't returned, even if they are matched.
@@ -137,23 +154,20 @@ class BaseApp {
137
154
  if (!pathname) {
138
155
  pathname = prependForwardSlash(this.removeBase(url.pathname));
139
156
  }
140
- const match = this.#router.match(decodeURI(pathname), { allowWithoutBase: true });
141
- if (match.type !== "match") return void 0;
142
- const routeData = match.route;
157
+ const routeData = this.pipeline.matchRoute(this.safeDecodeURI(pathname));
158
+ if (!routeData) return void 0;
143
159
  if (allowPrerenderedRoutes) {
144
160
  return routeData;
145
- } else if (routeData.prerender) {
161
+ }
162
+ if (routeData.prerender) {
163
+ if (routeData.params.length > 0) {
164
+ const allMatches = this.pipeline.matchAllRoutes(this.safeDecodeURI(pathname));
165
+ return allMatches.find((r) => !r.prerender);
166
+ }
146
167
  return void 0;
147
168
  }
148
169
  return routeData;
149
170
  }
150
- createRouter(manifestData) {
151
- return new Router(manifestData.routes, {
152
- base: this.manifest.base,
153
- trailingSlash: this.manifest.trailingSlash,
154
- buildFormat: this.manifest.buildFormat
155
- });
156
- }
157
171
  /**
158
172
  * A matching route function to use in the development server.
159
173
  * Contrary to the `.match` function, this function resolves props and params, returning the correct
@@ -165,75 +179,14 @@ class BaseApp {
165
179
  return void 0;
166
180
  }
167
181
  computePathnameFromDomain(request) {
168
- let pathname = void 0;
169
- const url = new URL(request.url);
170
- if (this.manifest.i18n && (this.manifest.i18n.strategy === "domains-prefix-always" || this.manifest.i18n.strategy === "domains-prefix-other-locales" || this.manifest.i18n.strategy === "domains-prefix-always-no-redirect")) {
171
- let host = request.headers.get("X-Forwarded-Host");
172
- let protocol = request.headers.get("X-Forwarded-Proto");
173
- if (protocol) {
174
- protocol = protocol + ":";
175
- } else {
176
- protocol = url.protocol;
177
- }
178
- if (!host) {
179
- host = request.headers.get("Host");
180
- }
181
- if (host && protocol) {
182
- host = host.split(":")[0];
183
- try {
184
- let locale;
185
- const hostAsUrl = new URL(`${protocol}//${host}`);
186
- for (const [domainKey, localeValue] of Object.entries(
187
- this.manifest.i18n.domainLookupTable
188
- )) {
189
- const domainKeyAsUrl = new URL(domainKey);
190
- if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
191
- locale = localeValue;
192
- break;
193
- }
194
- }
195
- if (locale) {
196
- pathname = prependForwardSlash(
197
- joinPaths(normalizeTheLocale(locale), this.removeBase(url.pathname))
198
- );
199
- if (this.manifest.trailingSlash === "always") {
200
- pathname = appendForwardSlash(pathname);
201
- } else if (this.manifest.trailingSlash === "never") {
202
- pathname = removeTrailingForwardSlash(pathname);
203
- } else if (url.pathname.endsWith("/")) {
204
- pathname = appendForwardSlash(pathname);
205
- }
206
- }
207
- } catch (e) {
208
- this.logger.error(
209
- "router",
210
- `Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
211
- );
212
- this.logger.error("router", `Error: ${e}`);
213
- }
214
- }
215
- }
216
- return pathname;
217
- }
218
- redirectTrailingSlash(pathname) {
219
- const { trailingSlash } = this.manifest;
220
- if (pathname === "/" || isInternalPath(pathname)) {
221
- return pathname;
222
- }
223
- const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
224
- if (path !== pathname) {
225
- return path;
226
- }
227
- if (trailingSlash === "ignore") {
228
- return pathname;
229
- }
230
- if (trailingSlash === "always" && !hasFileExtension(pathname)) {
231
- return appendForwardSlash(pathname);
232
- }
233
- if (trailingSlash === "never") {
234
- return removeTrailingForwardSlash(pathname);
235
- }
236
- return pathname;
182
+ return computePathnameFromDomain(
183
+ request,
184
+ new URL(request.url),
185
+ this.manifest.i18n,
186
+ this.manifest.base,
187
+ this.manifest.trailingSlash,
188
+ this.logger
189
+ );
237
190
  }
238
191
  async render(request, {
239
192
  addCookieHeader = false,
@@ -244,28 +197,6 @@ class BaseApp {
244
197
  waitUntil
245
198
  } = {}) {
246
199
  await this.pipeline.getLogger();
247
- const timeStart = performance.now();
248
- const url = new URL(request.url);
249
- const redirect = this.redirectTrailingSlash(url.pathname);
250
- if (redirect !== url.pathname) {
251
- const status = request.method === "GET" ? 301 : 308;
252
- const response2 = new Response(
253
- redirectTemplate({
254
- status,
255
- relativeLocation: url.pathname,
256
- absoluteLocation: redirect,
257
- from: request.url
258
- }),
259
- {
260
- status,
261
- headers: {
262
- location: redirect + url.search
263
- }
264
- }
265
- );
266
- this.#prepareResponse(response2, { addCookieHeader });
267
- return response2;
268
- }
269
200
  if (routeData) {
270
201
  this.logger.debug(
271
202
  "router",
@@ -275,150 +206,71 @@ class BaseApp {
275
206
  this.logger.debug("router", "RouteData");
276
207
  this.logger.debug("router", routeData);
277
208
  }
278
- const resolvedRenderOptions = {
279
- addCookieHeader,
280
- clientAddress,
281
- prerenderedErrorPageFetch,
282
- locals,
283
- routeData,
284
- waitUntil
285
- };
286
209
  if (locals) {
287
210
  if (typeof locals !== "object") {
288
211
  const error = new AstroError(AstroErrorData.LocalsNotAnObject);
289
212
  this.logger.error(null, error.stack);
290
213
  return this.renderError(request, {
291
- ...resolvedRenderOptions,
214
+ addCookieHeader,
215
+ clientAddress,
216
+ prerenderedErrorPageFetch,
292
217
  // If locals are invalid, we don't want to include them when
293
218
  // rendering the error page
294
219
  locals: void 0,
220
+ routeData,
221
+ waitUntil,
295
222
  status: 500,
296
223
  error
297
224
  });
298
225
  }
299
226
  }
300
227
  if (!routeData) {
301
- if (this.isDev()) {
302
- const result = await this.devMatch(this.getPathnameFromRequest(request));
303
- if (result) {
304
- routeData = result.routeData;
305
- }
306
- } else {
307
- routeData = this.match(request);
228
+ const domainPathname = this.computePathnameFromDomain(request);
229
+ if (domainPathname) {
230
+ routeData = this.pipeline.matchRoute(this.safeDecodeURI(domainPathname));
308
231
  }
309
- this.logger.debug("router", "Astro matched the following route for " + request.url);
310
- this.logger.debug("router", "RouteData:\n" + routeData);
311
- }
312
- if (!routeData) {
313
- routeData = this.manifestData.routes.find(
314
- (route) => route.component === "404.astro" || route.component === DEFAULT_404_COMPONENT
315
- );
316
- }
317
- if (!routeData) {
318
- this.logger.debug("router", "Astro hasn't found routes that match " + request.url);
319
- this.logger.debug("router", "Here's the available routes:\n", this.manifestData);
320
- return this.renderError(request, {
321
- ...resolvedRenderOptions,
322
- status: 404
323
- });
324
232
  }
325
- let pathname = this.getPathnameFromRequest(request);
326
- if (this.isDev() && !routeHasHtmlExtension(routeData)) {
327
- pathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
328
- }
329
- const defaultStatus = this.getDefaultStatusCode(routeData, pathname);
233
+ const resolvedOptions = {
234
+ addCookieHeader,
235
+ clientAddress,
236
+ prerenderedErrorPageFetch,
237
+ locals,
238
+ routeData,
239
+ waitUntil
240
+ };
330
241
  let response;
331
- let session;
332
- let cache;
333
242
  try {
334
- const componentInstance = await this.pipeline.getComponentByRoute(routeData);
335
- const renderContext = await this.createRenderContext({
336
- pipeline: this.pipeline,
337
- locals,
338
- pathname,
339
- request,
340
- routeData,
341
- status: defaultStatus,
342
- clientAddress
343
- });
344
- session = renderContext.session;
345
- cache = renderContext.cache;
346
- if (this.pipeline.cacheProvider) {
347
- const cacheProvider = await this.pipeline.getCacheProvider();
348
- if (cacheProvider?.onRequest) {
349
- response = await cacheProvider.onRequest(
350
- {
351
- request,
352
- url: new URL(request.url),
353
- waitUntil: resolvedRenderOptions.waitUntil
354
- },
355
- async () => {
356
- const res = await renderContext.render(componentInstance);
357
- applyCacheHeaders(cache, res);
358
- return res;
359
- }
360
- );
361
- response.headers.delete("CDN-Cache-Control");
362
- response.headers.delete("Cache-Tag");
363
- } else {
364
- response = await renderContext.render(componentInstance);
365
- applyCacheHeaders(cache, response);
366
- }
243
+ if (this.#fetchHandler instanceof DefaultFetchHandler) {
244
+ Reflect.set(request, appSymbol, this);
245
+ response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
367
246
  } else {
368
- response = await renderContext.render(componentInstance);
247
+ setRenderOptions(request, resolvedOptions);
248
+ Reflect.set(request, appSymbol, this);
249
+ response = await this.#fetchHandler.fetch(request);
369
250
  }
370
- const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
371
- this.logThisRequest({
372
- pathname,
373
- method: request.method,
374
- statusCode: response.status,
375
- isRewrite,
376
- timeStart
377
- });
378
251
  } catch (err) {
379
- this.logger.error(null, err.stack || err.message || String(err));
380
- return this.renderError(request, {
381
- ...resolvedRenderOptions,
382
- status: 500,
383
- error: err
384
- });
385
- } finally {
386
- await session?.[PERSIST_SYMBOL]();
252
+ if (err instanceof MultiLevelEncodingError) {
253
+ return new Response("Bad Request", { status: 400 });
254
+ }
255
+ throw err;
387
256
  }
388
- if (REROUTABLE_STATUS_CODES.includes(response.status) && // If the body isn't null, that means the user sets the 404 status
389
- // but uses the current route to handle the 404
390
- response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
257
+ this.#warnMissingFeatures();
258
+ if (response.headers.get(ASTRO_ERROR_HEADER)) {
259
+ response.headers.delete(ASTRO_ERROR_HEADER);
391
260
  return this.renderError(request, {
392
- ...resolvedRenderOptions,
261
+ addCookieHeader,
262
+ clientAddress,
263
+ prerenderedErrorPageFetch,
264
+ locals,
265
+ routeData,
266
+ waitUntil,
393
267
  response,
394
268
  status: response.status,
395
- // We don't have an error to report here. Passing null means we pass nothing intentionally
396
- // while undefined means there's no error
397
269
  error: response.status === 500 ? null : void 0
398
270
  });
399
271
  }
400
- this.#prepareResponse(response, { addCookieHeader });
401
272
  return response;
402
273
  }
403
- #prepareResponse(response, { addCookieHeader }) {
404
- for (const headerName of [
405
- REROUTE_DIRECTIVE_HEADER,
406
- REWRITE_DIRECTIVE_HEADER_KEY,
407
- NOOP_MIDDLEWARE_HEADER,
408
- ROUTE_TYPE_HEADER
409
- ]) {
410
- if (response.headers.has(headerName)) {
411
- response.headers.delete(headerName);
412
- }
413
- }
414
- if (addCookieHeader) {
415
- for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) {
416
- response.headers.append("set-cookie", setCookieHeaderValue);
417
- }
418
- }
419
- this.logger.flush();
420
- Reflect.set(response, responseSentSymbol, true);
421
- }
422
274
  setCookieHeaders(response) {
423
275
  return getSetCookiesFromResponse(response);
424
276
  }
@@ -436,126 +288,50 @@ class BaseApp {
436
288
  static getSetCookieFromResponse = getSetCookiesFromResponse;
437
289
  /**
438
290
  * If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
439
- * This also handles pre-rendered /404 or /500 routes
291
+ * This also handles pre-rendered /404 or /500 routes.
292
+ *
293
+ * Delegates to the app's configured `ErrorHandler`. To customize behavior
294
+ * for a specific environment, override `createErrorHandler()` rather than
295
+ * this method.
440
296
  */
441
- async renderError(request, {
442
- status,
443
- response: originalResponse,
444
- skipMiddleware = false,
445
- error,
446
- ...resolvedRenderOptions
447
- }) {
448
- const errorRoutePath = `/${status}${this.manifest.trailingSlash === "always" ? "/" : ""}`;
449
- const errorRouteData = matchRoute(errorRoutePath, this.manifestData);
450
- const url = new URL(request.url);
451
- if (errorRouteData) {
452
- if (errorRouteData.prerender) {
453
- const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? ".html" : "";
454
- const statusURL = new URL(`${this.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`, url);
455
- if (statusURL.toString() !== request.url && resolvedRenderOptions.prerenderedErrorPageFetch) {
456
- const response2 = await resolvedRenderOptions.prerenderedErrorPageFetch(
457
- statusURL.toString()
458
- );
459
- const override = { status, removeContentEncodingHeaders: true };
460
- const newResponse = this.mergeResponses(response2, originalResponse, override);
461
- this.#prepareResponse(newResponse, resolvedRenderOptions);
462
- return newResponse;
463
- }
464
- }
465
- const mod = await this.pipeline.getComponentByRoute(errorRouteData);
466
- let session;
467
- try {
468
- const renderContext = await this.createRenderContext({
469
- locals: resolvedRenderOptions.locals,
470
- pipeline: this.pipeline,
471
- skipMiddleware,
472
- pathname: this.getPathnameFromRequest(request),
473
- request,
474
- routeData: errorRouteData,
475
- status,
476
- props: { error },
477
- clientAddress: resolvedRenderOptions.clientAddress
478
- });
479
- session = renderContext.session;
480
- const response2 = await renderContext.render(mod);
481
- const newResponse = this.mergeResponses(response2, originalResponse);
482
- this.#prepareResponse(newResponse, resolvedRenderOptions);
483
- return newResponse;
484
- } catch {
485
- if (skipMiddleware === false) {
486
- return this.renderError(request, {
487
- ...resolvedRenderOptions,
488
- status,
489
- response: originalResponse,
490
- skipMiddleware: true
491
- });
492
- }
493
- } finally {
494
- await session?.[PERSIST_SYMBOL]();
495
- }
496
- }
497
- const response = this.mergeResponses(new Response(null, { status }), originalResponse);
498
- this.#prepareResponse(response, resolvedRenderOptions);
499
- return response;
297
+ async renderError(request, options) {
298
+ return this.#errorHandler.renderError(request, options);
500
299
  }
501
- mergeResponses(newResponse, originalResponse, override) {
502
- let newResponseHeaders = newResponse.headers;
503
- if (override?.removeContentEncodingHeaders) {
504
- newResponseHeaders = new Headers(newResponseHeaders);
505
- newResponseHeaders.delete("Content-Encoding");
506
- newResponseHeaders.delete("Content-Length");
300
+ /**
301
+ * One-shot check: after the first request with a custom `src/app.ts`,
302
+ * compare `usedFeatures` against the manifest and warn about any
303
+ * configured features the user's pipeline doesn't call.
304
+ */
305
+ #warnMissingFeatures() {
306
+ if (this.#featureCheckDone || !this.#hasCustomFetchHandler) return;
307
+ this.#featureCheckDone = true;
308
+ const manifest = this.manifest;
309
+ const missing = [];
310
+ const used = this.pipeline.usedFeatures;
311
+ if (manifest.routes.some((r) => r.routeData.type === "redirect") && !(used & PipelineFeatures.redirects)) {
312
+ missing.push("redirects");
507
313
  }
508
- if (!originalResponse) {
509
- if (override !== void 0) {
510
- return new Response(newResponse.body, {
511
- status: override.status,
512
- statusText: newResponse.statusText,
513
- headers: newResponseHeaders
514
- });
515
- }
516
- return newResponse;
314
+ if (manifest.sessionConfig && !(used & PipelineFeatures.sessions)) {
315
+ missing.push("sessions");
517
316
  }
518
- const status = override?.status ? override.status : originalResponse.status === 200 ? newResponse.status : originalResponse.status;
519
- try {
520
- originalResponse.headers.delete("Content-type");
521
- originalResponse.headers.delete("Content-Length");
522
- originalResponse.headers.delete("Transfer-Encoding");
523
- } catch {
317
+ if (manifest.actions && !(used & PipelineFeatures.actions)) {
318
+ missing.push("actions");
524
319
  }
525
- const newHeaders = new Headers();
526
- const seen = /* @__PURE__ */ new Set();
527
- for (const [name, value] of originalResponse.headers) {
528
- newHeaders.append(name, value);
529
- seen.add(name.toLowerCase());
320
+ if (manifest.middleware && !(used & PipelineFeatures.middleware)) {
321
+ missing.push("middleware");
530
322
  }
531
- for (const [name, value] of newResponseHeaders) {
532
- if (!seen.has(name.toLowerCase())) {
533
- newHeaders.append(name, value);
534
- }
323
+ if (manifest.i18n && manifest.i18n.strategy !== "manual" && !(used & PipelineFeatures.i18n)) {
324
+ missing.push("i18n");
535
325
  }
536
- const mergedResponse = new Response(newResponse.body, {
537
- status,
538
- statusText: status === 200 ? newResponse.statusText : originalResponse.statusText,
539
- // If you're looking at here for possible bugs, it means that it's not a bug.
540
- // With the middleware, users can meddle with headers, and we should pass to the 404/500.
541
- // If users see something weird, it's because they are setting some headers they should not.
542
- //
543
- // Although, we don't want it to replace the content-type, because the error page must return `text/html`
544
- headers: newHeaders
545
- });
546
- const originalCookies = getCookiesFromResponse(originalResponse);
547
- const newCookies = getCookiesFromResponse(newResponse);
548
- if (originalCookies) {
549
- if (newCookies) {
550
- for (const cookieValue of AstroCookies.consume(newCookies)) {
551
- originalResponse.headers.append("set-cookie", cookieValue);
552
- }
553
- }
554
- attachCookiesToResponse(mergedResponse, originalCookies);
555
- } else if (newCookies) {
556
- attachCookiesToResponse(mergedResponse, newCookies);
326
+ if (manifest.cacheConfig && !(used & PipelineFeatures.cache)) {
327
+ missing.push("cache");
328
+ }
329
+ for (const feature of missing) {
330
+ this.logger.warn(
331
+ "router",
332
+ `Your project uses ${feature}, but your custom src/app.ts does not call the ${feature}() handler. This feature will not work unless you add it to your app.ts pipeline.`
333
+ );
557
334
  }
558
- return mergedResponse;
559
335
  }
560
336
  getDefaultStatusCode(routeData, pathname) {
561
337
  if (!routeData.pattern.test(pathname)) {
@@ -1,6 +1,7 @@
1
1
  import type { RouteData } from '../../../types/public/index.js';
2
+ import type { ErrorHandler } from '../../errors/handler.js';
2
3
  import type { AstroLogger } from '../../logger/core.js';
3
- import { BaseApp, type DevMatch, type LogRequestPayload, type RenderErrorOptions } from '../base.js';
4
+ import { BaseApp, type DevMatch, type LogRequestPayload } from '../base.js';
4
5
  import type { SSRManifest } from '../types.js';
5
6
  import { NonRunnablePipeline } from './pipeline.js';
6
7
  import type { RoutesList } from '../../../types/astro.js';
@@ -20,6 +21,6 @@ export declare class DevApp extends BaseApp<NonRunnablePipeline> {
20
21
  updateRoutes(newRoutesList: RoutesList): void;
21
22
  match(request: Request): RouteData | undefined;
22
23
  devMatch(pathname: string): Promise<DevMatch | undefined>;
23
- renderError(request: Request, { skipMiddleware, error, status, response: _response, ...resolvedRenderOptions }: RenderErrorOptions): Promise<Response>;
24
+ protected createErrorHandler(): ErrorHandler;
24
25
  logRequest({ pathname, method, statusCode, isRewrite, reqTime }: LogRequestPayload): void;
25
26
  }