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
@@ -0,0 +1,171 @@
1
+ import { ActionHandler } from "../../actions/handler.js";
2
+ import {
3
+ REROUTABLE_STATUS_CODES,
4
+ REROUTE_DIRECTIVE_HEADER,
5
+ REWRITE_DIRECTIVE_HEADER_KEY
6
+ } from "../constants.js";
7
+ import { TrailingSlashHandler } from "./trailing-slash-handler.js";
8
+ import { CacheHandler, provideCache } from "../cache/handler.js";
9
+ import { I18n } from "../i18n/handler.js";
10
+ import { AstroMiddleware } from "../middleware/astro-middleware.js";
11
+ import { PagesHandler } from "../pages/handler.js";
12
+ import { renderRedirect } from "../redirects/render.js";
13
+ import { provideSession } from "../session/handler.js";
14
+ import { prepareResponse } from "../app/prepare-response.js";
15
+ import { ALL_PIPELINE_FEATURES, PipelineFeatures } from "../base-pipeline.js";
16
+ class AstroHandler {
17
+ #app;
18
+ #trailingSlashHandler;
19
+ #actionHandler;
20
+ #astroMiddleware;
21
+ #pagesHandler;
22
+ #cacheHandler;
23
+ /** Bound callback for the middleware chain — created once, reused per request. */
24
+ #renderRouteCallback;
25
+ /**
26
+ * i18n post-processor. Only set when the app has i18n configured and
27
+ * the strategy is not `manual` — for the manual strategy users wire
28
+ * `astro:i18n.middleware(...)` into their own `onRequest`.
29
+ */
30
+ #i18n;
31
+ /** Whether sessions are configured on the manifest. */
32
+ #hasSession;
33
+ constructor(app) {
34
+ this.#app = app;
35
+ this.#trailingSlashHandler = new TrailingSlashHandler(app);
36
+ this.#actionHandler = new ActionHandler();
37
+ this.#astroMiddleware = new AstroMiddleware(app.pipeline);
38
+ this.#pagesHandler = new PagesHandler(app.pipeline);
39
+ this.#cacheHandler = new CacheHandler(app);
40
+ this.#renderRouteCallback = this.#actionsAndPages.bind(this);
41
+ this.#hasSession = !!app.manifest.sessionConfig;
42
+ const i18n = app.manifest.i18n;
43
+ if (i18n && i18n.strategy !== "manual") {
44
+ this.#i18n = new I18n(
45
+ i18n,
46
+ app.manifest.base,
47
+ app.manifest.trailingSlash,
48
+ app.manifest.buildFormat
49
+ );
50
+ }
51
+ }
52
+ /**
53
+ * Runs actions then pages — the callback at the bottom of the
54
+ * middleware chain. Bound once in the constructor to avoid
55
+ * per-request closure allocation.
56
+ */
57
+ #actionsAndPages(state, ctx) {
58
+ if (!state.skipMiddleware) {
59
+ const actionResult = this.#actionHandler.handle(ctx, state);
60
+ if (actionResult) {
61
+ return actionResult.then((response) => response ?? this.#pagesHandler.handle(state, ctx));
62
+ }
63
+ }
64
+ return this.#pagesHandler.handle(state, ctx);
65
+ }
66
+ async handle(state) {
67
+ state.pipeline.usedFeatures |= ALL_PIPELINE_FEATURES;
68
+ const trailingSlashRedirect = this.#trailingSlashHandler.handle(state);
69
+ if (trailingSlashRedirect) {
70
+ return trailingSlashRedirect;
71
+ }
72
+ if (!state.routeData) {
73
+ return this.#app.renderError(state.request, {
74
+ ...state.renderOptions,
75
+ status: 404,
76
+ pathname: state.pathname
77
+ });
78
+ }
79
+ return this.render(state);
80
+ }
81
+ /**
82
+ * Renders a response for the given `FetchState`. Assumes
83
+ * trailing-slash redirects and routeData resolution have already run.
84
+ *
85
+ * User-triggered rewrites (`Astro.rewrite` / `ctx.rewrite`) go through
86
+ * `Rewrites.execute` on the current `FetchState` — they mutate the
87
+ * existing state in place and re-run middleware + page dispatch.
88
+ */
89
+ async render(state) {
90
+ const routeData = state.routeData;
91
+ const pathname = state.pathname;
92
+ const request = state.request;
93
+ const { addCookieHeader } = state.renderOptions;
94
+ const defaultStatus = this.#app.getDefaultStatusCode(routeData, pathname);
95
+ state.status = defaultStatus;
96
+ let response;
97
+ try {
98
+ const sessionP = this.#hasSession ? provideSession(state) : void 0;
99
+ const cacheP = provideCache(state);
100
+ if (sessionP || cacheP) await Promise.all([sessionP, cacheP]);
101
+ state.pipeline.usedFeatures |= PipelineFeatures.sessions;
102
+ if (routeData.type === "redirect") {
103
+ const redirectResponse = await renderRedirect(state);
104
+ this.#app.logThisRequest({
105
+ pathname,
106
+ method: request.method,
107
+ statusCode: redirectResponse.status,
108
+ isRewrite: false,
109
+ timeStart: state.timeStart
110
+ });
111
+ prepareResponse(redirectResponse, { addCookieHeader });
112
+ this.#app.pipeline.logger.flush();
113
+ return redirectResponse;
114
+ }
115
+ if (!this.#app.pipeline.cacheProvider) {
116
+ this.#app.pipeline.usedFeatures |= PipelineFeatures.cache;
117
+ response = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
118
+ if (this.#i18n) {
119
+ response = await this.#i18n.finalize(state, response);
120
+ }
121
+ } else {
122
+ const runPipeline = async () => {
123
+ let res = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
124
+ if (this.#i18n) {
125
+ res = await this.#i18n.finalize(state, res);
126
+ }
127
+ return res;
128
+ };
129
+ response = await this.#cacheHandler.handle(state, runPipeline);
130
+ }
131
+ const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
132
+ this.#app.logThisRequest({
133
+ pathname,
134
+ method: request.method,
135
+ statusCode: response.status,
136
+ isRewrite,
137
+ timeStart: state.timeStart
138
+ });
139
+ } catch (err) {
140
+ this.#app.logger.error(null, err.stack || err.message || String(err));
141
+ return this.#app.renderError(request, {
142
+ ...state.renderOptions,
143
+ status: 500,
144
+ error: err,
145
+ pathname: state.pathname
146
+ });
147
+ } finally {
148
+ const finalize = state.finalizeAll();
149
+ if (finalize) await finalize;
150
+ }
151
+ if (REROUTABLE_STATUS_CODES.includes(response.status) && // If the body isn't null, that means the user sets the 404 status
152
+ // but uses the current route to handle the 404
153
+ response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
154
+ return this.#app.renderError(request, {
155
+ ...state.renderOptions,
156
+ response,
157
+ status: response.status,
158
+ // We don't have an error to report here. Passing null means we pass nothing intentionally
159
+ // while undefined means there's no error
160
+ error: response.status === 500 ? null : void 0,
161
+ pathname: state.pathname
162
+ });
163
+ }
164
+ prepareResponse(response, { addCookieHeader });
165
+ this.#app.pipeline.logger.flush();
166
+ return response;
167
+ }
168
+ }
169
+ export {
170
+ AstroHandler
171
+ };
@@ -18,10 +18,3 @@ export declare function isRoute404or500(route: RouteData): boolean;
18
18
  * @return {boolean} Returns true if the route's component is the server island component; otherwise, false.
