astro 4.4.13 → 4.4.14

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.
@@ -1524,7 +1524,7 @@ export interface AstroUserConfig {
1524
1524
  * @description
1525
1525
  * Enables pre-rendering your prefetched pages on the client in supported browsers.
1526
1526
  *
1527
- * This feature uses the experimental [Speculation Rules Web API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) and overrides the default `prefetch` behavior globally to prerender links on the client.
1527
+ * This feature uses the experimental [Speculation Rules Web API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) and enhances the default `prefetch` behavior globally to prerender links on the client.
1528
1528
  * You may wish to review the [possible risks when prerendering on the client](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API#unsafe_prefetching) before enabling this feature.
1529
1529
  *
1530
1530
  * Enable client side prerendering in your `astro.config.mjs` along with any desired `prefetch` configuration options:
@@ -2413,7 +2413,7 @@ export interface SSRResult {
2413
2413
  componentMetadata: Map<string, SSRComponentMetadata>;
2414
2414
  createAstro(Astro: AstroGlobalPartial, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
2415
2415
  resolve: (s: string) => Promise<string>;
2416
- response: ResponseInit;
2416
+ response: AstroGlobal['response'];
2417
2417
  renderers: SSRLoadedRenderer[];
2418
2418
  /**
2419
2419
  * Map of directive name (e.g. `load`) to the directive script code
@@ -14,7 +14,7 @@ export declare function createGetCollection({ contentCollectionToEntryMap, dataC
14
14
  contentCollectionToEntryMap: CollectionToEntryMap;
15
15
  dataCollectionToEntryMap: CollectionToEntryMap;
16
16
  getRenderEntryImport: GetEntryImport;
17
- }): (collection: string, filter?: ((entry: any) => unknown) | undefined) => Promise<any[] | undefined>;
17
+ }): (collection: string, filter?: ((entry: any) => unknown) | undefined) => Promise<any[]>;
18
18
  export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, }: {
19
19
  getEntryImport: GetEntryImport;
20
20
  getRenderEntryImport: GetEntryImport;
@@ -49,7 +49,7 @@ function createGetCollection({
49
49
  collection
50
50
  )} does not exist or is empty. Ensure a collection directory with this name exists.`
51
51
  );
52
- return;
52
+ return [];
53
53
  }
54
54
  const lazyImports = Object.values(
55
55
  type === "content" ? contentCollectionToEntryMap[collection] : dataCollectionToEntryMap[collection]
@@ -33,7 +33,7 @@ export declare abstract class Pipeline {
33
33
  /**
34
34
  * Used for `Astro.site`.
35
35
  */
36
- readonly site: string | undefined;
36
+ readonly site: URL | undefined;
37
37
  readonly internalMiddleware: MiddlewareHandler[];
38
38
  constructor(logger: Logger, manifest: SSRManifest,
39
39
  /**
@@ -51,7 +51,7 @@ export declare abstract class Pipeline {
51
51
  /**
52
52
  * Used for `Astro.site`.
53
53
  */
54
- site?: string | undefined);
54
+ site?: URL | undefined);
55
55
  abstract headElements(routeData: RouteData): Promise<HeadElements> | HeadElements;
56
56
  abstract componentMetadata(routeData: RouteData): Promise<SSRResult['componentMetadata']> | void;
57
57
  }
@@ -1,7 +1,7 @@
1
1
  import { createI18nMiddleware } from "../i18n/middleware.js";
2
2
  import { RouteCache } from "./render/route-cache.js";
3
3
  class Pipeline {
4
- constructor(logger, manifest, mode, renderers, resolve, serverLike, streaming, adapterName = manifest.adapterName, clientDirectives = manifest.clientDirectives, compressHTML = manifest.compressHTML, i18n = manifest.i18n, middleware = manifest.middleware, routeCache = new RouteCache(logger, mode), site = manifest.site) {
4
+ constructor(logger, manifest, mode, renderers, resolve, serverLike, streaming, adapterName = manifest.adapterName, clientDirectives = manifest.clientDirectives, compressHTML = manifest.compressHTML, i18n = manifest.i18n, middleware = manifest.middleware, routeCache = new RouteCache(logger, mode), site = manifest.site ? new URL(manifest.site) : void 0) {
5
5
  this.logger = logger;
6
6
  this.manifest = manifest;
7
7
  this.mode = mode;
@@ -428,7 +428,7 @@ function createBuildManifest(settings, internals, renderers, middleware) {
428
428
  renderers,
429
429
  base: settings.config.base,
430
430
  assetsPrefix: settings.config.build.assetsPrefix,
431
- site: settings.config.site ? new URL(settings.config.base, settings.config.site).toString() : settings.config.site,
431
+ site: settings.config.site,
432
432
  componentMetadata: internals.componentMetadata,
433
433
  i18n: i18nManifest,
434
434
  buildFormat: settings.config.build.format,
@@ -22,6 +22,8 @@ async function compile({
22
22
  normalizedFilename: normalizeFilename(filename, astroConfig.root),
23
23
  sourcemap: "both",
24
24
  internalURL: "astro/compiler-runtime",
25
+ // TODO: this is no longer neccessary for `Astro.site`
26
+ // but it somehow allows working around caching issues in content collections for some tests
25
27
  astroGlobalArgs: JSON.stringify(astroConfig.site),
26
28
  scopedStyleStrategy: astroConfig.scopedStyleStrategy,
27
29
  resultScopedSlot: true,
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.4.13";
1
+ const ASTRO_VERSION = "4.4.14";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
4
4
  const REROUTABLE_STATUS_CODES = [404, 500];
@@ -23,7 +23,7 @@ async function dev(inlineConfig) {
23
23
  base: restart.container.settings.config.base
24
24
  })
25
25
  );
26
- const currentVersion = "4.4.13";
26
+ const currentVersion = "4.4.14";
27
27
  if (currentVersion.includes("-")) {
28
28
  logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
29
29
  }
@@ -701,7 +701,7 @@ export declare const MiddlewareNotAResponse: {
701
701
  * @docs
702
702
  * @description
703
703
  *
704
- * Thrown in development mode when `locals` is overwritten with something that is not an object
704
+ * Thrown when `locals` is overwritten with something that is not an object
705
705
  *
706
706
  * For example:
707
707
  * ```ts
@@ -718,6 +718,17 @@ export declare const LocalsNotAnObject: {
718
718
  message: string;
719
719
  hint: string;
720
720
  };
721
+ /**
722
+ * @docs
723
+ * @description
724
+ * Thrown when a value is being set as the `headers` field on the `ResponseInit` object available as `Astro.response`.
725
+ */
726
+ export declare const AstroResponseHeadersReassigned: {
727
+ name: string;
728
+ title: string;
729
+ message: string;
730
+ hint: string;
731
+ };
721
732
  /**
722
733
  * @docs
723
734
  * @description
@@ -256,6 +256,12 @@ const LocalsNotAnObject = {
256
256
  message: "`locals` can only be assigned to an object. Other values like numbers, strings, etc. are not accepted.",
257
257
  hint: "If you tried to remove some information from the `locals` object, try to use `delete` or set the property to `undefined`."
258
258
  };
259
+ const AstroResponseHeadersReassigned = {
260
+ name: "AstroResponseHeadersReassigned",
261
+ title: "`Astro.response.headers` must not be reassigned.",
262
+ message: "Individual headers can be added to and removed from `Astro.response.headers`, but it must not be replaced with another instance of `Headers` altogether.",
263
+ hint: "Consider using `Astro.response.headers.add()`, and `Astro.response.headers.delete()`."
264
+ };
259
265
  const MiddlewareCantBeLoaded = {
260
266
  name: "MiddlewareCantBeLoaded",
261
267
  title: "Can't load the middleware.",
@@ -483,6 +489,7 @@ const UnknownError = { name: "UnknownError", title: "Unknown Error." };
483
489
  export {
484
490
  AstroGlobNoMatch,
485
491
  AstroGlobUsedOutside,
492
+ AstroResponseHeadersReassigned,
486
493
  CSSSyntaxError,
487
494
  CantRenderPage,
488
495
  ClientAddressNotAvailable,
@@ -36,7 +36,7 @@ function serverStart({
36
36
  host,
37
37
  base
38
38
  }) {
39
- const version = "4.4.13";
39
+ const version = "4.4.14";
40
40
  const localPrefix = `${dim("\u2503")} Local `;
41
41
  const networkPrefix = `${dim("\u2503")} Network `;
42
42
  const emptyPrefix = " ".repeat(11);
@@ -261,7 +261,7 @@ function printHelp({
261
261
  message.push(
262
262
  linebreak(),
263
263
  ` ${bgGreen(black(` ${commandName} `))} ${green(
264
- `v${"4.4.13"}`
264
+ `v${"4.4.14"}`
265
265
  )} ${headline}`
266
266
  );
267
267
  }
@@ -44,12 +44,7 @@ function createContext({
44
44
  return preferredLocaleList ??= computePreferredLocaleList(request, userDefinedLocales);
45
45
  },
46
46
  get currentLocale() {
47
- return currentLocale ??= computeCurrentLocale(
48
- route,
49
- userDefinedLocales,
50
- void 0,
51
- void 0
52
- );
47
+ return currentLocale ??= computeCurrentLocale(route, userDefinedLocales);
53
48
  },
54
49
  url,
55
50
  get clientAddress() {
@@ -3,7 +3,7 @@ import type { Pipeline } from '../base-pipeline.js';
3
3
  export { Pipeline } from '../base-pipeline.js';
4
4
  export { getParams, getProps } from './params-and-props.js';
5
5
  export { loadRenderer } from './renderer.js';
6
- export { createResult } from './result.js';
6
+ export { Slots } from './result.js';
7
7
  export interface SSROptions {
8
8
  /** The pipeline instance */
9
9
  pipeline: Pipeline;
@@ -1,10 +1,10 @@
1
1
  import { Pipeline } from "../base-pipeline.js";
2
2
  import { getParams, getProps } from "./params-and-props.js";
3
3
  import { loadRenderer } from "./renderer.js";
4
- import { createResult } from "./result.js";
4
+ import { Slots } from "./result.js";
5
5
  export {
6
6
  Pipeline,
7
- createResult,
7
+ Slots,
8
8
  getParams,
9
9
  getProps,
10
10
  loadRenderer
@@ -1,39 +1,9 @@
1
- import type { Locales, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro.js';
2
- import { type RoutingStrategies } from '../../i18n/utils.js';
3
- import { AstroCookies } from '../cookies/index.js';
1
+ import type { SSRResult } from '../../@types/astro.js';
2
+ import { type ComponentSlots } from '../../runtime/server/index.js';
4
3
  import type { Logger } from '../logger/core.js';
5
- export interface CreateResultArgs {
6
- /**
7
- * Used to provide better error messages for `Astro.clientAddress`
8
- */
9
- adapterName: string | undefined;
10
- /**
11
- * Value of Astro config's `output` option, true if "server" or "hybrid"
12
- */
13
- ssr: boolean;
14
- logger: Logger;
15
- params: Params;
16
- pathname: string;
17
- renderers: SSRLoadedRenderer[];
18
- clientDirectives: Map<string, string>;
19
- compressHTML: boolean;
20
- partial: boolean;
21
- resolve: (s: string) => Promise<string>;
22
- /**
23
- * Used for `Astro.site`
24
- */
25
- site: string | undefined;
26
- links: Set<SSRElement>;
27
- scripts: Set<SSRElement>;
28
- styles: Set<SSRElement>;
29
- componentMetadata: SSRResult['componentMetadata'];
30
- request: Request;
31
- status: number;
32
- locals: App.Locals;
33
- cookies: AstroCookies;
34
- locales: Locales | undefined;
35
- defaultLocale: string | undefined;
36
- route: string;
37
- strategy: RoutingStrategies | undefined;
4
+ export declare class Slots {
5
+ #private;
6
+ constructor(result: SSRResult, slots: ComponentSlots | null, logger: Logger);
7
+ has(name: string): boolean;
8
+ render(name: string, args?: any[]): Promise<any>;
38
9
  }
39
- export declare function createResult(args: CreateResultArgs): SSRResult;
@@ -1,13 +1,6 @@
1
- import {
2
- computeCurrentLocale,
3
- computePreferredLocale,
4
- computePreferredLocaleList
5
- } from "../../i18n/utils.js";
6
1
  import { renderSlotToString } from "../../runtime/server/index.js";
7
2
  import { renderJSX } from "../../runtime/server/jsx.js";
8
3
  import { chunkToString } from "../../runtime/server/render/index.js";
9
- import { clientAddressSymbol, responseSentSymbol } from "../constants.js";
10
- import { AstroCookies } from "../cookies/index.js";
11
4
  import { AstroError, AstroErrorData } from "../errors/index.js";
12
5
  function getFunctionExpression(slot) {
13
6
  if (!slot)
@@ -76,137 +69,6 @@ class Slots {
76
69
  return outHTML;
77
70
  }
78
71
  }
79
- function createResult(args) {
80
- const { params, request, resolve, locals } = args;
81
- const url = new URL(request.url);
82
- const headers = new Headers();
83
- headers.set("Content-Type", "text/html");
84
- const response = {
85
- status: args.status,
86
- statusText: "OK",
87
- headers
88
- };
89
- Object.defineProperty(response, "headers", {
90
- value: response.headers,
91
- enumerable: true,
92
- writable: false
93
- });
94
- let cookies = args.cookies;
95
- let preferredLocale = void 0;
96
- let preferredLocaleList = void 0;
97
- let currentLocale = void 0;
98
- const result = {
99
- styles: args.styles ?? /* @__PURE__ */ new Set(),
100
- scripts: args.scripts ?? /* @__PURE__ */ new Set(),
101
- links: args.links ?? /* @__PURE__ */ new Set(),
102
- componentMetadata: args.componentMetadata ?? /* @__PURE__ */ new Map(),
103
- renderers: args.renderers,
104
- clientDirectives: args.clientDirectives,
105
- compressHTML: args.compressHTML,
106
- partial: args.partial,
107
- pathname: args.pathname,
108
- cookies,
109
- /** This function returns the `Astro` faux-global */
110
- createAstro(astroGlobal, props, slots) {
111
- const astroSlots = new Slots(result, slots, args.logger);
112
- const Astro = {
113
- // @ts-expect-error
114
- __proto__: astroGlobal,
115
- get clientAddress() {
116
- if (!(clientAddressSymbol in request)) {
117
- if (args.adapterName) {
118
- throw new AstroError({
119
- ...AstroErrorData.ClientAddressNotAvailable,
120
- message: AstroErrorData.ClientAddressNotAvailable.message(args.adapterName)
121
- });
122
- } else {
123
- throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
124
- }
125
- }
126
- return Reflect.get(request, clientAddressSymbol);
127
- },
128
- get cookies() {
129
- if (cookies) {
130
- return cookies;
131
- }
132
- cookies = new AstroCookies(request);
133
- result.cookies = cookies;
134
- return cookies;
135
- },
136
- get preferredLocale() {
137
- if (preferredLocale) {
138
- return preferredLocale;
139
- }
140
- if (args.locales) {
141
- preferredLocale = computePreferredLocale(request, args.locales);
142
- return preferredLocale;
143
- }
144
- return void 0;
145
- },
146
- get preferredLocaleList() {
147
- if (preferredLocaleList) {
148
- return preferredLocaleList;
149
- }
150
- if (args.locales) {
151
- preferredLocaleList = computePreferredLocaleList(request, args.locales);
152
- return preferredLocaleList;
153
- }
154
- return void 0;
155
- },
156
- get currentLocale() {
157
- if (currentLocale) {
158
- return currentLocale;
159
- }
160
- if (args.locales) {
161
- currentLocale = computeCurrentLocale(
162
- url.pathname,
163
- args.locales,
164
- args.strategy,
165
- args.defaultLocale
166
- );
167
- if (currentLocale) {
168
- return currentLocale;
169
- }
170
- }
171
- return void 0;
172
- },
173
- params,
174
- props,
175
- locals,
176
- request,
177
- url,
178
- redirect(path, status) {
179
- if (request[responseSentSymbol]) {
180
- throw new AstroError({
181
- ...AstroErrorData.ResponseSentError
182
- });
183
- }
184
- return new Response(null, {
185
- status: status || 302,
186
- headers: {
187
- Location: path
188
- }
189
- });
190
- },
191
- response,
192
- slots: astroSlots
193
- };
194
- return Astro;
195
- },
196
- resolve,
197
- response,
198
- _metadata: {
199
- hasHydrationScript: false,
200
- rendererSpecificHydrationScripts: /* @__PURE__ */ new Set(),
201
- hasRenderedHead: false,
202
- hasDirectives: /* @__PURE__ */ new Set(),
203
- headInTree: false,
204
- extraHead: [],
205
- propagators: /* @__PURE__ */ new Set()
206
- }
207
- };
208
- return result;
209
- }
210
72
  export {
211
- createResult
73
+ Slots
212
74
  };
@@ -1,4 +1,4 @@
1
- import type { APIContext, ComponentInstance, MiddlewareHandler, RouteData } from '../@types/astro.js';
1
+ import type { APIContext, AstroGlobal, AstroGlobalPartial, ComponentInstance, MiddlewareHandler, RouteData, SSRResult } from '../@types/astro.js';
2
2
  import { AstroCookies } from './cookies/index.js';
3
3
  import { type Pipeline } from './render/index.js';
4
4
  export declare class RenderContext {
@@ -28,7 +28,9 @@ export declare class RenderContext {
28
28
  */
29
29
  render(componentInstance: ComponentInstance | undefined): Promise<Response>;
30
30
  createAPIContext(props: APIContext['props']): APIContext;
31
- createResult(mod: ComponentInstance): Promise<import("../@types/astro.js").SSRResult>;
31
+ createResult(mod: ComponentInstance): Promise<SSRResult>;
32
+ createAstro(result: SSRResult, astroGlobalPartial: AstroGlobalPartial, props: Record<string, any>, slotValues: Record<string, any> | null): AstroGlobal;
33
+ clientAddress(): string;
32
34
  computeCurrentLocale(): string | undefined;
33
35
  computePreferredLocale(): string | undefined;
34
36
  computePreferredLocaleList(): string[] | undefined;
@@ -10,16 +10,15 @@ import {
10
10
  REROUTE_DIRECTIVE_HEADER,
11
11
  ROUTE_TYPE_HEADER,
12
12
  clientAddressSymbol,
13
- clientLocalsSymbol
13
+ clientLocalsSymbol,
14
+ responseSentSymbol
14
15
  } from "./constants.js";
15
- import { attachCookiesToResponse } from "./cookies/index.js";
16
- import { AstroCookies } from "./cookies/index.js";
16
+ import { AstroCookies, attachCookiesToResponse } from "./cookies/index.js";
17
17
  import { AstroError, AstroErrorData } from "./errors/index.js";
18
18
  import { callMiddleware } from "./middleware/callMiddleware.js";
19
19
  import { sequence } from "./middleware/index.js";
20
20
  import { renderRedirect } from "./redirects/render.js";
21
- import { createResult } from "./render/index.js";
22
- import { getParams, getProps } from "./render/index.js";
21
+ import { Slots, getParams, getProps } from "./render/index.js";
23
22
  class RenderContext {
24
23
  constructor(pipeline, locals, middleware, pathname, request, routeData, status, cookies = new AstroCookies(request), params = getParams(routeData, pathname), url = new URL(request.url)) {
25
24
  this.pipeline = pipeline;
@@ -106,38 +105,15 @@ class RenderContext {
106
105
  const { cookies, params, pipeline, request, url } = this;
107
106
  const generator = `Astro v${ASTRO_VERSION}`;
108
107
  const redirect = (path, status = 302) => new Response(null, { status, headers: { Location: path } });
109
- const site = pipeline.site ? new URL(pipeline.site) : void 0;
110
108
  return {
111
109
  cookies,
110
+ get clientAddress() {
111
+ return renderContext.clientAddress();
112
+ },
112
113
  get currentLocale() {
113
114
  return renderContext.computeCurrentLocale();
114
115
  },
115
116
  generator,
116
- params,
117
- get preferredLocale() {
118
- return renderContext.computePreferredLocale();
119
- },
120
- get preferredLocaleList() {
121
- return renderContext.computePreferredLocaleList();
122
- },
123
- props,
124
- redirect,
125
- request,
126
- site,
127
- url,
128
- get clientAddress() {
129
- if (clientAddressSymbol in request) {
130
- return Reflect.get(request, clientAddressSymbol);
131
- }
132
- if (pipeline.adapterName) {
133
- throw new AstroError({
134
- ...AstroErrorData.ClientAddressNotAvailable,
135
- message: AstroErrorData.ClientAddressNotAvailable.message(pipeline.adapterName)
136
- });
137
- } else {
138
- throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
139
- }
140
- },
141
117
  get locals() {
142
118
  return renderContext.locals;
143
119
  },
@@ -149,52 +125,119 @@ class RenderContext {
149
125
  renderContext.locals = val;
150
126
  Reflect.set(request, clientLocalsSymbol, val);
151
127
  }
152
- }
128
+ },
129
+ params,
130
+ get preferredLocale() {
131
+ return renderContext.computePreferredLocale();
132
+ },
133
+ get preferredLocaleList() {
134
+ return renderContext.computePreferredLocaleList();
135
+ },
136
+ props,
137
+ redirect,
138
+ request,
139
+ site: pipeline.site,
140
+ url
153
141
  };
154
142
  }
155
143
  async createResult(mod) {
156
- const { cookies, locals, params, pathname, pipeline, request, routeData, status } = this;
157
- const {
158
- adapterName,
159
- clientDirectives,
160
- compressHTML,
161
- i18n,
162
- manifest,
163
- logger,
164
- renderers,
165
- resolve,
166
- site,
167
- serverLike
168
- } = pipeline;
144
+ const { cookies, pathname, pipeline, routeData, status } = this;
145
+ const { clientDirectives, compressHTML, manifest, renderers, resolve } = pipeline;
169
146
  const { links, scripts, styles } = await pipeline.headElements(routeData);
170
147
  const componentMetadata = await pipeline.componentMetadata(routeData) ?? manifest.componentMetadata;
171
- const { defaultLocale, locales, strategy } = i18n ?? {};
148
+ const headers = new Headers({ "Content-Type": "text/html" });
172
149
  const partial = Boolean(mod.partial);
173
- return createResult({
174
- adapterName,
150
+ const response = {
151
+ status,
152
+ statusText: "OK",
153
+ get headers() {
154
+ return headers;
155
+ },
156
+ // Disallow `Astro.response.headers = new Headers`
157
+ set headers(_) {
158
+ throw new AstroError(AstroErrorData.AstroResponseHeadersReassigned);
159
+ }
160
+ };
161
+ const result = {
175
162
  clientDirectives,
176
163
  componentMetadata,
177
164
  compressHTML,
178
165
  cookies,
179
- defaultLocale,
180
- locales,
181
- locals,
182
- logger,
166
+ /** This function returns the `Astro` faux-global */
167
+ createAstro: (astroGlobal, props, slots) => this.createAstro(result, astroGlobal, props, slots),
183
168
  links,
184
- params,
185
169
  partial,
186
170
  pathname,
187
171
  renderers,
188
172
  resolve,
189
- request,
190
- route: routeData.route,
191
- strategy,
192
- site,
173
+ response,
193
174
  scripts,
194
- ssr: serverLike,
195
- status,
196
- styles
197
- });
175
+ styles,
176
+ _metadata: {
177
+ hasHydrationScript: false,
178
+ rendererSpecificHydrationScripts: /* @__PURE__ */ new Set(),
179
+ hasRenderedHead: false,
180
+ hasDirectives: /* @__PURE__ */ new Set(),
181
+ headInTree: false,
182
+ extraHead: [],
183
+ propagators: /* @__PURE__ */ new Set()
184
+ }
185
+ };
186
+ return result;
187
+ }
188
+ createAstro(result, astroGlobalPartial, props, slotValues) {
189
+ const renderContext = this;
190
+ const { cookies, locals, params, pipeline, request, url } = this;
191
+ const { response } = result;
192
+ const redirect = (path, status = 302) => {
193
+ if (request[responseSentSymbol]) {
194
+ throw new AstroError({
195
+ ...AstroErrorData.ResponseSentError
196
+ });
197
+ }
198
+ return new Response(null, { status, headers: { Location: path } });
199
+ };
200
+ const slots = new Slots(result, slotValues, pipeline.logger);
201
+ const astroGlobalCombined = {
202
+ ...astroGlobalPartial,
203
+ cookies,
204
+ get clientAddress() {
205
+ return renderContext.clientAddress();
206
+ },
207
+ get currentLocale() {
208
+ return renderContext.computeCurrentLocale();
209
+ },
210
+ params,
211
+ get preferredLocale() {
212
+ return renderContext.computePreferredLocale();
213
+ },
214
+ get preferredLocaleList() {
215
+ return renderContext.computePreferredLocaleList();
216
+ },
217
+ props,
218
+ locals,
219
+ redirect,
220
+ request,
221
+ response,
222
+ slots,
223
+ site: pipeline.site,
224
+ url
225
+ };
226
+ return astroGlobalCombined;
227
+ }
228
+ clientAddress() {
229
+ const { pipeline, request } = this;
230
+ if (clientAddressSymbol in request) {
231
+ return Reflect.get(request, clientAddressSymbol);
232
+ }
233
+ if (pipeline.adapterName) {
234
+ throw new AstroError({
235
+ ...AstroErrorData.ClientAddressNotAvailable,
236
+ message: AstroErrorData.ClientAddressNotAvailable.message(pipeline.adapterName)
237
+ });
238
+ } else {
239
+ throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
240
+ }
198
241
  }
199
242
  /**
200
243
  * API Context may be created multiple times per request, i18n data needs to be computed only once.
@@ -210,12 +253,8 @@ class RenderContext {
210
253
  if (!i18n)
211
254
  return;
212
255
  const { defaultLocale, locales, strategy } = i18n;
213
- return this.#currentLocale ??= computeCurrentLocale(
214
- routeData.route,
215
- locales,
216
- strategy,
217
- defaultLocale
218
- );
256
+ const fallbackTo = strategy === "pathname-prefix-other-locales" || strategy === "domains-prefix-other-locales" ? defaultLocale : void 0;
257
+ return this.#currentLocale ??= computeCurrentLocale(routeData.route, locales) ?? computeCurrentLocale(url.pathname, locales) ?? fallbackTo;
219
258
  }
220
259
  #preferredLocale;
221
260
  computePreferredLocale() {
@@ -20,9 +20,11 @@ function countOccurrences(needle, haystack) {
20
20
  }
21
21
  return count;
22
22
  }
23
+ const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
24
+ const ROUTE_SPREAD = /^\.{3}.+$/;
23
25
  function getParts(part, file) {
24
26
  const result = [];
25
- part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
27
+ part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
26
28
  if (!str)
27
29
  return;
28
30
  const dynamic = i % 2 === 1;
@@ -33,7 +35,7 @@ function getParts(part, file) {
33
35
  result.push({
34
36
  content,
35
37
  dynamic,
36
- spread: dynamic && /^\.{3}.+$/.test(content)
38
+ spread: dynamic && ROUTE_SPREAD.test(content)
37
39
  });
38
40
  });
39
41
  return result;
@@ -116,6 +118,11 @@ function routeComparator(a, b) {
116
118
  if (aIsStatic !== bIsStatic) {
117
119
  return aIsStatic ? -1 : 1;
118
120
  }
121
+ const aAllDynamic = aSegment.every((part) => part.dynamic);
122
+ const bAllDynamic = bSegment.every((part) => part.dynamic);
123
+ if (aAllDynamic !== bAllDynamic) {
124
+ return aAllDynamic ? 1 : -1;
125
+ }
119
126
  const aHasSpread = aSegment.some((part) => part.spread);
120
127
  const bHasSpread = bSegment.some((part) => part.spread);
121
128
  if (aHasSpread !== bHasSpread) {
@@ -20,7 +20,7 @@ export declare function parseLocale(header: string): BrowserLocale[];
20
20
  */
21
21
  export declare function computePreferredLocale(request: Request, locales: Locales): string | undefined;
22
22
  export declare function computePreferredLocaleList(request: Request, locales: Locales): string[];
23
- export declare function computeCurrentLocale(pathname: string, locales: Locales, routingStrategy: RoutingStrategies | undefined, defaultLocale: string | undefined): undefined | string;
23
+ export declare function computeCurrentLocale(pathname: string, locales: Locales): undefined | string;
24
24
  export type RoutingStrategies = 'pathname-prefix-always' | 'pathname-prefix-other-locales' | 'pathname-prefix-always-no-redirect' | 'domains-prefix-always' | 'domains-prefix-other-locales' | 'domains-prefix-always-no-redirect';
25
25
  export declare function toRoutingStrategy(i18n: NonNullable<AstroConfig['i18n']>): RoutingStrategies;
26
26
  export {};
@@ -109,7 +109,7 @@ function computePreferredLocaleList(request, locales) {
109
109
  }
110
110
  return result;
111
111
  }
112
- function computeCurrentLocale(pathname, locales, routingStrategy, defaultLocale) {
112
+ function computeCurrentLocale(pathname, locales) {
113
113
  for (const segment of pathname.split("/")) {
114
114
  for (const locale of locales) {
115
115
  if (typeof locale === "string") {
@@ -131,10 +131,6 @@ function computeCurrentLocale(pathname, locales, routingStrategy, defaultLocale)
131
131
  }
132
132
  }
133
133
  }
134
- if (routingStrategy === "pathname-prefix-other-locales" || routingStrategy === "domains-prefix-other-locales") {
135
- return defaultLocale;
136
- }
137
- return void 0;
138
134
  }
139
135
  function toRoutingStrategy(i18n) {
140
136
  let { routing, domains } = i18n;
@@ -200,6 +200,15 @@ function appendSpeculationRules(url) {
200
200
  source: "list",
201
201
  urls: [url]
202
202
  }
203
+ ],
204
+ // Currently, adding `prefetch` is required to fallback if `prerender` fails.
205
+ // Possibly will be automatic in the future, in which case it can be removed.
206
+ // https://github.com/WICG/nav-speculation/issues/162#issuecomment-1977818473
207
+ prefetch: [
208
+ {
209
+ source: "list",
210
+ urls: [url]
211
+ }
203
212
  ]
204
213
  });
205
214
  document.head.append(script);
@@ -35,7 +35,24 @@ const a11y_required_attributes = {
35
35
  img: ["alt"],
36
36
  object: ["title", "aria-label", "aria-labelledby"]
37
37
  };
38
- const interactiveElements = ["button", "details", "embed", "iframe", "label", "select", "textarea"];
38
+ const MAYBE_INTERACTIVE = /* @__PURE__ */ new Map([
39
+ ["a", "href"],
40
+ ["input", "type"],
41
+ ["audio", "controls"],
42
+ ["img", "usemap"],
43
+ ["object", "usemap"],
44
+ ["video", "controls"]
45
+ ]);
46
+ const interactiveElements = [
47
+ "button",
48
+ "details",
49
+ "embed",
50
+ "iframe",
51
+ "label",
52
+ "select",
53
+ "textarea",
54
+ ...MAYBE_INTERACTIVE.keys()
55
+ ];
39
56
  const labellableElements = ["input", "meter", "output", "progress", "select", "textarea"];
40
57
  const aria_non_interactive_roles = [
41
58
  "alert",
@@ -170,6 +187,13 @@ const ariaRoles = new Set(
170
187
  " "
171
188
  )
172
189
  );
190
+ function isInteractive(element) {
191
+ const attribute = MAYBE_INTERACTIVE.get(element.localName);
192
+ if (attribute) {
193
+ return element.hasAttribute(attribute);
194
+ }
195
+ return true;
196
+ }
173
197
  const a11y = [
174
198
  {
175
199
  code: "a11y-accesskey",
@@ -390,6 +414,8 @@ const a11y = [
390
414
  message: "Interactive HTML elements like `<a>` and `<button>` cannot use non-interactive roles like `heading`, `list`, `menu`, and `toolbar`.",
391
415
  selector: `[role]:is(${interactiveElements.join(",")})`,
392
416
  match(element) {
417
+ if (!isInteractive(element))
418
+ return false;
393
419
  const role = element.getAttribute("role");
394
420
  if (!role)
395
421
  return false;
@@ -405,6 +431,8 @@ const a11y = [
405
431
  message: "Interactive roles should not be used to convert a non-interactive element to an interactive element",
406
432
  selector: `[role]:not(${interactiveElements.join(",")})`,
407
433
  match(element) {
434
+ if (!isInteractive(element))
435
+ return false;
408
436
  const role = element.getAttribute("role");
409
437
  if (!role)
410
438
  return false;
@@ -426,6 +454,8 @@ const a11y = [
426
454
  const isScrollable = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
427
455
  if (isScrollable)
428
456
  return false;
457
+ if (!isInteractive(element))
458
+ return false;
429
459
  if (!interactiveElements.includes(element.localName))
430
460
  return true;
431
461
  }
@@ -21,6 +21,8 @@ function createAstroGlobFn() {
21
21
  }
22
22
  function createAstro(site) {
23
23
  return {
24
+ // TODO: this is no longer neccessary for `Astro.site`
25
+ // but it somehow allows working around caching issues in content collections for some tests
24
26
  site: site ? new URL(site) : void 0,
25
27
  generator: `Astro v${ASTRO_VERSION}`,
26
28
  glob: createAstroGlobFn()
@@ -107,7 +107,7 @@ function createDevelopmentManifest(settings) {
107
107
  renderers: [],
108
108
  base: settings.config.base,
109
109
  assetsPrefix: settings.config.build.assetsPrefix,
110
- site: settings.config.site ? new URL(settings.config.base, settings.config.site).toString() : settings.config.site,
110
+ site: settings.config.site,
111
111
  componentMetadata: /* @__PURE__ */ new Map(),
112
112
  i18n: i18nManifest,
113
113
  middleware(_, next) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.4.13",
3
+ "version": "4.4.14",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -163,8 +163,8 @@
163
163
  "which-pm": "^2.1.1",
164
164
  "yargs-parser": "^21.1.1",
165
165
  "zod": "^3.22.4",
166
- "@astrojs/internal-helpers": "0.2.1",
167
166
  "@astrojs/telemetry": "3.0.4",
167
+ "@astrojs/internal-helpers": "0.2.1",
168
168
  "@astrojs/markdown-remark": "4.2.1"
169
169
  },
170
170
  "optionalDependencies": {