astro 6.3.3 → 6.3.4

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 (33) hide show
  1. package/dist/assets/utils/metadata.js +1 -1
  2. package/dist/assets/utils/vendor/image-size/types/svg.js +1 -1
  3. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  4. package/dist/content/content-layer.js +3 -3
  5. package/dist/content/runtime.js +5 -2
  6. package/dist/core/build/generate.js +8 -1
  7. package/dist/core/build/plugins/plugin-internals.js +1 -1
  8. package/dist/core/build/static-build.js +9 -147
  9. package/dist/core/build/vite-build-config.d.ts +28 -0
  10. package/dist/core/build/vite-build-config.js +160 -0
  11. package/dist/core/config/schemas/base.d.ts +3 -1
  12. package/dist/core/config/schemas/base.js +6 -1
  13. package/dist/core/config/schemas/relative.d.ts +9 -3
  14. package/dist/core/constants.js +1 -1
  15. package/dist/core/dev/dev.js +1 -1
  16. package/dist/core/fetch/fetch-state.d.ts +7 -0
  17. package/dist/core/fetch/fetch-state.js +5 -0
  18. package/dist/core/fetch/types.d.ts +19 -0
  19. package/dist/core/fetch/vite-plugin.js +11 -4
  20. package/dist/core/hono/index.d.ts +1 -1
  21. package/dist/core/hono/index.js +6 -3
  22. package/dist/core/messages/runtime.js +1 -1
  23. package/dist/core/middleware/astro-middleware.js +3 -1
  24. package/dist/core/module-loader/vite.js +1 -2
  25. package/dist/core/pages/handler.js +1 -0
  26. package/dist/types/public/config.d.ts +20 -3
  27. package/dist/types/public/context.d.ts +1 -1
  28. package/dist/types/public/extendables.d.ts +19 -0
  29. package/dist/types/public/index.d.ts +1 -0
  30. package/dist/vite-plugin-app/app.js +11 -11
  31. package/dist/vite-plugin-astro-server/plugin.js +8 -7
  32. package/package.json +5 -5
  33. package/templates/content/types.d.ts +1 -0
@@ -10,7 +10,7 @@ async function imageMetadata(data, src) {
10
10
  message: AstroErrorData.NoImageMetadata.message(src)
11
11
  });
12
12
  }