19
19
  */
20
20
  export declare function isRouteServerIsland(route: RouteData): boolean;
21
- /**
22
- * Determines whether a given route is an external redirect.
23
- *
24
- * @param {RouteData} route - The route object to check.
25
- * @return {boolean} Returns true if the route is an external redirect; otherwise, false.
26
- */
27
- export declare function isRouteExternalRedirect(route: RouteData): boolean;
@@ -1,4 +1,3 @@
1
- import { redirectIsExternal } from "../redirects/render.js";
2
1
  import { SERVER_ISLAND_COMPONENT } from "../server-islands/endpoint.js";
3
2
  import { isRoute404, isRoute500 } from "./internal/route-errors.js";
4
3
  function matchRoute(pathname, manifest) {
@@ -23,12 +22,8 @@ function isRoute404or500(route) {
23
22
  function isRouteServerIsland(route) {
24
23
  return route.component === SERVER_ISLAND_COMPONENT;
25
24
  }
26
- function isRouteExternalRedirect(route) {
27
- return !!(route.type === "redirect" && route.redirect && redirectIsExternal(route.redirect));
28
- }
29
25
  export {
30
26
  isRoute404or500,
31
- isRouteExternalRedirect,
32
27
  isRouteServerIsland,
33
28
  matchAllRoutes,
34
29
  matchRoute
@@ -58,7 +58,7 @@ function joinSegments(segments) {
58
58
  const arr = segments.map((segment) => {
59
59
  return segment.map((part) => part.dynamic ? `[${part.content}]` : part.content).join("");
60
60
  });
61
- return `/${arr.join("/")}`.toLowerCase();
61
+ return `/${arr.join("/")}`;
62
62
  }
63
63
  export {
64
64
  parseRoute
@@ -16,7 +16,7 @@ function getPattern(segments, base, addTrailingSlash) {
16
16
  }).join("");
17
17
  const trailing = addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : "$";
18
18
  let initial = "\\/";
19
- if (addTrailingSlash === "never" && base !== "/") {
19
+ if (addTrailingSlash === "never" && base !== "/" && pathname !== "") {
20
20
  initial = "";
21
21
  }
22
22
  return new RegExp(`^${pathname || initial}${trailing}`);
@@ -101,7 +101,7 @@ function copyRequest(newUrl, oldRequest, isPrerendered, logger, routePattern) {
101
101
  signal: oldRequest.signal,
102
102
  keepalive: oldRequest.keepalive,
103
103
  // https://fetch.spec.whatwg.org/#dom-request-duplex
104
- // @ts-expect-error It isn't part of the types, but undici accepts it and it allows to carry over the body to a new request
104
+ // @ts-expect-error It isn't part of the types, but undici accepts it and it allows carrying over the body to a new request
105
105
  duplex: "half"
106
106
  }
107
107
  });
@@ -134,7 +134,7 @@ function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat)
134
134
  if (base !== "/") {
135
135
  const isBasePathRequest = urlPathname === base || urlPathname === removeTrailingForwardSlash(base);
136
136
  if (isBasePathRequest) {
137
- pathname = shouldAppendSlash ? "/" : "";
137
+ pathname = "/";
138
138
  } else if (urlPathname.startsWith(base)) {
139
139
  pathname = shouldAppendSlash ? appendForwardSlash(urlPathname) : removeTrailingForwardSlash(urlPathname);
140
140
  pathname = pathname.slice(base.length);
@@ -143,9 +143,6 @@ function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat)
143
143
  if (!pathname.startsWith("/") && shouldAppendSlash && urlPathname.endsWith("/")) {
144
144
  pathname = prependForwardSlash(pathname);
145
145
  }
146
- if (pathname === "/" && base !== "/" && !shouldAppendSlash) {
147
- pathname = "";
148
- }
149
146
  if (buildFormat === "file") {
150
147
  pathname = pathname.replace(/\.html$/, "");
151
148
  }
@@ -46,5 +46,13 @@ export declare class Router {
46
46
  match(inputPathname: string, { allowWithoutBase }?: {
47
47
  allowWithoutBase?: boolean;
48
48
  }): RouterMatch;
49
+ /**
50
+ * Returns all routes that match the given pathname, in priority order.
51
+ * Used when the first match (e.g. a prerendered route) cannot serve
52
+ * the request and subsequent matches need to be tried.
53
+ */
54
+ matchAll(inputPathname: string, { allowWithoutBase }?: {
55
+ allowWithoutBase?: boolean;
56
+ }): RouteData[];
49
57
  }
50
58
  export {};
@@ -57,6 +57,34 @@ class Router {
57
57
  const params = getParams(route, pathname);
58
58
  return { type: "match", route, params, pathname };
59
59
  }
60
+ /**
61
+ * Returns all routes that match the given pathname, in priority order.
62
+ * Used when the first match (e.g. a prerendered route) cannot serve
63
+ * the request and subsequent matches need to be tried.
64
+ */
65
+ matchAll(inputPathname, { allowWithoutBase = false } = {}) {
66
+ const normalized = getRedirectForPathname(inputPathname);
67
+ if (normalized.redirect) {
68
+ return [];
69
+ }
70
+ const baseResult = stripBase(
71
+ normalized.pathname,
72
+ this.#base,
73
+ this.#baseWithoutTrailingSlash,
74
+ this.#trailingSlash
75
+ );
76
+ if (!baseResult && !allowWithoutBase) {
77
+ return [];
78
+ }
79
+ let pathname = baseResult ?? normalized.pathname;
80
+ if (this.#buildFormat === "file") {
81
+ pathname = normalizeFileFormatPathname(pathname);
82
+ }
83
+ return this.#routes.filter((candidate) => {
84
+ if (candidate.pattern.test(pathname)) return true;
85
+ return candidate.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
86
+ });
87
+ }
60
88
  }
