astro 4.4.13 → 4.4.15

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
@@ -34,10 +34,11 @@ const ALIASES = /* @__PURE__ */ new Map([
34
34
  ["solid", "solid-js"],
35
35
  ["tailwindcss", "tailwind"]
36
36
  ]);
37
- const ASTRO_CONFIG_STUB = `import { defineConfig } from 'astro/config';
38
-
39
- export default defineConfig({});`;
40
- const TAILWIND_CONFIG_STUB = `/** @type {import('tailwindcss').Config} */
37
+ const STUBS = {
38
+ ASTRO_CONFIG: `import { defineConfig } from 'astro/config';
39
+ // https://astro.build/config
40
+ export default defineConfig({});`,
41
+ TAILWIND_CONFIG: `/** @type {import('tailwindcss').Config} */
41
42
  export default {
42
43
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
43
44
  theme: {
@@ -45,16 +46,31 @@ export default {
45
46
  },
46
47
  plugins: [],
47
48
  }
48
- `;
49
- const SVELTE_CONFIG_STUB = `import { vitePreprocess } from '@astrojs/svelte';
49
+ `,
50
+ SVELTE_CONFIG: `import { vitePreprocess } from '@astrojs/svelte';
50
51
 
51
52
  export default {
52
53
  preprocess: vitePreprocess(),
53
- };
54
- `;
55
- const LIT_NPMRC_STUB = `# Lit libraries are required to be hoisted due to dependency issues.
54
+ }
55
+ `,
56
+ LIT_NPMRC: `# Lit libraries are required to be hoisted due to dependency issues.
56
57
  public-hoist-pattern[]=*lit*
57
- `;
58
+ `,
59
+ DB_CONFIG: `import { defineDb } from 'astro:db';
60
+
61
+ // https://astro.build/db/config
62
+ export default defineDb({
63
+ tables: {}
64
+ });
65
+ `,
66
+ DB_SEED: `import { db } from 'astro:db';
67
+
68
+ // https://astro.build/db/seed
69
+ export default async function seed() {
70
+ // TODO
71
+ }
72
+ `
73
+ };
58
74
  const OFFICIAL_ADAPTER_TO_IMPORT_MAP = {
59
75
  netlify: "@astrojs/netlify",
60
76
  vercel: "@astrojs/vercel/serverless",
@@ -147,7 +163,7 @@ async function add(names, { flags }) {
147
163
  "./tailwind.config.js"
148
164
  ],
149
165
  defaultConfigFile: "./tailwind.config.mjs",
150
- defaultConfigContent: TAILWIND_CONFIG_STUB
166
+ defaultConfigContent: STUBS.TAILWIND_CONFIG
151
167
  });
152
168
  }
153
169
  if (integrations.find((integration) => integration.id === "svelte")) {
@@ -158,9 +174,38 @@ async function add(names, { flags }) {
158
174
  integrationName: "Svelte",
159
175
  possibleConfigFiles: ["./svelte.config.js", "./svelte.config.cjs", "./svelte.config.mjs"],
160
176
  defaultConfigFile: "./svelte.config.js",
161
- defaultConfigContent: SVELTE_CONFIG_STUB
177
+ defaultConfigContent: STUBS.SVELTE_CONFIG
162
178
  });
163
179
  }
180
+ if (integrations.find((integration) => integration.id === "db")) {
181
+ if (!existsSync(new URL("./db/", root))) {
182
+ logger.info(
183
+ "SKIP_FORMAT",
184
+ `
185
+ ${magenta(
186
+ `Astro will scaffold ${green("./db/config.ts")}${magenta(" and ")}${green(
187
+ "./db/seed.ts"
188
+ )}${magenta(" files.")}`
189
+ )}
190
+ `
191
+ );
192
+ if (await askToContinue({ flags })) {
193
+ await fs.mkdir(new URL("./db", root));
194
+ await Promise.all([
195
+ fs.writeFile(new URL("./db/config.ts", root), STUBS.DB_CONFIG, { encoding: "utf-8" }),
196
+ fs.writeFile(new URL("./db/seed.ts", root), STUBS.DB_SEED, { encoding: "utf-8" })
197
+ ]);
198
+ } else {
199
+ logger.info(
200
+ "SKIP_FORMAT",
201
+ `
202
+ Astro DB requires additional configuration. Please refer to https://astro.build/db/config`
203
+ );
204
+ }
205
+ } else {
206
+ logger.debug("add", `Using existing db configuration`);
207
+ }
208
+ }
164
209
  if (integrations.find((integration) => integration.id === "lit") && (await preferredPM(fileURLToPath(root)))?.name === "pnpm") {
165
210
  await setupIntegrationConfig({
166
211
  root,
@@ -169,14 +214,14 @@ async function add(names, { flags }) {
169
214
  integrationName: "Lit",
170
215
  possibleConfigFiles: ["./.npmrc"],
171
216
  defaultConfigFile: "./.npmrc",
172
- defaultConfigContent: LIT_NPMRC_STUB
217
+ defaultConfigContent: STUBS.LIT_NPMRC
173
218
  });
174
219
  }
175
220
  break;
176
221
  }
