astro 5.1.10 → 5.2.1

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 (65) hide show
  1. package/client.d.ts +14 -0
  2. package/dist/assets/endpoint/config.d.ts +2 -2
  3. package/dist/cli/add/index.js +51 -25
  4. package/dist/config/index.js +4 -4
  5. package/dist/container/index.js +6 -0
  6. package/dist/content/content-layer.js +3 -3
  7. package/dist/core/app/index.d.ts +2 -2
  8. package/dist/core/app/index.js +33 -0
  9. package/dist/core/app/pipeline.d.ts +2 -2
  10. package/dist/core/app/types.d.ts +9 -3
  11. package/dist/core/build/generate.js +8 -2
  12. package/dist/core/build/index.js +13 -8
  13. package/dist/core/build/page-data.d.ts +2 -2
  14. package/dist/core/build/pipeline.d.ts +3 -3
  15. package/dist/core/build/pipeline.js +4 -4
  16. package/dist/core/build/plugins/plugin-manifest.js +9 -3
  17. package/dist/core/build/types.d.ts +2 -2
  18. package/dist/core/config/schema.d.ts +17 -0
  19. package/dist/core/config/schema.js +4 -2
  20. package/dist/core/constants.js +1 -1
  21. package/dist/core/create-vite.d.ts +6 -6
  22. package/dist/core/create-vite.js +6 -4
  23. package/dist/core/dev/container.js +6 -5
  24. package/dist/core/dev/dev.js +1 -1
  25. package/dist/core/errors/errors-data.d.ts +22 -0
  26. package/dist/core/errors/errors-data.js +12 -0
  27. package/dist/core/messages.js +2 -2
  28. package/dist/core/preview/index.js +2 -2
  29. package/dist/core/redirects/render.d.ts +2 -0
  30. package/dist/core/redirects/render.js +24 -5
  31. package/dist/core/routing/astro-designed-error-pages.d.ts +2 -2
  32. package/dist/core/routing/index.d.ts +1 -1
  33. package/dist/core/routing/index.js +2 -2
  34. package/dist/core/routing/manifest/create.d.ts +3 -3
  35. package/dist/core/routing/manifest/create.js +12 -10
  36. package/dist/core/routing/match.d.ts +3 -3
  37. package/dist/core/server-islands/endpoint.d.ts +2 -2
  38. package/dist/core/sync/index.d.ts +5 -3
  39. package/dist/core/sync/index.js +13 -8
  40. package/dist/i18n/utils.d.ts +2 -0
  41. package/dist/i18n/utils.js +24 -0
  42. package/dist/manifest/virtual-module.d.ts +9 -0
  43. package/dist/manifest/virtual-module.js +111 -0
  44. package/dist/template/4xx.d.ts +1 -0
  45. package/dist/template/4xx.js +14 -1
  46. package/dist/types/astro.d.ts +1 -1
  47. package/dist/types/public/config.d.ts +36 -18
  48. package/dist/types/public/index.d.ts +1 -0
  49. package/dist/types/public/manifest.d.ts +23 -0
  50. package/dist/types/public/manifest.js +0 -0
  51. package/dist/vite-plugin-astro-server/base.js +3 -13
  52. package/dist/vite-plugin-astro-server/pipeline.d.ts +4 -4
  53. package/dist/vite-plugin-astro-server/pipeline.js +5 -5
  54. package/dist/vite-plugin-astro-server/plugin.d.ts +4 -4
  55. package/dist/vite-plugin-astro-server/plugin.js +21 -10
  56. package/dist/vite-plugin-astro-server/request.d.ts +3 -3
  57. package/dist/vite-plugin-astro-server/request.js +3 -3
  58. package/dist/vite-plugin-astro-server/route.d.ts +4 -4
  59. package/dist/vite-plugin-astro-server/route.js +7 -7
  60. package/dist/vite-plugin-astro-server/trailing-slash.d.ts +3 -0
  61. package/dist/vite-plugin-astro-server/trailing-slash.js +30 -0
  62. package/dist/vite-plugin-scanner/index.d.ts +3 -3
  63. package/dist/vite-plugin-scanner/index.js +3 -3
  64. package/package.json +4 -4
  65. package/templates/content/types.d.ts +25 -24