13
- if (!result.height || !result.width || !result.type) {
13
+ if (result.height == null || result.width == null || !result.type) {
14
14
  throw new AstroError({
15
15
  ...AstroErrorData.NoImageMetadata,
16
16
  message: AstroErrorData.NoImageMetadata.message(src)
@@ -77,7 +77,7 @@ const SVG = {
77
77
  const root = extractorRegExps.root.exec(toUTF8String(input));
78
78
  if (root) {
79
79
  const attrs = parseAttributes(root[0]);
80
- if (attrs.width && attrs.height) {
80
+ if (attrs.width != null && attrs.height != null) {
81
81
  return calculateByDimensions(attrs);
82
82
  }
83
83
  if (attrs.viewbox) {
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.3.3";
3
+ version = "6.3.4";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -191,7 +191,7 @@ ${contentConfig.error.message}`
191
191
  logger.info("Content config changed");
192
192
  shouldClear = true;
193
193
  }
194
- if (previousAstroVersion && previousAstroVersion !== "6.3.3") {
194
+ if (previousAstroVersion && previousAstroVersion !== "6.3.4") {
195
195
  logger.info("Astro version changed");
196
196
  shouldClear = true;
197
197
  }
@@ -199,8 +199,8 @@ ${contentConfig.error.message}`
199
199
  logger.info("Clearing content store");
200
200
  this.#store.clearAll();
201
201
  }
202
- if ("6.3.3") {
203
- this.#store.metaStore().set("astro-version", "6.3.3");
202
+ if ("6.3.4") {
203
+ this.#store.metaStore().set("astro-version", "6.3.4");
204
204
  }
205
205
  if (currentConfigDigest) {
206
206
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -134,9 +134,10 @@ function createGetEntry({ liveCollections }) {
134
134
  return;
135
135
  }
136
136
  const { default: imageAssetMap } = await import("astro:asset-imports");
137
- entry.data = updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap);
137
+ const data = updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap);
138
138
  const result = {
139
139
  ...entry,
140
+ data,
140
141
  collection
141
142
  };
142
143
  warnForPropertyAccess(
@@ -354,7 +355,8 @@ async function updateImageReferencesInBody(html, fileName) {
354
355
  });
355
356
  }
356
357
  function updateImageReferencesInData(data, fileName, imageAssetMap) {
357
- return new Traverse(data).map(function(ctx, val) {
358
+ const copy = structuredClone(data);
359
+ new Traverse(copy).forEach(function(ctx, val) {
358
360
  if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
359
361
  const src = val.replace(IMAGE_IMPORT_PREFIX, "");
360
362
  const id = imageSrcToImportId(src, fileName);
@@ -375,6 +377,7 @@ function updateImageReferencesInData(data, fileName, imageAssetMap) {
375
377
  }
376
378
  }
377
379
  });
380
+ return copy;
378
381
  }
379
382
  async function renderEntry(entry) {
380
383
  if (!entry) {
@@ -187,13 +187,20 @@ ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
187
187
  const cpuCount = os.cpus().length;
188
188
  const assetsCreationPipeline = await prepareAssetsGenerationEnv(options, totalCount);
189
189
  const queue = new PQueue({ concurrency: Math.max(cpuCount, 1) });
190
+ const errors = [];
190
191
  const assetsTimer = performance.now();
191
192
  for (const [originalPath, transforms] of staticImageList) {
192
193
  queue.add(() => generateImagesForPath(originalPath, transforms, assetsCreationPipeline)).catch((e) => {
193
- throw e;
194
+ logger.warn("build", `Unable to generate optimized image for ${originalPath}: ${e}`);
195
+ errors.push(new Error(`Error generating image for ${originalPath}: ${e}`, { cause: e }));
194
196
  });
195
197
  }
196
198
  await queue.onIdle();
199
+ if (errors.length === 1) {
200
+ throw errors[0];
201
+ } else if (errors.length > 1) {
202
+ throw new AggregateError(errors, `${errors.length} errors occurred during asset generation`);
203
+ }
197
204
  const assetsTimeEnd = performance.now();
198
205
  logger.info(null, colors.green(`\u2713 Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.
199
206
  `));
@@ -32,7 +32,7 @@ function pluginInternals(options, internals) {
32
32
  },
33
33
  resolve: {
34
34
  // Always bundle Astro runtime when building for SSR
35
- noExternal: ["astro"]
35
+ noExternal: ["astro", "@astrojs/internal-helpers"]
36
36
  }
37
37
  };
38
38
  }
@@ -10,31 +10,21 @@ import { emptyDir, removeEmptyDirs } from "../../core/fs/index.js";
10
10
  import { appendForwardSlash, prependForwardSlash } from "../../core/path.js";
11
11
  import { runHookBuildSetup } from "../../integrations/hooks.js";
12
12
  import { SERIALIZED_MANIFEST_RESOLVED_ID } from "../../manifest/serialized.js";
13
- import {
14
- getClientOutputDirectory,
15
- getPrerenderOutputDirectory,
16
- getServerOutputDirectory
17
- } from "../../prerender/utils.js";
18
- import { VIRTUAL_PAGE_RESOLVED_MODULE_ID } from "../../vite-plugin-pages/const.js";
13
+ import { getPrerenderOutputDirectory } from "../../prerender/utils.js";
19
14
  import { PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
20
15
  import { routeIsRedirect } from "../routing/helpers.js";
21
16
  import { getOutDirWithinCwd } from "./common.js";
22
- import { CHUNKS_PATH } from "./consts.js";
23
17
  import { generatePages } from "./generate.js";
24
18
  import { trackPageData } from "./internal.js";
25
19
  import { getAllBuildPlugins } from "./plugins/index.js";
26
20
  import { manifestBuildPostHook } from "./plugins/plugin-manifest.js";
27
- import {
28
- isLegacyAdapter,
29
- LEGACY_SSR_ENTRY_VIRTUAL_MODULE,
30
- RESOLVED_LEGACY_SSR_ENTRY_VIRTUAL_MODULE
31
- } from "./plugins/plugin-ssr.js";
32
21
  import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from "./plugins/util.js";
33
- import { cleanChunkName, getTimeStat, viteBuildReturnToRollupOutputs } from "./util.js";
22
+ import { getTimeStat, viteBuildReturnToRollupOutputs } from "./util.js";
34
23
  import { NOOP_MODULE_ID } from "./plugins/plugin-noop.js";
35
24
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
36
25
  import { getSSRAssets } from "./internal.js";
37
26
  import { SERVER_ISLAND_MAP_MARKER } from "../server-islands/vite-plugin-server-islands.js";
27
+ import { createViteBuildConfig } from "./vite-build-config.js";
38
28
  const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
39
29
  function extractRelevantChunks(outputs, prerender) {
40
30
  const extracted = [];
@@ -83,7 +73,6 @@ async function viteBuild(opts) {
83
73
  async function buildEnvironments(opts, internals) {
84
74
  const { allPages, settings, viteConfig } = opts;
85
75
  const routes = Object.values(allPages).flatMap((pageData) => pageData.route);
86
- const legacyAdapter = !settings.adapter || isLegacyAdapter(settings.adapter);
87
76
  const buildPlugins = getAllBuildPlugins(internals, opts);
88
77
  const flatPlugins = buildPlugins.flat().filter(Boolean);
89
78
  const plugins = [...flatPlugins, ...viteConfig.plugins || []];
@@ -149,76 +138,10 @@ async function buildEnvironments(opts, internals) {
149
138
  return Object.keys(currentRollupInput).includes(moduleName);
150
139
  }
151
140
  }
152
- const viteBuildConfig = {
153
- ...viteConfig,
154
- logLevel: viteConfig.logLevel ?? "error",
155
- build: {
156
- target: "esnext",
157
- // Vite defaults cssMinify to false in SSR by default, but we want to minify it
158
- // as the CSS generated are used and served to the client.
159
- cssMinify: viteConfig.build?.minify == null ? true : !!viteConfig.build?.minify,
160
- ...viteConfig.build,
161
- emptyOutDir: false,
162
- copyPublicDir: false,
163
- manifest: false,
164
- rollupOptions: {
165
- ...viteConfig.build?.rollupOptions,
166
- // Setting as `exports-only` allows us to safely delete inputs that are only used during prerendering
167
- preserveEntrySignatures: "exports-only",
168
- ...legacyAdapter && settings.buildOutput === "server" ? { input: LEGACY_SSR_ENTRY_VIRTUAL_MODULE } : {},
169
- output: {
170
- hoistTransitiveImports: false,
171
- format: "esm",
172
- minifyInternalExports: true,
173
- // Server chunks can't go in the assets (_astro) folder
174
- // We need to keep these separate
175
- chunkFileNames(chunkInfo) {
176
- const { name } = chunkInfo;
177
- let prefix = CHUNKS_PATH;
178
- let suffix = "_[hash].mjs";
179
- if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
180
- const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
181
- return [prefix, cleanChunkName(sanitizedName), suffix].join("");
182
- }
183
- if (name.startsWith("pages/")) {
184
- const sanitizedName = name.split(".")[0];
185
- return [prefix, cleanChunkName(sanitizedName), suffix].join("");
186
- }
187
- return [prefix, cleanChunkName(name), suffix].join("");
188
- },
189
- assetFileNames(assetInfo) {
190
- const name = assetInfo.names?.[0] ?? "";
191
- if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
192
- const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
193
- return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`;
194
- }
195
- return `${settings.config.build.assets}/[name].[hash][extname]`;
196
- },
197
- ...viteConfig.build?.rollupOptions?.output,
198
- entryFileNames(chunkInfo) {
199
- if (chunkInfo.facadeModuleId?.startsWith(VIRTUAL_PAGE_RESOLVED_MODULE_ID)) {
200
- return makeAstroPageEntryPointFileName(
201
- VIRTUAL_PAGE_RESOLVED_MODULE_ID,
202
- chunkInfo.facadeModuleId,
203
- routes
204
- );
205
- } else if (chunkInfo.facadeModuleId === RESOLVED_LEGACY_SSR_ENTRY_VIRTUAL_MODULE || // This catches the case when the adapter uses `entrypointResolution: 'auto'`. When doing so,
206
- // the adapter must set rollupOptions.input or Astro sets it from `serverEntrypoint`.
207
- isRollupInput(chunkInfo.name) || isRollupInput(chunkInfo.facadeModuleId)) {
208
- return opts.settings.config.build.serverEntry;
209
- } else {
210
- return "[name].mjs";
211
- }
212
- }
213
- }
214
- },
215
- ssr: true,
216
- ssrEmitAssets: true,
217
- // improve build performance
218
- minify: false,
219
- modulePreload: { polyfill: false },
220
- reportCompressedSize: false
221
- },
141
+ const viteBuildConfig = createViteBuildConfig({
142
+ settings,
143
+ viteConfig,
144
+ routes,
222
145
  plugins,
223
146
  // Top-level buildApp for framework build orchestration
224
147
  // This takes precedence over platform plugin fallbacks (e.g., Cloudflare)
@@ -258,69 +181,8 @@ async function buildEnvironments(opts, internals) {
258
181
  internals.extractedChunks = [...ssrChunks, ...prerenderChunks];
259
182
  }
260
183
  },
261
- envPrefix: viteConfig.envPrefix ?? "PUBLIC_",
262
- base: settings.config.base,
263
- environments: {
264
- ...viteConfig.environments ?? {},
265
- [ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: {
266
- build: {
267
- emitAssets: true,
268
- outDir: fileURLToPath(getPrerenderOutputDirectory(settings)),
269
- rollupOptions: {
270
- // Only skip the default prerender entrypoint if an adapter with `entrypointResolution: 'self'` is used
271
- // AND provides a custom prerenderer. Otherwise, use the default.
272
- ...!legacyAdapter && settings.prerenderer ? {} : { input: "astro/entrypoints/prerender" },
273
- output: {
274
- entryFileNames: `${PRERENDER_ENTRY_FILENAME_PREFIX}.[hash].mjs`,
275
- format: "esm",
276
- ...viteConfig.environments?.prerender?.build?.rollupOptions?.output
277
- }
278
- },
279
- ssr: true
280
- }
281
- },
282
- [ASTRO_VITE_ENVIRONMENT_NAMES.client]: {
283
- build: {
284
- emitAssets: true,
285
- target: "esnext",
286
- outDir: fileURLToPath(getClientOutputDirectory(settings)),
287
- copyPublicDir: true,
288
- sourcemap: viteConfig.environments?.client?.build?.sourcemap ?? false,
289
- minify: true,
290
- rollupOptions: {
291
- preserveEntrySignatures: "exports-only",
292
- output: {
293
- entryFileNames(chunkInfo) {
294
- return `${settings.config.build.assets}/${cleanChunkName(chunkInfo.name)}.[hash].js`;
295
- },
296
- chunkFileNames(chunkInfo) {
297
- return `${settings.config.build.assets}/${cleanChunkName(chunkInfo.name)}.[hash].js`;
298
- },
299
- assetFileNames(assetInfo) {
300
- const name = assetInfo.names?.[0] ?? "";
301
- if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
302
- const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
303
- return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`;
304
- }
305
- return `${settings.config.build.assets}/[name].[hash][extname]`;
306
- },
307
- ...viteConfig.environments?.client?.build?.rollupOptions?.output
308
- }
309
- }
310
- }
311
- },
312
- [ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: {
313
- build: {
314
- outDir: fileURLToPath(getServerOutputDirectory(settings)),
315
- rollupOptions: {
316
- output: {
317
- ...viteConfig.environments?.ssr?.build?.rollupOptions?.output
318
- }
319
- }
320
- }
321
- }
322
- }
323
- };
184
+ isRollupInput
185
+ });
324
186
  const updatedViteBuildConfig = await runHookBuildSetup({
325
187
  config: settings.config,
326
188
  pages: internals.pagesByKeys,
@@ -0,0 +1,28 @@
1
+ import type * as vite from 'vite';
2
+ import type { RouteData } from '../../types/public/internal.js';
3
+ import type { AstroSettings } from '../../types/astro.js';
4
+ export interface CreateViteBuildConfigOptions {
5
+ /** The resolved Astro settings. */
6
+ settings: AstroSettings;
7
+ /** The base Vite config produced by createVite(). */
8
+ viteConfig: vite.InlineConfig;
9
+ /** All routes to be built. */
10
+ routes: RouteData[];
11
+ /** Assembled Vite plugins (build plugins + user plugins). */
12
+ plugins: vite.PluginOption[];
13
+ /** The buildApp callback for the Vite builder. */
14
+ builder: vite.BuilderOptions;
15
+ /**
16
+ * A function that checks whether a given module name is a rollup input.
17
+ * Used by entryFileNames to determine the server entry.
18
+ */
19
+ isRollupInput: (moduleName: string | null) => boolean;
20
+ }
21
+ /**
22
+ * Creates the Vite InlineConfig used for the multi-environment build.
23
+ *
24
+ * This is a pure config assembly function — it does not execute the build.
25
+ * Extracted from `buildEnvironments()` to enable unit testing of config
26
+ * merging behavior (e.g. user rollup output overrides).
27
+ */
28
+ export declare function createViteBuildConfig(opts: CreateViteBuildConfigOptions): vite.InlineConfig;
@@ -0,0 +1,160 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import {
3
+ getClientOutputDirectory,
4
+ getPrerenderOutputDirectory,
5
+ getServerOutputDirectory
6
+ } from "../../prerender/utils.js";
7
+ import { VIRTUAL_PAGE_RESOLVED_MODULE_ID } from "../../vite-plugin-pages/const.js";
8
+ import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
9
+ import { CHUNKS_PATH } from "./consts.js";
10
+ import {
11
+ isLegacyAdapter,
12
+ LEGACY_SSR_ENTRY_VIRTUAL_MODULE,
13
+ RESOLVED_LEGACY_SSR_ENTRY_VIRTUAL_MODULE
14
+ } from "./plugins/plugin-ssr.js";
15
+ import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from "./plugins/util.js";
16
+ import { cleanChunkName } from "./util.js";
17
+ import { makeAstroPageEntryPointFileName } from "./static-build.js";
18
+ const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
19
+ function createViteBuildConfig(opts) {
20
+ const { settings, viteConfig, routes, plugins, builder, isRollupInput } = opts;
21
+ const legacyAdapter = !settings.adapter || isLegacyAdapter(settings.adapter);
22
+ return {
23
+ ...viteConfig,
24
+ logLevel: viteConfig.logLevel ?? "error",
25
+ build: {
26
+ target: "esnext",
27
+ // Vite defaults cssMinify to false in SSR by default, but we want to minify it
28
+ // as the CSS generated are used and served to the client.
29
+ cssMinify: viteConfig.build?.minify == null ? true : !!viteConfig.build?.minify,
30
+ ...viteConfig.build,
31
+ emptyOutDir: false,
32
+ copyPublicDir: false,
33
+ manifest: false,
34
+ rollupOptions: {
35
+ ...viteConfig.build?.rollupOptions,
36
+ // Setting as `exports-only` allows us to safely delete inputs that are only used during prerendering
37
+ preserveEntrySignatures: "exports-only",
38
+ ...legacyAdapter && settings.buildOutput === "server" ? { input: LEGACY_SSR_ENTRY_VIRTUAL_MODULE } : {},
39
+ output: {
40
+ hoistTransitiveImports: false,
41
+ format: "esm",
42
+ minifyInternalExports: true,
43
+ // Server chunks can't go in the assets (_astro) folder
44
+ // We need to keep these separate
45
+ chunkFileNames(chunkInfo) {
46
+ const { name } = chunkInfo;
47
+ let prefix = CHUNKS_PATH;
48
+ let suffix = "_[hash].mjs";
49
+ if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
50
+ const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
51
+ return [prefix, cleanChunkName(sanitizedName), suffix].join("");
52
+ }
53
+ if (name.startsWith("pages/")) {
54
+ const sanitizedName = name.split(".")[0];
55
+ return [prefix, cleanChunkName(sanitizedName), suffix].join("");
56
+ }
57
+ return [prefix, cleanChunkName(name), suffix].join("");
58
+ },
59
+ assetFileNames(assetInfo) {
60
+ const name = assetInfo.names?.[0] ?? "";
61
+ if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
62
+ const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
63
+ return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`;
64
+ }
65
+ return `${settings.config.build.assets}/[name].[hash][extname]`;
66
+ },
67
+ ...viteConfig.build?.rollupOptions?.output,
68
+ entryFileNames(chunkInfo) {
69
+ if (chunkInfo.facadeModuleId?.startsWith(VIRTUAL_PAGE_RESOLVED_MODULE_ID)) {
70
+ return makeAstroPageEntryPointFileName(
71
+ VIRTUAL_PAGE_RESOLVED_MODULE_ID,
72
+ chunkInfo.facadeModuleId,
73
+ routes
74
+ );
75
+ } else if (chunkInfo.facadeModuleId === RESOLVED_LEGACY_SSR_ENTRY_VIRTUAL_MODULE || // This catches the case when the adapter uses `entrypointResolution: 'auto'`. When doing so,
76
+ // the adapter must set rollupOptions.input or Astro sets it from `serverEntrypoint`.
77
+ isRollupInput(chunkInfo.name) || isRollupInput(chunkInfo.facadeModuleId)) {
78
+ return settings.config.build.serverEntry;
79
+ } else {
80
+ return "[name].mjs";
81
+ }
82
+ }
83
+ }
84
+ },
85
+ ssr: true,
86
+ ssrEmitAssets: true,
87
+ // improve build performance
88
+ minify: false,
89
+ modulePreload: { polyfill: false },
90
+ reportCompressedSize: false
91
+ },
92
+ plugins,
93
+ builder,
94
+ envPrefix: viteConfig.envPrefix ?? "PUBLIC_",
95
+ base: settings.config.base,
96
+ environments: {
97
+ ...viteConfig.environments ?? {},
98
+ [ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: {
99
+ build: {
100
+ emitAssets: true,
101
+ outDir: fileURLToPath(getPrerenderOutputDirectory(settings)),
102
+ rollupOptions: {
103
+ // Only skip the default prerender entrypoint if an adapter with `entrypointResolution: 'self'` is used
104
+ // AND provides a custom prerenderer. Otherwise, use the default.
105
+ ...!legacyAdapter && settings.prerenderer ? {} : { input: "astro/entrypoints/prerender" },
106
+ output: {
107
+ entryFileNames: `${PRERENDER_ENTRY_FILENAME_PREFIX}.[hash].mjs`,
108
+ format: "esm",
109
+ ...viteConfig.environments?.prerender?.build?.rollupOptions?.output
110
+ }
111
+ },
112
+ ssr: true
113
+ }
114
+ },
115
+ [ASTRO_VITE_ENVIRONMENT_NAMES.client]: {
116
+ build: {
117
+ emitAssets: true,
118
+ target: "esnext",
119
+ outDir: fileURLToPath(getClientOutputDirectory(settings)),
120
+ copyPublicDir: true,
121
+ sourcemap: viteConfig.environments?.client?.build?.sourcemap ?? false,
122
+ minify: true,
123
+ rollupOptions: {
124
+ preserveEntrySignatures: "exports-only",
125
+ output: {
126
+ entryFileNames(chunkInfo) {
127
+ return `${settings.config.build.assets}/${cleanChunkName(chunkInfo.name)}.[hash].js`;
128
+ },
129
+ chunkFileNames(chunkInfo) {
130
+ return `${settings.config.build.assets}/${cleanChunkName(chunkInfo.name)}.[hash].js`;
131
+ },
132
+ assetFileNames(assetInfo) {
133
+ const name = assetInfo.names?.[0] ?? "";
134
+ if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
135
+ const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
136
+ return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`;
137
+ }
138
+ return `${settings.config.build.assets}/[name].[hash][extname]`;
139
+ },
140
+ ...viteConfig.environments?.client?.build?.rollupOptions?.output
141
+ }
142
+ }
143
+ }
144
+ },
145
+ [ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: {
146
+ build: {
147
+ outDir: fileURLToPath(getServerOutputDirectory(settings)),
148
+ rollupOptions: {
149
+ output: {
150
+ ...viteConfig.environments?.ssr?.build?.rollupOptions?.output
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ };
157
+ }
158
+ export {
159
+ createViteBuildConfig
160
+ };
@@ -501,7 +501,9 @@ export declare const AstroConfigSchema: z.ZodObject<{
501
501
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
502
502
  }, z.core.$strict>>>;
503
503
  experimental: z.ZodPrefault<z.ZodObject<{
504
- advancedRouting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
504
+ advancedRouting: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
505
+ fetchFile: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
506
+ }, z.core.$strict>]>>>;
505
507
  clientPrerender: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
506
508
  contentIntellisense: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
507
509
  chromeDevtoolsWorkspace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -310,7 +310,12 @@ const AstroConfigSchema = z.object({
310
310
  prerenderConflictBehavior: z.enum(["error", "warn", "ignore"]).optional().default(ASTRO_CONFIG_DEFAULTS.prerenderConflictBehavior),
311
311
  fonts: z.array(FontFamilySchema).optional(),
312
312
  experimental: z.strictObject({
313
- advancedRouting: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.advancedRouting),
313
+ advancedRouting: z.union([
314
+ z.boolean(),
315
+ z.strictObject({
316
+ fetchFile: z.string().nullable().optional().default("app")
317
+ })
318
+ ]).optional().default(ASTRO_CONFIG_DEFAULTS.experimental.advancedRouting),
314
319
  clientPrerender: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.clientPrerender),
315
320
  contentIntellisense: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.contentIntellisense),
316
321
  chromeDevtoolsWorkspace: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.chromeDevtoolsWorkspace),
@@ -382,7 +382,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
382
382
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
383
383
  }, z.core.$strict>>>;
384
384
  experimental: z.ZodPrefault<z.ZodObject<{
385
- advancedRouting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
385
+ advancedRouting: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
386
+ fetchFile: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
387
+ }, z.core.$strict>]>>>;
386
388
  clientPrerender: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
387
389
  contentIntellisense: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
388
390
  chromeDevtoolsWorkspace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -576,7 +578,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
576
578
  };
577
579
  prerenderConflictBehavior: "error" | "ignore" | "warn";
578
580
  experimental: {
579
- advancedRouting: boolean;
581
+ advancedRouting: boolean | {
582
+ fetchFile: string | null;
583
+ };
580
584
  clientPrerender: boolean;
581
585
  contentIntellisense: boolean;
582
586
  chromeDevtoolsWorkspace: boolean;
@@ -826,7 +830,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
826
830
  };
827
831
  prerenderConflictBehavior: "error" | "ignore" | "warn";
828
832
  experimental: {
829
- advancedRouting: boolean;
833
+ advancedRouting: boolean | {
834
+ fetchFile: string | null;
835
+ };
830
836
  clientPrerender: boolean;
831
837
  contentIntellisense: boolean;
832
838
  chromeDevtoolsWorkspace: boolean;
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.3.3";
1
+ const ASTRO_VERSION = "6.3.4";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
4
4
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
@@ -37,7 +37,7 @@ async function dev(inlineConfig) {
37
37
  await telemetry.record([]);
38
38
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
39
39
  const logger = restart.container.logger;
40
- const currentVersion = "6.3.3";
40
+ const currentVersion = "6.3.4";
41
41
  const isPrerelease = currentVersion.includes("-");
42
42
  if (!isPrerelease) {
43
43
  try {
@@ -44,6 +44,8 @@ export interface AstroFetchState {
44
44
  readonly locals: App.Locals;
45
45
  /** Route params derived from routeData + pathname. */
46
46
  readonly params: Params | undefined;
47
+ /** The `Response` produced by handlers, if any. Set after rendering. */
48
+ response: Response | undefined;
47
49
  /** Default HTTP status for the rendered response. */
48
50
  status: number;
49
51
  /**
@@ -119,6 +121,11 @@ export declare class FetchState implements AstroFetchState {
119
121
  * allocate an empty object per request.
120
122
  */
121
123
  slots: Record<string, any> | undefined;
124
+ /**
125
+ * The `Response` produced by handlers, if any. Set after page
126
+ * rendering or middleware completes.
127
+ */
128
+ response: Response | undefined;
122
129
  /**
123
130
  * Default HTTP status for the rendered response. Callers override
124
131
  * before rendering runs (e.g. `AstroHandler` sets this from
@@ -72,6 +72,11 @@ class FetchState {
72
72
  * allocate an empty object per request.
73
73
  */
74
74
  slots;
75
+ /**
76
+ * The `Response` produced by handlers, if any. Set after page
77
+ * rendering or middleware completes.
78
+ */
79
+ response;
75
80
  /**
76
81
  * Default HTTP status for the rendered response. Callers override
77
82
  * before rendering runs (e.g. `AstroHandler` sets this from
@@ -4,3 +4,22 @@
4
4
  * lets handlers compose easily with other middleware systems later.
5
5
  */
6
6
  export type FetchHandler = (request: Request) => Promise<Response>;
7
+ /**
8
+ * An object with a `fetch` method that handles incoming requests.
9
+ * This is the shape expected by `src/app.ts` and aligns with the
10
+ * convention used by Cloudflare Workers, Bun, and Hono.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import type { Fetchable } from 'astro';
15
+ *
16
+ * export default {
17
+ * async fetch(request) {
18
+ * return new Response('ok');
19
+ * }
20
+ * } satisfies Fetchable;
21
+ * ```
22
+ */
23
+ export interface Fetchable {
24
+ fetch(request: Request): Response | Promise<Response>;
25
+ }
@@ -5,11 +5,14 @@ import {
5
5
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
6
6
  const FETCHABLE_MODULE_ID = "virtual:astro:fetchable";
7
7
  const FETCHABLE_RESOLVED_MODULE_ID = "\0" + FETCHABLE_MODULE_ID;
8
- const APP_PATH_SEGMENT_NAME = "app";
8
+ const DEFAULT_FETCH_FILE = "app";
9
9
  function vitePluginFetchable({ settings }) {
10
10
  let resolvedUserAppId;
11
11
  let userAppPresent = false;
12
- const advancedRoutingEnabled = settings.config.experimental.advancedRouting;
12
+ const advancedRoutingConfig = settings.config.experimental.advancedRouting;
13
+ const advancedRoutingEnabled = !!advancedRoutingConfig;
14
+ const fetchFile = (typeof advancedRoutingConfig === "object" ? advancedRoutingConfig.fetchFile : void 0) ?? DEFAULT_FETCH_FILE;
15
+ const fetchFileDisabled = typeof advancedRoutingConfig === "object" && advancedRoutingConfig.fetchFile === null;
13
16
  const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir));
14
17
  return {
15
18
  name: "@astro/plugin-fetchable",
@@ -21,7 +24,7 @@ function vitePluginFetchable({ settings }) {
21
24
  const normalizedPath = viteNormalizePath(path);
22
25
  if (!normalizedPath.startsWith(normalizedSrcDir)) return;
23
26
  const relativePath = normalizedPath.slice(normalizedSrcDir.length);
24
- if (!relativePath.startsWith(`${APP_PATH_SEGMENT_NAME}.`)) return;
27
+ if (!relativePath.startsWith(`${fetchFile}.`)) return;
25
28
  for (const name of [
26
29
  ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
27
30
  ASTRO_VITE_ENVIRONMENT_NAMES.astro
@@ -40,7 +43,11 @@ function vitePluginFetchable({ settings }) {
40
43
  id: new RegExp(`^${FETCHABLE_MODULE_ID}$`)
41
44
  },
42
45
  async handler() {
43
- const resolved = await this.resolve(`${normalizedSrcDir}${APP_PATH_SEGMENT_NAME}`);
46
+ if (fetchFileDisabled) {
47
+ userAppPresent = false;
48
+ return FETCHABLE_RESOLVED_MODULE_ID;
49
+ }
50
+ const resolved = await this.resolve(`${normalizedSrcDir}${fetchFile}`);
44
51
  userAppPresent = advancedRoutingEnabled && !!resolved;
45
52
  resolvedUserAppId = resolved?.id;
46
53
  return FETCHABLE_RESOLVED_MODULE_ID;
@@ -17,5 +17,5 @@ export declare function redirects(): HonoMiddlewareHandler;
17
17
  export declare function actions(): HonoMiddlewareHandler;
18
18
  export declare function pages(): HonoMiddlewareHandler;
19
19
  export declare function sessions(): HonoMiddlewareHandler;
20
- export declare function cache(next: () => Promise<Response>): HonoMiddlewareHandler;
20
+ export declare function cache(): HonoMiddlewareHandler;
21
21
  export declare function i18n(): HonoMiddlewareHandler;
@@ -73,9 +73,12 @@ function sessions() {
73
73
  }
74
74
  };
75
75
  }
76
- function cache(next) {
77
- return async (context, _honoNext) => {
78
- return fetchCache(getFetchState(context), next);
76
+ function cache() {
77
+ return async (context, honoNext) => {
78
+ return fetchCache(getFetchState(context), async () => {
79
+ await honoNext();
80
+ return context.res;
81
+ });
79
82
  };
80
83
  }
81
84
  function i18n() {
@@ -276,7 +276,7 @@ function printHelp({
276
276
  message.push(
277
277
  linebreak(),
278
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
279
- `v${"6.3.3"}`
279
+ `v${"6.3.4"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -38,7 +38,9 @@ class AstroMiddleware {
38
38
  const composed = sequence(...pipeline.internalMiddleware, pipelineMiddleware);
39
39
  response = await callMiddleware(composed, apiContext, next);
40
40
  }
41
- return this.#finalize(state, response);
41
+ response = this.#finalize(state, response);
42
+ state.response = response;
43
+ return response;
42
44
  }
43
45
  #finalize(state, response) {
44
46
  if (response.headers.get(ROUTE_TYPE_HEADER)) {
@@ -3,7 +3,6 @@ import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { collectErrorMetadata } from "../errors/dev/utils.js";
5
5
  import { getViteErrorPayload } from "../errors/dev/vite.js";
6
- import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
7
6
  function createViteLoader(viteServer, ssrEnvironment) {
8
7
  const events = new EventEmitter();
9
8
  let isTsconfigUpdated = false;
@@ -90,7 +89,7 @@ function createViteLoader(viteServer, ssrEnvironment) {
90
89
  return viteServer.environments.client.hot.send(msg);
91
90
  },
92
91
  getSSREnvironment() {
93
- return viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
92
+ return ssrEnvironment;
94
93
  },
95
94
  isHttps() {
96
95
  return !!ssrEnvironment.config.server.https;
@@ -66,6 +66,7 @@ class PagesHandler {
66
66
  if (responseCookies) {
67
67
  state.cookies.merge(responseCookies);
68
68
  }
69
+ state.response = response;
69
70
  return response;
70
71
  }
71
72
  }
@@ -2647,21 +2647,38 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2647
2647
  experimental?: {
2648
2648
  /**
2649
2649
  * @name experimental.advancedRouting
2650
- * @type {boolean}
2650
+ * @type {boolean | object}
2651
2651
  * @default `false`
2652
2652
  * @description
2653
2653
  * Enables `src/app.ts` as an advanced routing entrypoint, allowing you to
2654
2654
  * compose Astro's request pipeline with the Web Fetch standard or your own Hono middleware.
2655
2655
  *
2656
+ * Pass `true` to enable with default settings, or an object to customize:
2657
+ *
2656
2658
  * ```js
2657
2659
  * export default defineConfig({
2658
2660
  * experimental: {
2659
- * advancedRouting: true,
2661
+ * advancedRouting: {
2662
+ * fetchFile: 'fetch.ts',
2663
+ * },
2660
2664
  * },
2661
2665
  * });
2662
2666
  * ```
2663
2667
  */
2664
- advancedRouting?: boolean;
2668
+ advancedRouting?: boolean | {
2669
+ /**
2670
+ * @name experimental.advancedRouting.fetchFile
2671
+ * @type {string | null}
2672
+ * @default 'app'
2673
+ * @description
2674
+ *
2675
+ * Customizes the file used as the advanced routing entrypoint inside `srcDir`.
2676
+ * Defaults to `'app'`, meaning Astro looks for `src/app.ts`.
2677
+ *
2678
+ * If you already have a `src/app.ts` file in use for other purposes, define a different filename or set the value to `null` to disable the entrypoint.
2679
+ */
2680
+ fetchFile?: string | null;
2681
+ };
2665
2682
  /**
2666
2683
  *
2667
2684
  * @name experimental.clientPrerender
@@ -126,7 +126,7 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
126
126
  *
127
127
  * [Astro reference](https://docs.astro.build/en/reference/api-reference/)
128
128
  */
129
- export interface APIContext<Props extends Record<string, any> = Record<string, any>, Params extends Record<string, string | undefined> = Record<string, string | undefined>> {
129
+ export interface APIContext<Props extends Record<string, any> = Record<string, any>, Params extends Record<string, string | undefined> = Record<string, string | undefined>> extends App.Providers {
130
130
  /**
131
131
  * The site provided in the astro config, parsed as an instance of `URL`, without base.
132
132
  * `undefined` if the site is not provided in the config.
@@ -12,6 +12,25 @@ declare global {
12
12
  */
13
13
  interface SessionData {
14
14
  }
15
+ /**
16
+ * Declare custom context providers to get typed access on `Astro` and `ctx`.
17
+ * Libraries and users register providers via `state.provide(key, { create, finalize? })`,
18
+ * and the corresponding types are declared here using module augmentation.
19
+ *
20
+ * Built-in providers like `session` are already typed by Astro and don't
21
+ * need to be declared here.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * declare namespace App {
26
+ * interface Providers {
27
+ * oauth: import('./lib/oauth').OAuthSession;
28
+ * }
29
+ * }
30
+ * ```
31
+ */
32
+ interface Providers {
33
+ }
15
34
  }
16
35
  namespace Astro {
17
36
  interface IntegrationHooks extends BaseIntegrationHooks {
@@ -9,6 +9,7 @@ export type { AstroIntegrationLogger } from '../../core/logger/core.js';
9
9
  export type { AstroSession } from '../../core/session/runtime.js';
10
10
  export type { ToolbarServerHelpers } from '../../runtime/client/dev-toolbar/helpers.js';
11
11
  export type { AstroEnvironmentNames } from '../../core/constants.js';
12
+ export type { Fetchable } from '../../core/fetch/types.js';
12
13
  export type { SessionDriver, SessionDriverConfig } from '../../core/session/types.js';
13
14
  export type { CacheProvider, CacheProviderConfig, CacheProviderFactory, CacheOptions, InvalidateOptions, } from '../../core/cache/types.js';
14
15
  export type * from './common.js';
@@ -125,17 +125,6 @@ class AstroServerApp extends BaseApp {
125
125
  if (url.pathname.endsWith("/") && !shouldAppendForwardSlash(this.manifest.trailingSlash, this.manifest.buildFormat)) {
126
126
  url.pathname = url.pathname.slice(0, -1);
127
127
  }
128
- let body = void 0;
129
- if (!(incomingRequest.method === "GET" || incomingRequest.method === "HEAD")) {
130
- let bytes = [];
131
- await new Promise((resolve) => {
132
- incomingRequest.on("data", (part) => {
133
- bytes.push(part);
134
- });
135
- incomingRequest.on("end", resolve);
136
- });
137
- body = Buffer.concat(bytes);
138
- }
139
128
  const self = this;
140
129
  await self.#loadFetchHandler();
141
130
  let handled = true;
@@ -155,6 +144,17 @@ class AstroServerApp extends BaseApp {
155
144
  handled = false;
156
145
  return;
157
146
  }
147
+ let body = void 0;
148
+ if (!(incomingRequest.method === "GET" || incomingRequest.method === "HEAD")) {
149
+ let bytes = [];
150
+ await new Promise((resolve) => {
151
+ incomingRequest.on("data", (part) => {
152
+ bytes.push(part);
153
+ });
154
+ incomingRequest.on("end", resolve);
155
+ });
156
+ body = Buffer.concat(bytes);
157
+ }
158
158
  const request = createRequest({
159
159
  url,
160
160
  headers: incomingRequest.headers,
@@ -107,17 +107,18 @@ function createVitePluginAstroServer({
107
107
  }
108
108
  try {
109
109
  const pathname = decodeURI(new URL(request.url, "http://localhost").pathname);
110
- const { routes } = await prerenderHandler.environment.runner.import("virtual:astro:routes");
111
- const routesList = { routes: routes.map((r) => r.routeData) };
110
+ const { routes } = await prerenderHandler.environment.runner.import(
111
+ "virtual:astro:routes"
112
+ );
113
+ const routesList = { routes: routes.map((route) => route.routeData) };
112
114
  const matches = matchAllRoutes(pathname, routesList);
113
115
  if (!matches.some((route) => route.prerender)) {
114
116
  return next();
115
117
  }
116
- const handled = await new Promise((resolve) => {
117
- localStorage.run(request, () => {
118
- prerenderHandler.handler(request, response, { prerenderOnly: true }).then((result) => resolve(result)).catch(() => resolve(true));
119
- });
120
- });
118
+ const handled = await localStorage.run(
119
+ request,
120
+ () => prerenderHandler.handler(request, response, { prerenderOnly: true })
121
+ );
121
122
  if (!handled) {
122
123
  return next();
123
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.3.3",
3
+ "version": "6.3.4",
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
  "xxhash-wasm": "^1.1.0",
165
165
  "yargs-parser": "^22.0.0",
166
166
  "zod": "^4.3.6",
167
- "@astrojs/markdown-remark": "7.1.2",
168
167
  "@astrojs/internal-helpers": "0.9.1",
168
+ "@astrojs/markdown-remark": "7.1.2",
169
169
  "@astrojs/telemetry": "3.3.2"
170
170
  },
171
171
  "optionalDependencies": {
@@ -198,7 +198,7 @@
198
198
  "remark-code-titles": "^0.1.2",
199
199
  "rollup": "^4.58.0",
200
200
  "sass": "^1.98.0",
201
- "typescript": "^5.9.3",
201
+ "typescript": "^6.0.3",
202
202
  "undici": "^7.22.0",
203
203
  "unified": "^11.0.5",
204
204
  "vitest": "^4.1.0",
@@ -223,8 +223,8 @@
223
223
  "build:ci": "pnpm run prebuild && astro-scripts build \"src/**/*.{ts,js}\" --copy-wasm",
224
224
  "dev": "astro-scripts dev --copy-wasm --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.{ts,js}\"",
225
225
  "test": "pnpm run test:unit && pnpm run test:integration && pnpm run test:types",
226
- "test:match": "astro-scripts test \"test/**/*.test.js\" --match",
227
- "test:cli": "astro-scripts test \"test/**/cli.test.js\"",
226
+ "test:match": "astro-scripts test \"test/**/*.test.ts\" --match",
227
+ "test:cli": "astro-scripts test \"test/**/cli.test.ts\"",
228
228
  "test:e2e": "pnpm test:e2e:chrome && pnpm test:e2e:firefox",
229
229
  "test:e2e:match": "playwright test -g",
230
230
  "test:e2e:chrome": "playwright test",
@@ -134,6 +134,7 @@ declare module 'astro:content' {
134
134
  type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
135
135
  type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
136
136
  type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
137
+ type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
137
138
 
138
139
  type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
139
140
  LiveContentConfig['collections'][C]['schema'] extends undefined