177
222
  case 2 /* cancelled */: {
178
223
  logger.info(
179
- null,
224
+ "SKIP_FORMAT",
180
225
  msg.cancelled(
181
226
  `Dependencies ${bold("NOT")} installed.`,
182
227
  `Be sure to install them manually before continuing!`
@@ -201,7 +246,7 @@ async function add(names, { flags }) {
201
246
  } else {
202
247
  logger.info("add", `Unable to locate a config file, generating one for you.`);
203
248
  configURL = new URL("./astro.config.mjs", root);
204
- await fs.writeFile(fileURLToPath(configURL), ASTRO_CONFIG_STUB, { encoding: "utf-8" });
249
+ await fs.writeFile(fileURLToPath(configURL), STUBS.ASTRO_CONFIG, { encoding: "utf-8" });
205
250
  }
206
251
  let ast = null;
207
252
  try {
@@ -224,7 +269,7 @@ async function add(names, { flags }) {
224
269
  await setAdapter(ast, integration, officialExportName);
225
270
  } else {
226
271
  logger.info(
227
- null,
272
+ "SKIP_FORMAT",
228
273
  `
229
274
  ${magenta(
230
275
  `Check our deployment docs for ${bold(
@@ -259,7 +304,10 @@ async function add(names, { flags }) {
259
304
  }
260
305
  switch (configResult) {
261
306
  case 2 /* cancelled */: {
262
- logger.info(null, msg.cancelled(`Your configuration has ${bold("NOT")} been updated.`));
307
+ logger.info(
308
+ "SKIP_FORMAT",
309
+ msg.cancelled(`Your configuration has ${bold("NOT")} been updated.`)
310
+ );
263
311
  break;
264
312
  }
265
313
  case 0 /* none */: {
@@ -271,17 +319,17 @@ async function add(names, { flags }) {
271
319
  (integration) => !deps.includes(integration.packageName)
272
320
  );
273
321
  if (missingDeps.length === 0) {
274
- logger.info(null, msg.success(`Configuration up-to-date.`));
322
+ logger.info("SKIP_FORMAT", msg.success(`Configuration up-to-date.`));
275
323
  break;
276
324
  }
277
325
  }
278
- logger.info(null, msg.success(`Configuration up-to-date.`));
326
+ logger.info("SKIP_FORMAT", msg.success(`Configuration up-to-date.`));
279
327
  break;
280
328
  }
281
329
  default: {
282
330
  const list = integrations.map((integration) => ` - ${integration.packageName}`).join("\n");
283
331
  logger.info(
284
- null,
332
+ "SKIP_FORMAT",
285
333
  msg.success(
286
334
  `Added the following integration${integrations.length === 1 ? "" : "s"} to your project:
287
335
  ${list}`
@@ -296,7 +344,7 @@ ${list}`
296
344
  }
297
345
  case 2 /* cancelled */: {
298
346
  logger.info(
299
- null,
347
+ "SKIP_FORMAT",
300
348
  msg.cancelled(`Your TypeScript configuration has ${bold("NOT")} been updated.`)
301
349
  );
302
350
  break;
@@ -307,7 +355,7 @@ ${list}`
307
355
  );
308
356
  }
309
357
  default:
310
- logger.info(null, msg.success(`Successfully updated TypeScript settings`));
358
+ logger.info("SKIP_FORMAT", msg.success(`Successfully updated TypeScript settings`));
311
359
  }
312
360
  }
313
361
  function isAdapter(integration) {
@@ -488,14 +536,14 @@ ${boxen(diff, {
488
536
  })}
489
537
  `;
490
538
  logger.info(
491
- null,
539
+ "SKIP_FORMAT",
492
540
  `
493
541
  ${magenta("Astro will make the following changes to your config file:")}
494
542
  ${message}`
495
543
  );
496
544
  if (logAdapterInstructions) {
497
545
  logger.info(
498
- null,
546
+ "SKIP_FORMAT",
499
547
  magenta(
500
548
  ` For complete deployment options, visit
501
549
  ${bold(
@@ -597,7 +645,7 @@ ${boxen(coloredOutput, {
597
645
  })}
598
646
  `;
599
647
  logger.info(
600
- null,
648
+ "SKIP_FORMAT",
601
649
  `
602
650
  ${magenta("Astro will run the following command:")}
603
651
  ${dim(
@@ -801,7 +849,7 @@ ${boxen(diff, {
801
849
  })}
802
850
  `;
803
851
  logger.info(
804
- null,
852
+ "SKIP_FORMAT",
805
853
  `
806
854
  ${magenta(`Astro will make the following changes to your ${configFileName}:`)}
807
855
  ${message}`
@@ -810,7 +858,7 @@ ${message}`
810
858
  const hasConflictingIntegrations = integrations.filter((integration) => presets.has(integration)).length > 1 && integrations.filter((integration) => conflictingIntegrations.includes(integration)).length > 0;
811
859
  if (hasConflictingIntegrations) {
812
860
  logger.info(
813
- null,
861
+ "SKIP_FORMAT",
814
862
  red(
815
863
  ` ${bold(
816
864
  "Caution:"
@@ -895,7 +943,7 @@ async function setupIntegrationConfig(opts) {
895
943
  }
896
944
  if (!alreadyConfigured) {
897
945
  logger.info(
898
- null,
946
+ "SKIP_FORMAT",
899
947
  `
900
948
  ${magenta(`Astro will generate a minimal ${bold(opts.defaultConfigFile)} file.`)}
901
949
  `
@@ -25,7 +25,7 @@ async function getPackage(packageName, logger, options, otherDeps = []) {
25
25
  return packageImport;
26
26
  } catch (e) {
27
27
  logger.info(
28
- null,
28
+ "SKIP_FORMAT",
29
29
  `To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`
30
30
  );
31
31
  const result = await installPackage([packageName, ...otherDeps], options, logger);
@@ -87,7 +87,7 @@ ${boxen(coloredOutput, {
87
87
  })}
88
88
  `;
89
89
  logger.info(
90
- null,
90
+ "SKIP_FORMAT",
91
91
  `
92
92
  ${magenta("Astro will run the following command:")}
93
93
  ${dim(
@@ -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.15";
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.15";
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.15";
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.15"}`
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()
@@ -49,7 +49,7 @@ function isHTMLComponent(Component) {
49
49
  }
50
50
  const ASTRO_SLOT_EXP = /<\/?astro-slot\b[^>]*>/g;
51
51
  const ASTRO_STATIC_SLOT_EXP = /<\/?astro-static-slot\b[^>]*>/g;
52
- function removeStaticAstroSlot(html, supportsAstroStaticSlot) {
52
+ function removeStaticAstroSlot(html, supportsAstroStaticSlot = true) {
53
53
  const exp = supportsAstroStaticSlot ? ASTRO_STATIC_SLOT_EXP : ASTRO_SLOT_EXP;
54
54
  return html.replace(exp, "");
55
55
  }
@@ -245,9 +245,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
245
245
  destination.write(html);
246
246
  } else if (html && html.length > 0) {
247
247
  destination.write(
248
- markHTMLString(
249
- removeStaticAstroSlot(html, renderer?.ssr?.supportsAstroStaticSlot ?? false)
250
- )
248
+ markHTMLString(removeStaticAstroSlot(html, renderer?.ssr?.supportsAstroStaticSlot))
251
249
  );
252
250
  }
253
251
  }
@@ -306,7 +304,8 @@ ${serializeProps(
306
304
  })
307
305
  );
308
306
  }
309
- destination.write(markHTMLString(renderElement("astro-island", island, false)));
307
+ const renderedElement = renderElement("astro-island", island, false);
308
+ destination.write(markHTMLString(renderedElement));
310
309
  }
311
310
  };
312
311
  }
@@ -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.15",
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",
@@ -164,8 +164,8 @@
164
164
  "yargs-parser": "^21.1.1",
165
165
  "zod": "^3.22.4",
166
166
  "@astrojs/internal-helpers": "0.2.1",
167
- "@astrojs/telemetry": "3.0.4",
168
- "@astrojs/markdown-remark": "4.2.1"
167
+ "@astrojs/markdown-remark": "4.2.1",
168
+ "@astrojs/telemetry": "3.0.4"
169
169
  },
170
170
  "optionalDependencies": {
171
171
  "sharp": "^0.32.6"