@@ -9,7 +9,7 @@ import { AstroError, AstroErrorData } from "../core/errors/index.js";
9
9
  import { patchOverlay } from "../core/errors/overlay.js";
10
10
  import { NOOP_MIDDLEWARE_FN } from "../core/middleware/noop-middleware.js";
11
11
  import { createViteLoader } from "../core/module-loader/index.js";
12
- import { createRouteManifest } from "../core/routing/index.js";
12
+ import { createRoutesList } from "../core/routing/index.js";
13
13
  import { getRoutePrerenderOption } from "../core/routing/manifest/prerender.js";
14
14
  import { toFallbackType, toRoutingStrategy } from "../i18n/utils.js";
15
15
  import { runHookRoutesResolved } from "../integrations/hooks.js";
@@ -19,21 +19,22 @@ import { recordServerError } from "./error.js";
19
19
  import { DevPipeline } from "./pipeline.js";
20
20
  import { handleRequest } from "./request.js";
21
21
  import { setRouteError } from "./server-state.js";
22
+ import { trailingSlashMiddleware } from "./trailing-slash.js";
22
23
  function createVitePluginAstroServer({
23
24
  settings,
24
25
  logger,
25
26
  fs: fsMod,
26
- manifest: routeManifest,
27
- ssrManifest: devSSRManifest
27
+ routesList,
28
+ manifest
28
29
  }) {
29
30
  return {
30
31
  name: "astro:server",
31
32
  configureServer(viteServer) {
32
33
  const loader = createViteLoader(viteServer);
33
- const pipeline = DevPipeline.create(routeManifest, {
34
+ const pipeline = DevPipeline.create(routesList, {
34
35
  loader,
35
36
  logger,
36
- manifest: devSSRManifest,
37
+ manifest,
37
38
  settings
38
39
  });
39
40
  const controller = createController({ loader });
@@ -41,7 +42,7 @@ function createVitePluginAstroServer({
41
42
  async function rebuildManifest(path = null) {
42
43
  pipeline.clearRouteCache();
43
44
  if (path !== null) {
44
- const route = routeManifest.routes.find(
45
+ const route = routesList.routes.find(
45
46
  (r) => normalizePath(path) === normalizePath(fileURLToPath(new URL(r.component, settings.config.root)))
46
47
  );
47
48
  if (!route) {
@@ -52,15 +53,15 @@ function createVitePluginAstroServer({
52
53
  try {
53
54
  const content = await fsMod.promises.readFile(routePath, "utf-8");
54
55
  await getRoutePrerenderOption(content, route, settings, logger);
55
- await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });
56
+ await runHookRoutesResolved({ routes: routesList.routes, settings, logger });
56
57
  } catch (_) {
57
58
  }
58
59
  } else {
59
- routeManifest = await createRouteManifest({ settings, fsMod }, logger, { dev: true });
60
+ routesList = await createRoutesList({ settings, fsMod }, logger, { dev: true });
60
61
  }
61
62
  warnMissingAdapter(logger, settings);
62
63
  pipeline.manifest.checkOrigin = settings.config.security.checkOrigin && settings.buildOutput === "server";
63
- pipeline.setManifestData(routeManifest);
64
+ pipeline.setManifestData(routesList);
64
65
  }
65
66
  viteServer.watcher.on("add", rebuildManifest.bind(null, null));
66
67
  viteServer.watcher.on("unlink", rebuildManifest.bind(null, null));
@@ -90,6 +91,10 @@ function createVitePluginAstroServer({
90
91
  route: "",
91
92
  handle: baseMiddleware(settings, logger)
92
93
  });
94
+ viteServer.middlewares.stack.unshift({
95
+ route: "",
96
+ handle: trailingSlashMiddleware(settings)
97
+ });
93
98
  viteServer.middlewares.use(async function astroDevHandler(request, response) {
94
99
  if (request.url === void 0 || !request.method) {
95
100
  response.writeHead(500, "Incomplete request");
@@ -99,7 +104,7 @@ function createVitePluginAstroServer({
99
104
  localStorage.run(request, () => {
100
105
  handleRequest({
101
106
  pipeline,
102
- manifestData: routeManifest,
107
+ routesList,
103
108
  controller,
104
109
  incomingRequest: request,
105
110
  incomingResponse: response
@@ -129,6 +134,12 @@ function createDevelopmentManifest(settings) {
129
134
  }
130
135
  return {
131
136
  hrefRoot: settings.config.root.toString(),
137
+ srcDir: settings.config.srcDir,
138
+ cacheDir: settings.config.cacheDir,
139
+ outDir: settings.config.outDir,
140
+ buildServerDir: settings.config.build.server,
141
+ buildClientDir: settings.config.build.client,
142
+ publicDir: settings.config.publicDir,
132
143
  trailingSlash: settings.config.trailingSlash,
133
144
  buildFormat: settings.config.build.format,
134
145
  compressHTML: settings.config.compressHTML,
@@ -1,14 +1,14 @@
1
1
  import type http from 'node:http';
2
- import type { ManifestData } from '../types/astro.js';
2
+ import type { RoutesList } from '../types/astro.js';
3
3
  import type { DevServerController } from './controller.js';
4
4
  import type { DevPipeline } from './pipeline.js';
5
5
  type HandleRequest = {
6
6
  pipeline: DevPipeline;
7
- manifestData: ManifestData;
7
+ routesList: RoutesList;
8
8
  controller: DevServerController;
9
9
  incomingRequest: http.IncomingMessage;
10
10
  incomingResponse: http.ServerResponse;
11
11
  };
12
12
  /** The main logic to route dev server requests to pages in Astro. */
13
- export declare function handleRequest({ pipeline, manifestData, controller, incomingRequest, incomingResponse, }: HandleRequest): Promise<void>;
13
+ export declare function handleRequest({ pipeline, routesList, controller, incomingRequest, incomingResponse, }: HandleRequest): Promise<void>;
14
14
  export {};
@@ -5,7 +5,7 @@ import { handle500Response } from "./response.js";
5
5
  import { handleRoute, matchRoute } from "./route.js";
6
6
  async function handleRequest({
7
7
  pipeline,
8
- manifestData,
8
+ routesList,
9
9
  controller,
10
10
  incomingRequest,
11
11
  incomingResponse
@@ -35,7 +35,7 @@ async function handleRequest({
35
35
  controller,
36
36
  pathname,
37
37
  async run() {
38
- const matchedRoute = await matchRoute(pathname, manifestData, pipeline);
38
+ const matchedRoute = await matchRoute(pathname, routesList, pipeline);
39
39
  const resolvedPathname = matchedRoute?.resolvedPathname ?? pathname;
40
40
  return await handleRoute({
41
41
  matchedRoute,
@@ -43,7 +43,7 @@ async function handleRequest({
43
43
  pathname: resolvedPathname,
44
44
  body,
45
45
  pipeline,
46
- manifestData,
46
+ routesList,
47
47
  incomingRequest,
48
48
  incomingResponse
49
49
  });
@@ -1,5 +1,5 @@
1
1
  import type http from 'node:http';
2
- import type { ComponentInstance, ManifestData } from '../types/astro.js';
2
+ import type { ComponentInstance, RoutesList } from '../types/astro.js';
3
3
  import type { RouteData } from '../types/public/internal.js';
4
4
  import type { DevPipeline } from './pipeline.js';
5
5
  type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (...args: any) => Promise<infer R> ? R : any;
@@ -10,16 +10,16 @@ export interface MatchedRoute {
10
10
  preloadedComponent: ComponentInstance;
11
11
  mod: ComponentInstance;
12
12
  }
13
- export declare function matchRoute(pathname: string, manifestData: ManifestData, pipeline: DevPipeline): Promise<MatchedRoute | undefined>;
13
+ export declare function matchRoute(pathname: string, routesList: RoutesList, pipeline: DevPipeline): Promise<MatchedRoute | undefined>;
14
14
  type HandleRoute = {
15
15
  matchedRoute: AsyncReturnType<typeof matchRoute>;
16
16
  url: URL;
17
17
  pathname: string;
18
18
  body: ArrayBuffer | undefined;
19
- manifestData: ManifestData;
19
+ routesList: RoutesList;
20
20
  incomingRequest: http.IncomingMessage;
21
21
  incomingResponse: http.ServerResponse;
22
22
  pipeline: DevPipeline;
23
23
  };
24
- export declare function handleRoute({ matchedRoute, url, pathname, body, pipeline, manifestData, incomingRequest, incomingResponse, }: HandleRoute): Promise<void>;
24
+ export declare function handleRoute({ matchedRoute, url, pathname, body, pipeline, routesList, incomingRequest, incomingResponse, }: HandleRoute): Promise<void>;
25
25
  export {};
@@ -27,9 +27,9 @@ function getCustom404Route(manifestData) {
27
27
  function getCustom500Route(manifestData) {
28
28
  return manifestData.routes.find((r) => isRoute500(r.route));
29
29
  }
30
- async function matchRoute(pathname, manifestData, pipeline) {
30
+ async function matchRoute(pathname, routesList, pipeline) {
31
31
  const { config, logger, routeCache, serverLike, settings } = pipeline;
32
- const matches = matchAllRoutes(pathname, manifestData);
32
+ const matches = matchAllRoutes(pathname, routesList);
33
33
  const preloadedMatches = await getSortedPreloadedMatches({ pipeline, matches, settings });
34
34
  for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
35
35
  try {
@@ -58,7 +58,7 @@ async function matchRoute(pathname, manifestData, pipeline) {
58
58
  }
59
59
  const altPathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
60
60
  if (altPathname !== pathname) {
61
- return await matchRoute(altPathname, manifestData, pipeline);
61
+ return await matchRoute(altPathname, routesList, pipeline);
62
62
  }
63
63
  if (matches.length) {
64
64
  const possibleRoutes = matches.flatMap((route) => route.component);
@@ -71,7 +71,7 @@ async function matchRoute(pathname, manifestData, pipeline) {
71
71
  ${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
72
72
  );
73
73
  }
74
- const custom404 = getCustom404Route(manifestData);
74
+ const custom404 = getCustom404Route(routesList);
75
75
  if (custom404) {
76
76
  const filePath = new URL(`./${custom404.component}`, config.root);
77
77
  const preloadedComponent = await pipeline.preload(custom404, filePath);
@@ -91,7 +91,7 @@ async function handleRoute({
91
91
  pathname,
92
92
  body,
93
93
  pipeline,
94
- manifestData,
94
+ routesList,
95
95
  incomingRequest,
96
96
  incomingResponse
97
97
  }) {
@@ -148,7 +148,7 @@ async function handleRoute({
148
148
  !response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute ? response.status : statusCodedMatched ?? response.status
149
149
  );
150
150
  } catch (err) {
151
- const custom500 = getCustom500Route(manifestData);
151
+ const custom500 = getCustom500Route(routesList);
152
152
  if (!custom500) {
153
153
  throw err;
154
154
  }
@@ -177,7 +177,7 @@ async function handleRoute({
177
177
  if (statusCode === 404 && // If the body isn't null, that means the user sets the 404 status
178
178
  // but uses the current route to handle the 404
179
179
  response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
180
- const fourOhFourRoute = await matchRoute("/404", manifestData, pipeline);
180
+ const fourOhFourRoute = await matchRoute("/404", routesList, pipeline);
181
181
  if (fourOhFourRoute) {
182
182
  renderContext = await RenderContext.create({
183
183
  locals,
@@ -0,0 +1,3 @@
1
+ import type * as vite from 'vite';
2
+ import type { AstroSettings } from '../types/astro.js';
3
+ export declare function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.NextHandleFunction;
@@ -0,0 +1,30 @@
1
+ import { collapseDuplicateTrailingSlashes, hasFileExtension } from "@astrojs/internal-helpers/path";
2
+ import { trailingSlashMismatchTemplate } from "../template/4xx.js";
3
+ import { writeHtmlResponse, writeRedirectResponse } from "./response.js";
4
+ function trailingSlashMiddleware(settings) {
5
+ const { trailingSlash } = settings.config;
6
+ return function devTrailingSlash(req, res, next) {
7
+ const url = req.url;
8
+ const destination = collapseDuplicateTrailingSlashes(url, true);
9
+ if (url && destination !== url) {
10
+ return writeRedirectResponse(res, 301, destination);
11
+ }
12
+ let pathname;
13
+ try {
14
+ pathname = decodeURI(new URL(url, "http://localhost").pathname);
15
+ } catch (e) {
16
+ return next(e);
17
+ }
18
+ if (pathname.startsWith("/_") || pathname.startsWith("/@")) {
19
+ return next();
20
+ }
21
+ if (trailingSlash === "never" && pathname.endsWith("/") && pathname !== "/" || trailingSlash === "always" && !pathname.endsWith("/") && !hasFileExtension(pathname)) {
22
+ const html = trailingSlashMismatchTemplate(pathname, trailingSlash);
23
+ return writeHtmlResponse(res, 404, html);
24
+ }
25
+ return next();
26
+ };
27
+ }
28
+ export {
29
+ trailingSlashMiddleware
30
+ };
@@ -1,9 +1,9 @@
1
1
  import type { Plugin as VitePlugin } from 'vite';
2
2
  import type { Logger } from '../core/logger/core.js';
3
- import type { AstroSettings, ManifestData } from '../types/astro.js';
3
+ import type { AstroSettings, RoutesList } from '../types/astro.js';
4
4
  export interface AstroPluginScannerOptions {
5
5
  settings: AstroSettings;
6
6
  logger: Logger;
7
- manifest: ManifestData;
7
+ routesList: RoutesList;
8
8
  }
9
- export default function astroScannerPlugin({ settings, logger, manifest, }: AstroPluginScannerOptions): VitePlugin;
9
+ export default function astroScannerPlugin({ settings, logger, routesList, }: AstroPluginScannerOptions): VitePlugin;
@@ -9,7 +9,7 @@ const KNOWN_FILE_EXTENSIONS = [".astro", ".js", ".ts"];
9
9
  function astroScannerPlugin({
10
10
  settings,
11
11
  logger,
12
- manifest
12
+ routesList
13
13
  }) {
14
14
  return {
15
15
  name: "astro:scanner",
@@ -26,7 +26,7 @@ function astroScannerPlugin({
26
26
  const fileIsPage = isPage(fileURL, settings);
27
27
  const fileIsEndpoint = isEndpoint(fileURL, settings);
28
28
  if (!(fileIsPage || fileIsEndpoint)) return;
29
- const route = manifest.routes.find((r) => {
29
+ const route = routesList.routes.find((r) => {
30
30
  const filePath = new URL(`./${r.component}`, settings.config.root);
31
31
  return normalizePath(fileURLToPath(filePath)) === filename;
32
32
  });
@@ -69,7 +69,7 @@ function astroScannerPlugin({
69
69
  const fileIsPage = isPage(fileURL, settings);
70
70
  const fileIsEndpoint = isEndpoint(fileURL, settings);
71
71
  if (!(fileIsPage || fileIsEndpoint)) return;
72
- const route = manifest.routes.find((r) => {
72
+ const route = routesList.routes.find((r) => {
73
73
  const filePath = new URL(`./${r.component}`, settings.config.root);
74
74
  return normalizePath(fileURLToPath(filePath)) === filename;
75
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.1.10",
3
+ "version": "5.2.1",
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",
@@ -150,7 +150,7 @@
150
150
  "unist-util-visit": "^5.0.0",
151
151
  "unstorage": "^1.14.4",
152
152
  "vfile": "^6.0.3",
153
- "vite": "^6.0.9",
153
+ "vite": "^6.0.11",
154
154
  "vitefu": "^1.0.5",
155
155
  "which-pm": "^3.0.0",
156
156
  "xxhash-wasm": "^1.1.0",
@@ -159,8 +159,8 @@
159
159
  "zod": "^3.24.1",
160
160
  "zod-to-json-schema": "^3.24.1",
161
161
  "zod-to-ts": "^1.2.0",
162
- "@astrojs/internal-helpers": "0.4.2",
163
- "@astrojs/markdown-remark": "6.0.2",
162
+ "@astrojs/internal-helpers": "0.5.0",
163
+ "@astrojs/markdown-remark": "6.1.0",
164
164
  "@astrojs/telemetry": "3.2.0"
165
165
  },
166
166
  "optionalDependencies": {
@@ -31,6 +31,21 @@ declare module 'astro:content' {
31
31
  ContentEntryMap[C]
32
32
  >['slug'];
33
33
 
34
+ export type ReferenceDataEntry<
35
+ C extends CollectionKey,
36
+ E extends keyof DataEntryMap[C] = string,
37
+ > = {
38
+ collection: C;
39
+ id: E;
40
+ };
41
+ export type ReferenceContentEntry<
42
+ C extends keyof ContentEntryMap,
43
+ E extends ValidContentEntrySlug<C> | (string & {}) = string,
44
+ > = {
45
+ collection: C;
46
+ slug: E;
47
+ };
48
+
34
49
  /** @deprecated Use `getEntry` instead. */
35
50
  export function getEntryBySlug<
36
51
  C extends keyof ContentEntryMap,
@@ -61,19 +76,17 @@ declare module 'astro:content' {
61
76
  export function getEntry<
62
77
  C extends keyof ContentEntryMap,
63
78
  E extends ValidContentEntrySlug<C> | (string & {}),
64
- >(entry: {
65
- collection: C;
66
- slug: E;
67
- }): E extends ValidContentEntrySlug<C>
79
+ >(
80
+ entry: ReferenceContentEntry<C, E>,
81
+ ): E extends ValidContentEntrySlug<C>
68
82
  ? Promise<CollectionEntry<C>>
69
83
  : Promise<CollectionEntry<C> | undefined>;
70
84
  export function getEntry<
71
85
  C extends keyof DataEntryMap,
72
86
  E extends keyof DataEntryMap[C] | (string & {}),
73
- >(entry: {
74
- collection: C;
75
- id: E;
76
- }): E extends keyof DataEntryMap[C]
87
+ >(
88
+ entry: ReferenceDataEntry<C, E>,
89
+ ): E extends keyof DataEntryMap[C]
77
90
  ? Promise<DataEntryMap[C][E]>
78
91
  : Promise<CollectionEntry<C> | undefined>;
79
92
  export function getEntry<
@@ -99,16 +112,10 @@ declare module 'astro:content' {
99
112
 
100
113
  /** Resolve an array of entry references from the same collection */
101
114
  export function getEntries<C extends keyof ContentEntryMap>(
102
- entries: {
103
- collection: C;
104
- slug: ValidContentEntrySlug<C>;
105
- }[],
115
+ entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
106
116
  ): Promise<CollectionEntry<C>[]>;
107
117
  export function getEntries<C extends keyof DataEntryMap>(
108
- entries: {
109
- collection: C;
110
- id: keyof DataEntryMap[C];
111
- }[],
118
+ entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
112
119
  ): Promise<CollectionEntry<C>[]>;
113
120
 
114
121
  export function render<C extends keyof AnyEntryMap>(
@@ -120,14 +127,8 @@ declare module 'astro:content' {
120
127
  ): import('astro/zod').ZodEffects<
121
128
  import('astro/zod').ZodString,
122
129
  C extends keyof ContentEntryMap
123
- ? {
124
- collection: C;
125
- slug: ValidContentEntrySlug<C>;
126
- }
127
- : {
128
- collection: C;
129
- id: keyof DataEntryMap[C];
130
- }
130
+ ? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
131
+ : ReferenceDataEntry<C, keyof DataEntryMap[C]>
131
132
  >;
132
133
  // Allow generic `string` to avoid excessive type errors in the config
133
134
  // if `dev` is not running to update as you edit.