61
89
  function normalizeBase(base) {
62
90
  if (!base) return "/";
@@ -0,0 +1,18 @@
1
+ import type { BaseApp } from '../app/base.js';
2
+ import type { Pipeline } from '../base-pipeline.js';
3
+ import type { FetchState } from '../fetch/fetch-state.js';
4
+ /**
5
+ * Handles trailing-slash normalization for incoming requests. If the
6
+ * request's pathname does not match the app's configured `trailingSlash`
7
+ * policy, a redirect response is returned. Otherwise, returns `undefined`
8
+ * so the caller can continue processing the request.
9
+ */
10
+ export declare class TrailingSlashHandler {
11
+ #private;
12
+ constructor(app: BaseApp<Pipeline>);
13
+ /**
14
+ * Returns a redirect `Response` if the request pathname needs
15
+ * normalization, or `undefined` if no redirect is required.
16
+ */
17
+ handle(state: FetchState): Response | undefined;
18
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ appendForwardSlash,
3
+ collapseDuplicateTrailingSlashes,
4
+ hasFileExtension,
5
+ isInternalPath,
6
+ removeTrailingForwardSlash
7
+ } from "@astrojs/internal-helpers/path";
8
+ import { prepareResponse } from "../app/prepare-response.js";
9
+ import { redirectTemplate } from "./3xx.js";
10
+ class TrailingSlashHandler {
11
+ #app;
12
+ constructor(app) {
13
+ this.#app = app;
14
+ }
15
+ /**
16
+ * Returns a redirect `Response` if the request pathname needs
17
+ * normalization, or `undefined` if no redirect is required.
18
+ */
19
+ handle(state) {
20
+ const url = new URL(state.request.url);
21
+ const redirect = this.#redirectTrailingSlash(url.pathname);
22
+ if (redirect === url.pathname) {
23
+ return void 0;
24
+ }
25
+ const addCookieHeader = state.renderOptions.addCookieHeader;
26
+ const status = state.request.method === "GET" ? 301 : 308;
27
+ const response = new Response(
28
+ redirectTemplate({
29
+ status,
30
+ relativeLocation: url.pathname,
31
+ absoluteLocation: redirect,
32
+ from: state.request.url
33
+ }),
34
+ {
35
+ status,
36
+ headers: {
37
+ location: redirect + url.search
38
+ }
39
+ }
40
+ );
41
+ prepareResponse(response, { addCookieHeader });
42
+ return response;
43
+ }
44
+ #redirectTrailingSlash(pathname) {
45
+ const { trailingSlash } = this.#app.manifest;
46
+ if (pathname === "/" || isInternalPath(pathname)) {
47
+ return pathname;
48
+ }
49
+ const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
50
+ if (path !== pathname) {
51
+ return path;
52
+ }
53
+ if (trailingSlash === "ignore") {
54
+ return pathname;
55
+ }
56
+ if (trailingSlash === "always" && !hasFileExtension(pathname)) {
57
+ return appendForwardSlash(pathname);
58
+ }
59
+ if (trailingSlash === "never") {
60
+ return removeTrailingForwardSlash(pathname);
61
+ }
62
+ return pathname;
63
+ }
64
+ }
65
+ export {
66
+ TrailingSlashHandler
67
+ };
@@ -3,7 +3,7 @@ function validateDynamicRouteModule(mod, {
3
3
  ssr,
4
4
  route
5
5
  }) {
6
- if ((!ssr || route.prerender) && !mod.getStaticPaths) {
6
+ if ((!ssr || route.prerender) && route.origin !== "internal" && !mod.getStaticPaths) {
7
7
  throw new AstroError({
8
8
  ...AstroErrorData.GetStaticPathsRequired,
9
9
  location: { file: route.component }
@@ -1,4 +1,4 @@
1
- import type { Plugin as VitePlugin } from 'vite';
1
+ import type { BuildEnvironment, Plugin as VitePlugin } from 'vite';
2
2
  import type { AstroPluginOptions } from '../../types/astro.js';
3
3
  import type { ServerIslandsState } from './shared-state.js';
4
4
  export declare const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
@@ -6,3 +6,8 @@ export declare const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
6
6
  export declare function vitePluginServerIslands({ settings, serverIslandsState, }: AstroPluginOptions & {
7
7
  serverIslandsState: ServerIslandsState;
8
8
  }): VitePlugin;
9
+ /**
10
+ * Checks if the prerender environment discovered any server islands during the build.
11
+ * This encapsulates the logic of finding the server islands plugin and querying its state.
12
+ */
13
+ export declare function hasServerIslands(environment: BuildEnvironment): boolean;
@@ -28,6 +28,11 @@ function vitePluginServerIslands({
28
28
  return {
29
29
  name: "astro:server-islands",
30
30
  enforce: "post",
31
+ api: {
32
+ hasServerIslands() {
33
+ return serverIslandsState.hasIslands();
34
+ }
35
+ },
31
36
  config(_config, { command: _command }) {
32
37
  command = _command;
33
38
  },
@@ -111,9 +116,8 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
111
116
  }
112
117
  }
113
118
  if (id === RESOLVED_SERVER_ISLAND_MANIFEST) {
114
- if (command === "build" && settings.buildOutput) {
115
- const hasServerIslands = serverIslandsState.hasIslands();
116
- if (hasServerIslands && settings.buildOutput !== "server") {
119
+ if (command === "build") {
120
+ if (serverIslandsState.hasIslands() && !settings.adapter) {
117
121
  throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
118
122
  }
119
123
  }
@@ -163,8 +167,14 @@ export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
163
167
  }
164
168
  };
165
169
  }
170
+ function hasServerIslands(environment) {
171
+ const plugins = environment.config.plugins ?? [];
172
+ const serverIslandsPlugin = plugins.find((p) => p.name === "astro:server-islands");
173
+ return typeof serverIslandsPlugin?.api?.hasServerIslands === "function" && serverIslandsPlugin.api.hasServerIslands();
174
+ }
166
175
  export {
167
176
  SERVER_ISLAND_MANIFEST,
168
177
  SERVER_ISLAND_MAP_MARKER,
178
+ hasServerIslands,
169
179
  vitePluginServerIslands
170
180
  };
@@ -15,9 +15,9 @@ export declare const SessionSchema: z.ZodObject<{
15
15
  path: z.ZodOptional<z.ZodString>;
16
16
  maxAge: z.ZodOptional<z.ZodNumber>;
17
17
  sameSite: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
18
+ none: "none";
18
19
  strict: "strict";
19
20
  lax: "lax";
20
- none: "none";
21
21
  }>, z.ZodBoolean]>>;
22
22
  secure: z.ZodOptional<z.ZodBoolean>;
23
23
  }, z.core.$strip>, z.ZodPipe<z.ZodString, z.ZodTransform<{
@@ -1,6 +1,7 @@
1
1
  import type { SessionDriverConfig } from './types.js';
2
2
  export declare const sessionDrivers: {
3
3
  http: (config?: import("unstorage/drivers/http").HTTPOptions | undefined) => SessionDriverConfig;
4
+ fs: (config?: import("unstorage/drivers/fs").FSStorageOptions | undefined) => SessionDriverConfig;
4
5
  azureAppConfiguration: (config?: import("unstorage/drivers/azure-app-configuration").AzureAppConfigurationOptions | undefined) => SessionDriverConfig;
5
6
  azureCosmos: (config?: import("unstorage/drivers/azure-cosmos").AzureCosmosOptions | undefined) => SessionDriverConfig;
6
7
  azureKeyVault: (config?: import("unstorage/drivers/azure-key-vault").AzureKeyVaultOptions | undefined) => SessionDriverConfig;
@@ -14,7 +15,6 @@ export declare const sessionDrivers: {
14
15
  denoKVNode: (config?: import("unstorage/drivers/deno-kv-node").DenoKvNodeOptions | undefined) => SessionDriverConfig;
15
16
  denoKV: (config?: import("unstorage/drivers/deno-kv").DenoKvOptions | undefined) => SessionDriverConfig;
16
17
  fsLite: (config?: import("unstorage/drivers/fs-lite").FSStorageOptions | undefined) => SessionDriverConfig;
17
- fs: (config?: import("unstorage/drivers/fs").FSStorageOptions | undefined) => SessionDriverConfig;
18
18
  github: (config?: import("unstorage/drivers/github").GithubOptions | undefined) => SessionDriverConfig;
19
19
  indexedb: (config?: import("unstorage/drivers/indexedb").IDBKeyvalOptions | undefined) => SessionDriverConfig;
20
20
  localstorage: (config?: import("unstorage/drivers/localstorage").LocalStorageOptions | undefined) => SessionDriverConfig;
@@ -0,0 +1,11 @@
1
+ import type { FetchState } from '../fetch/fetch-state.js';
2
+ /**
3
+ * Registers a session provider on the given `FetchState`. When
4
+ * `state.resolve('session')` is first called, the `AstroSession` is
5
+ * created lazily. When `state.finalizeAll()` runs, any mutations are
6
+ * persisted.
7
+ *
8
+ * No-op (returns synchronously) if sessions are not configured on the
9
+ * pipeline, avoiding promise allocation on the hot path.
10
+ */
11
+ export declare function provideSession(state: FetchState): Promise<void> | void;
@@ -0,0 +1,33 @@
1
+ import { PipelineFeatures } from "../base-pipeline.js";
2
+ import { AstroSession, PERSIST_SYMBOL } from "./runtime.js";
3
+ const SESSION_KEY = "session";
4
+ function provideSession(state) {
5
+ state.pipeline.usedFeatures |= PipelineFeatures.sessions;
6
+ const pipeline = state.pipeline;
7
+ const config = pipeline.manifest.sessionConfig;
8
+ if (!config) return;
9
+ return provideSessionAsync(state, config);
10
+ }
11
+ async function provideSessionAsync(state, config) {
12
+ const pipeline = state.pipeline;
13
+ const driverFactory = await pipeline.getSessionDriver();
14
+ if (!driverFactory) return;
15
+ state.provide(SESSION_KEY, {
16
+ create() {
17
+ const cookies = state.cookies;
18
+ return new AstroSession({
19
+ cookies,
20
+ config,
21
+ runtimeMode: pipeline.runtimeMode,
22
+ driverFactory,
23
+ mockStorage: null
24
+ });
25
+ },
26
+ finalize(session) {
27
+ return session[PERSIST_SYMBOL]();
28
+ }
29
+ });
30
+ }
31
+ export {
32
+ provideSession
33
+ };
@@ -121,7 +121,8 @@ class AstroSession {
121
121
  * Deletes a session value.
122
122
  */
123
123
  delete(key) {
124
- this.#data?.delete(key);
124
+ this.#data ??= /* @__PURE__ */ new Map();
125
+ this.#data.delete(key);
125
126
  if (this.#partial) {
126
127
  this.#toDelete.add(key);
127
128
  }
@@ -272,11 +273,15 @@ class AstroSession {
272
273
  * If there is existing partial data, it will be merged into the new data object.
273
274
  */
274
275
  async #ensureData() {
275
- const storage = await this.#ensureStorage();
276
276
  if (this.#data && !this.#partial) {
277
277
  return this.#data;
278
278
  }
279
279
  this.#data ??= /* @__PURE__ */ new Map();
280
+ if (!this.#sessionID && !this.#cookies.get(this.#cookieName)?.value) {
281
+ this.#partial = false;
282
+ return this.#data;
283
+ }
284
+ const storage = await this.#ensureStorage();
280
285
  const raw = await storage.get(this.#ensureSessionID());
281
286
  if (!raw) {
282
287
  if (this.#sessionIDFromCookie) {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Creates a normalized URL from a request URL string.
3
+ * Decodes and validates the pathname, collapses duplicate slashes.
4
+ */
5
+ export declare function createNormalizedUrl(requestUrl: string): URL;
6
+ /**
7
+ * Normalizes an already-parsed URL in place: decodes and validates the
8
+ * pathname, collapses duplicate slashes. Returns the same URL object.
9
+ */
10
+ export declare function normalizeUrl(url: URL): URL;
@@ -0,0 +1,24 @@
1
+ import { collapseDuplicateSlashes } from "@astrojs/internal-helpers/path";
2
+ import { MultiLevelEncodingError, validateAndDecodePathname } from "./pathname.js";
3
+ function createNormalizedUrl(requestUrl) {
4
+ return normalizeUrl(new URL(requestUrl));
5
+ }
6
+ function normalizeUrl(url) {
7
+ try {
8
+ url.pathname = validateAndDecodePathname(url.pathname);
9
+ } catch (e) {
10
+ if (e instanceof MultiLevelEncodingError) {
11
+ throw e;
12
+ }
13
+ try {
14
+ url.pathname = decodeURI(url.pathname);
15
+ } catch {
16
+ }
17
+ }
18
+ url.pathname = collapseDuplicateSlashes(url.pathname);
19
+ return url;
20
+ }
21
+ export {
22
+ createNormalizedUrl,
23
+ normalizeUrl
24
+ };
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Error thrown when multi-level URL encoding is detected in a pathname.
3
+ * This is a distinct error type so callers can handle it specifically
4
+ * (e.g., returning a 400 response) rather than falling back to partial decoding.
5
+ */
6
+ export declare class MultiLevelEncodingError extends Error {
7
+ constructor();
8
+ }
1
9
  /**
2
10
  * Validates that a pathname is not multi-level encoded.
3
11
  * Detects if a pathname contains encoding that was encoded again (e.g., %2561dmin where %25 decodes to %).
@@ -5,6 +13,7 @@
5
13
  *
6
14
  * @param pathname - The pathname to validate
7
15
  * @returns The decoded pathname if valid
8
- * @throws Error if multi-level encoding is detected
16
+ * @throws MultiLevelEncodingError if multi-level encoding is detected
17
+ * @throws Error if the pathname contains invalid URL encoding
9
18
  */
10
19
  export declare function validateAndDecodePathname(pathname: string): string;
@@ -1,17 +1,26 @@
1
+ class MultiLevelEncodingError extends Error {
2
+ constructor() {
3
+ super("Multi-level URL encoding is not allowed");
4
+ this.name = "MultiLevelEncodingError";
5
+ }
6
+ }
7
+ const ENCODING_REGEX = /%25[0-9a-fA-F]{2}/;
1
8
  function validateAndDecodePathname(pathname) {
9
+ if (ENCODING_REGEX.test(pathname)) {
10
+ throw new MultiLevelEncodingError();
11
+ }
2
12
  let decoded;
3
13
  try {
4
14
  decoded = decodeURI(pathname);
5
15
  } catch (_e) {
6
16
  throw new Error("Invalid URL encoding");
7
17
  }
8
- const hasDecoding = decoded !== pathname;
9
- const decodedStillHasEncoding = /%[0-9a-fA-F]{2}/.test(decoded);
10
- if (hasDecoding && decodedStillHasEncoding) {
11
- throw new Error("Multi-level URL encoding is not allowed");
18
+ if (ENCODING_REGEX.test(decoded)) {
19
+ throw new MultiLevelEncodingError();
12
20
  }
13
21
  return decoded;
14
22
  }
15
23
  export {
24
+ MultiLevelEncodingError,
16
25
  validateAndDecodePathname
17
26
  };
@@ -1,6 +1,6 @@
1
1
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "./core/constants.js";
2
2
  function isAstroServerEnvironment(environment) {
3
- return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
3
+ return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro;
4
4
  }
5
5
  function isAstroClientEnvironment(environment) {
6
6
  return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client;
@@ -5,6 +5,14 @@ interface EventPayload {
5
5
  configKeys?: string[];
6
6
  flags?: string[];
7
7
  optionalIntegrations?: number;
8
+ /** Whether the CLI session was run by an AI coding agent. */
9
+ isAgentic?: boolean;
10
+ /** ID of the detected agent, e.g. "cursor-agent", "claude-code". */
11
+ agentId?: string;
12
+ /** Display name of the detected agent, e.g. "Cursor Agent", "Claude Code". */
13
+ agentName?: string;
14
+ /** Type of agentic environment: "agent", "interactive", or "hybrid". */
15
+ agentType?: string;
8
16
  }
9
17
  type ConfigInfoValue = string | boolean | string[] | undefined;
10
18
  type ConfigInfoRecord = Record<string, ConfigInfoValue>;