astro 6.0.5 → 6.0.6

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.
package/bin/astro.mjs CHANGED
@@ -8,12 +8,9 @@ const CI_INSTRUCTIONS = {
8
8
  VERCEL: 'https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-version',
9
9
  };
10
10
 
11
- // TODO: remove once Stackblitz supports Node 22
12
- const IS_STACKBLITZ = !!process.versions.webcontainer;
13
-
14
11
  // Hardcode supported Node.js version so we don't have to read differently in CJS & ESM.
15
- const engines = IS_STACKBLITZ ? '>=20.19.1' : '>=22.12.0';
16
- const skipSemverCheckIfAbove = IS_STACKBLITZ ? 21 : 23;
12
+ const engines = '>=22.12.0';
13
+ const skipSemverCheckIfAbove = 23;
17
14
 
18
15
  /** `astro *` */
19
16
  async function main() {
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.5";
3
+ version = "6.0.6";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -189,7 +189,7 @@ ${contentConfig.error.message}`
189
189
  logger.info("Content config changed");
190
190
  shouldClear = true;
191
191
  }
192
- if (previousAstroVersion && previousAstroVersion !== "6.0.5") {
192
+ if (previousAstroVersion && previousAstroVersion !== "6.0.6") {
193
193
  logger.info("Astro version changed");
194
194
  shouldClear = true;
195
195
  }
@@ -197,8 +197,8 @@ ${contentConfig.error.message}`
197
197
  logger.info("Clearing content store");
198
198
  this.#store.clearAll();
199
199
  }
200
- if ("6.0.5") {
201
- this.#store.metaStore().set("astro-version", "6.0.5");
200
+ if ("6.0.6") {
201
+ this.#store.metaStore().set("astro-version", "6.0.6");
202
202
  }
203
203
  if (currentConfigDigest) {
204
204
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -32,6 +32,7 @@ import { AstroIntegrationLogger, Logger } from "../logger/core.js";
32
32
  import { RenderContext } from "../render-context.js";
33
33
  import { redirectTemplate } from "../routing/3xx.js";
34
34
  import { ensure404Route } from "../routing/astro-designed-error-pages.js";
35
+ import { routeHasHtmlExtension } from "../routing/helpers.js";
35
36
  import { matchRoute } from "../routing/match.js";
36
37
  import { applyCacheHeaders } from "../cache/runtime/cache.js";
37
38
  import { Router } from "../routing/router.js";
@@ -303,7 +304,7 @@ class BaseApp {
303
304
  });
304
305
  }
305
306
  let pathname = this.getPathnameFromRequest(request);
306
- if (this.isDev()) {
307
+ if (this.isDev() && !routeHasHtmlExtension(routeData)) {
307
308
  pathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
308
309
  }
309
310
  const defaultStatus = this.getDefaultStatusCode(routeData, pathname);
@@ -81,13 +81,19 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
81
81
  const { settings } = opts;
82
82
  const routes = [];
83
83
  const domainLookupTable = {};
84
- const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
85
- if (settings.scripts.some((script) => script.stage === "page")) {
86
- staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
87
- }
84
+ const rawEntryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
88
85
  const assetQueryParams = settings.adapter?.client?.assetQueryParams;
89
86
  const assetQueryString = assetQueryParams ? assetQueryParams.toString() : void 0;
90
87
  const appendAssetQuery = (pth) => assetQueryString ? `${pth}?${assetQueryString}` : pth;
88
+ const entryModules = Object.fromEntries(
89
+ Object.entries(rawEntryModules).map(([key, value]) => [
90
+ key,
91
+ value ? appendAssetQuery(value) : value
92
+ ])
93
+ );
94
+ if (settings.scripts.some((script) => script.stage === "page")) {
95
+ staticFiles.push(rawEntryModules[PAGE_SCRIPT_ID]);
96
+ }
91
97
  const prefixAssetPath = (pth) => {
92
98
  let result = "";
93
99
  if (settings.config.build.assetsPrefix) {
@@ -118,7 +124,7 @@ async function buildManifest(opts, internals, staticFiles, encodedKey) {
118
124
  if (!pageData) continue;
119
125
  const scripts = [];
120
126
  if (settings.scripts.some((script) => script.stage === "page")) {
121
- const src = entryModules[PAGE_SCRIPT_ID];
127
+ const src = rawEntryModules[PAGE_SCRIPT_ID];
122
128
  scripts.push({
123
129
  type: "external",
124
130
  value: appendAssetQuery(src)
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.0.5";
1
+ const ASTRO_VERSION = "6.0.6";
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";
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
26
26
  await telemetry.record([]);
27
27
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
28
28
  const logger = restart.container.logger;
29
- const currentVersion = "6.0.5";
29
+ const currentVersion = "6.0.6";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"6.0.5"}`
272
+ `v${"6.0.6"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_404_COMPONENT } from "../constants.js";
2
2
  import { AstroError, AstroErrorData } from "../errors/index.js";
3
- import { routeIsFallback, routeIsRedirect } from "../routing/helpers.js";
3
+ import { routeHasHtmlExtension, routeIsFallback, routeIsRedirect } from "../routing/helpers.js";
4
4
  import { callGetStaticPaths, findPathItemByKey } from "./route-cache.js";
5
5
  async function getProps(opts) {
6
6
  const {
@@ -44,11 +44,9 @@ async function getProps(opts) {
44
44
  }
45
45
  function getParams(route, pathname) {
46
46
  if (!route.params.length) return {};
47
- let path = pathname;
48
- if (pathname.endsWith(".html")) {
49
- path = path.slice(0, -5);
50
- }
51
- const paramsMatch = route.pattern.exec(path) || route.fallbackRoutes.map((fallbackRoute) => fallbackRoute.pattern.exec(path)).find((x) => x);
47
+ const path = pathname.endsWith(".html") && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
48
+ const allPatterns = [route, ...route.fallbackRoutes].map((r) => r.pattern);
49
+ const paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
52
50
  if (!paramsMatch) return {};
53
51
  const params = {};
54
52
  route.params.forEach((key, i) => {
@@ -1,17 +1,39 @@
1
1
  import { getAssetsPrefix } from "../../assets/utils/getAssetsPrefix.js";
2
2
  import { fileExtension, joinPaths, prependForwardSlash, slash } from "../../core/path.js";
3
+ const URL_PARSE_BASE = "https://astro.build";
4
+ function splitAssetPath(path) {
5
+ const parsed = new URL(path, URL_PARSE_BASE);
6
+ const isAbsolute = URL.canParse(path);
7
+ const pathname = !isAbsolute && !path.startsWith("/") ? parsed.pathname.slice(1) : parsed.pathname;
8
+ return {
9
+ pathname,
10
+ suffix: `${parsed.search}${parsed.hash}`
11
+ };
12
+ }
13
+ function appendQueryParams(path, queryParams) {
14
+ const queryString = queryParams.toString();
15
+ if (!queryString) {
16
+ return path;
17
+ }
18
+ const hashIndex = path.indexOf("#");
19
+ const basePath = hashIndex === -1 ? path : path.slice(0, hashIndex);
20
+ const hash = hashIndex === -1 ? "" : path.slice(hashIndex);
21
+ const separator = basePath.includes("?") ? "&" : "?";
22
+ return `${basePath}${separator}${queryString}${hash}`;
23
+ }
3
24
  function createAssetLink(href, base, assetsPrefix, queryParams) {
25
+ const { pathname, suffix } = splitAssetPath(href);
4
26
  let url = "";
5
27
  if (assetsPrefix) {
6
- const pf = getAssetsPrefix(fileExtension(href), assetsPrefix);
7
- url = joinPaths(pf, slash(href));
28
+ const pf = getAssetsPrefix(fileExtension(pathname), assetsPrefix);
29
+ url = joinPaths(pf, slash(pathname)) + suffix;
8
30
  } else if (base) {
9
- url = prependForwardSlash(joinPaths(base, slash(href)));
31
+ url = prependForwardSlash(joinPaths(base, slash(pathname))) + suffix;
10
32
  } else {
11
33
  url = href;
12
34
  }
13
35
  if (queryParams) {
14
- url += "?" + queryParams.toString();
36
+ url = appendQueryParams(url, queryParams);
15
37
  }
16
38
  return url;
17
39
  }
@@ -32,6 +32,12 @@ export declare function getCustom404Route(manifestData: RoutesList): RouteData |
32
32
  * Return a user-provided 500 route if one exists.
33
33
  */
34
34
  export declare function getCustom500Route(manifestData: RoutesList): RouteData | undefined;
35
+ /**
36
+ * Returns true if the route definition contains `.html` as a static segment part,
37
+ * as is the case for routes like `[slug].html.astro`. Used to avoid stripping the
38
+ * `.html` suffix from pathnames that intentionally include it.
39
+ */
40
+ export declare function routeHasHtmlExtension(route: RouteData): boolean;
35
41
  export declare function hasNonPrerenderedProjectRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
36
42
  includeEndpoints?: boolean;
37
43
  }): boolean;
@@ -25,6 +25,11 @@ function getCustom404Route(manifestData) {
25
25
  function getCustom500Route(manifestData) {
26
26
  return manifestData.routes.find((r) => isRoute500(r.route));
27
27
  }
28
+ function routeHasHtmlExtension(route) {
29
+ return route.segments.some(
30
+ (segment) => segment.some((part) => !part.dynamic && part.content.includes(".html"))
31
+ );
32
+ }
28
33
  function hasNonPrerenderedProjectRoute(routes, options) {
29
34
  const includeEndpoints = options?.includeEndpoints ?? true;
30
35
  const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
@@ -38,6 +43,7 @@ export {
38
43
  getCustom500Route,
39
44
  getFallbackRoute,
40
45
  hasNonPrerenderedProjectRoute,
46
+ routeHasHtmlExtension,
41
47
  routeIsFallback,
42
48
  routeIsRedirect
43
49
  };
@@ -4,7 +4,13 @@ import type { ModuleLoader } from './module-loader/index.js';
4
4
  */
5
5
  export declare function normalizePath(id: string): string;
6
6
  /**
7
- * Resolve the hydration paths so that it can be imported in the client
7
+ * Resolve island component specifiers to stable paths for hydration metadata.
8
+ *
9
+ * Examples:
10
+ * - `./components/Button.jsx` from `/app/src/pages/index.astro`
11
+ * -> `/app/src/pages/components/Button.tsx` (when `.tsx` exists)
12
+ * - `#components/react/Counter.tsx`
13
+ * -> `/app/src/components/react/Counter.tsx` via package `imports`
8
14
  */
9
15
  export declare function resolvePath(specifier: string, importer: string): string;
10
16
  export declare function rootRelativePath(root: URL, idOrUrl: URL | string, shouldPrependForwardSlash?: boolean): string;
@@ -1,5 +1,6 @@
1
+ import { createRequire } from "node:module";
1
2
  import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
4
  import { prependForwardSlash, slash } from "../core/path.js";
4
5
  import { resolveJsToTs, unwrapId, VALID_ID_PREFIX, viteID } from "./util.js";
5
6
  const isWindows = typeof process !== "undefined" && process.platform === "win32";
@@ -10,6 +11,22 @@ function resolvePath(specifier, importer) {
10
11
  if (specifier.startsWith(".")) {
11
12
  const absoluteSpecifier = path.resolve(path.dirname(importer), specifier);
12
13
  return resolveJsToTs(normalizePath(absoluteSpecifier));
14
+ } else if (specifier.startsWith("#")) {
15
+ try {
16
+ const resolved = createRequire(pathToFileURL(importer)).resolve(specifier);
17
+ return resolveJsToTs(normalizePath(resolved));
18
+ } catch {
19
+ try {
20
+ const importerURL = pathToFileURL(importer).toString();
21
+ const resolved = import.meta.resolve(specifier, importerURL);
22
+ const resolvedUrl = new URL(resolved);
23
+ if (resolvedUrl.protocol === "file:") {
24
+ return resolveJsToTs(normalizePath(fileURLToPath(resolvedUrl)));
25
+ }
26
+ } catch {
27
+ }
28
+ }
29
+ return specifier;
13
30
  } else {
14
31
  return specifier;
15
32
  }
@@ -1,4 +1,4 @@
1
- import type { Plugin } from 'vite';
1
+ import { type Plugin } from 'vite';
2
2
  import type { AstroSettings } from '../types/astro.js';
3
3
  export declare const SERIALIZED_MANIFEST_ID = "virtual:astro:manifest";
4
4
  export declare const SERIALIZED_MANIFEST_RESOLVED_ID: string;
@@ -1,3 +1,5 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { normalizePath } from "vite";
1
3
  import { ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID } from "../actions/consts.js";
2
4
  import { toFallbackType } from "../core/app/common.js";
3
5
  import { toRoutingStrategy } from "../core/app/entrypoints/index.js";
@@ -31,8 +33,9 @@ function serializedManifestPlugin({
31
33
  command,
32
34
  sync
33
35
  }) {
36
+ const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
34
37
  function reloadManifest(path, server) {
35
- if (path != null && path.startsWith(settings.config.srcDir.pathname)) {
38
+ if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
36
39
  const environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
37
40
  const virtualMod = environment.moduleGraph.getModuleById(SERIALIZED_MANIFEST_RESOLVED_ID);
38
41
  if (!virtualMod) return;
@@ -1,10 +1,12 @@
1
1
  import type { HmrContext } from 'vite';
2
2
  import type { Logger } from '../core/logger/core.js';
3
+ import type { CompileAstroResult } from './compile.js';
3
4
  import type { CompileMetadata } from './types.js';
4
5
  interface HandleHotUpdateOptions {
5
6
  logger: Logger;
7
+ compile: (code: string, filename: string) => Promise<CompileAstroResult>;
6
8
  astroFileToCompileMetadata: Map<string, CompileMetadata>;
7
9
  }
8
- export declare function handleHotUpdate(ctx: HmrContext, { logger, astroFileToCompileMetadata }: HandleHotUpdateOptions): Promise<import("vite").ModuleNode[] | undefined>;
10
+ export declare function handleHotUpdate(ctx: HmrContext, { logger, compile, astroFileToCompileMetadata }: HandleHotUpdateOptions): Promise<import("vite").ModuleNode[] | undefined>;
9
11
  export declare function isStyleOnlyChanged(oldCode: string, newCode: string): boolean;
10
12
  export {};
@@ -1,6 +1,6 @@
1
1
  import { parseAstroRequest } from "./query.js";
2
2
  import { frontmatterRE } from "./utils.js";
3
- async function handleHotUpdate(ctx, { logger, astroFileToCompileMetadata }) {
3
+ async function handleHotUpdate(ctx, { logger, compile, astroFileToCompileMetadata }) {
4
4
  for (const [astroFile, compileData] of astroFileToCompileMetadata) {
5
5
  const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.includes(ctx.file));
6
6
  if (isUpdatedFileCssDep) {
@@ -12,7 +12,11 @@ async function handleHotUpdate(ctx, { logger, astroFileToCompileMetadata }) {
12
12
  const newCode = await ctx.read();
13
13
  if (isStyleOnlyChanged(oldCode, newCode)) {
14
14
  logger.debug("watch", "style-only change");
15
- astroFileToCompileMetadata.delete(ctx.file);
15
+ try {
16
+ await compile(newCode, ctx.file);
17
+ } catch {
18
+ astroFileToCompileMetadata.delete(ctx.file);
19
+ }
16
20
  return ctx.modules.filter((mod) => {
17
21
  if (!mod.id) {
18
22
  return false;
@@ -246,7 +246,7 @@ File: ${id}`
246
246
  }
247
247
  },
248
248
  async handleHotUpdate(ctx) {
249
- return handleHotUpdate(ctx, { logger, astroFileToCompileMetadata });
249
+ return handleHotUpdate(ctx, { logger, compile, astroFileToCompileMetadata });
250
250
  }
251
251
  },
252
252
  {
@@ -45,9 +45,6 @@ function createVitePluginAstroServer({
45
45
  const prerenderEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.prerender];
46
46
  const runnableSsrEnvironment = isRunnableDevEnvironment(ssrEnvironment) ? ssrEnvironment : void 0;
47
47
  const runnablePrerenderEnvironment = isRunnableDevEnvironment(prerenderEnvironment) ? prerenderEnvironment : void 0;
48
- if (!runnableSsrEnvironment && !runnablePrerenderEnvironment) {
49
- return;
50
- }
51
48
  async function createHandler(environment) {
52
49
  const loader = createViteLoader(viteServer, environment);
53
50
  const { default: createAstroServerApp } = await environment.runner.import(ASTRO_DEV_SERVER_APP_ID);
@@ -84,17 +81,16 @@ function createVitePluginAstroServer({
84
81
  );
85
82
  }
86
83
  }
87
- process.on("unhandledRejection", handleUnhandledRejection);
88
- viteServer.httpServer?.on("close", () => {
89
- process.off("unhandledRejection", handleUnhandledRejection);
90
- });
84
+ if (ssrHandler || prerenderHandler) {
85
+ process.on("unhandledRejection", handleUnhandledRejection);
86
+ viteServer.httpServer?.on("close", () => {
87
+ process.off("unhandledRejection", handleUnhandledRejection);
88
+ });
89
+ }
91
90
  return () => {
92
91
  const shouldHandlePrerenderInCore = Boolean(
93
92
  viteServer[devPrerenderMiddlewareSymbol]
94
93
  );
95
- if (!ssrHandler && !(prerenderHandler && shouldHandlePrerenderInCore)) {
96
- return;
97
- }
98
94
  viteServer.middlewares.stack.unshift({
99
95
  route: "",
100
96
  handle: baseMiddleware(settings, logger)
@@ -52,11 +52,12 @@ async function astroPluginRoutes({
52
52
  };
53
53
  }
54
54
  );
55
+ const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
55
56
  async function rebuildRoutes(path = null, server) {
56
- if (path != null && path.startsWith(settings.config.srcDir.pathname)) {
57
+ if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
57
58
  logger.debug(
58
59
  "update",
59
- `Re-calculating routes for ${path.slice(settings.config.srcDir.pathname.length)}`
60
+ `Re-calculating routes for ${normalizePath(path).slice(normalizedSrcDir.length)}`
60
61
  );
61
62
  const file = pathToFileURL(normalizePath(path));
62
63
  const newRoutesList = await createRoutesList(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.5",
3
+ "version": "6.0.6",
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",
@@ -153,8 +153,8 @@
153
153
  "xxhash-wasm": "^1.1.0",
154
154
  "yargs-parser": "^22.0.0",
155
155
  "zod": "^4.3.6",
156
- "@astrojs/markdown-remark": "7.0.0",
157
156
  "@astrojs/internal-helpers": "0.8.0",
157
+ "@astrojs/markdown-remark": "7.0.1",
158
158
  "@astrojs/telemetry": "3.3.0"
159
159
  },
160
160
  "optionalDependencies": {
@@ -194,7 +194,7 @@
194
194
  "astro-scripts": "0.0.14"
195
195
  },
196
196
  "engines": {
197
- "node": "^20.19.1 || >=22.12.0",
197
+ "node": ">=22.12.0",
198
198
  "npm": ">=9.6.5",
199
199
  "pnpm": ">=7.1.0"
200
200
  },
@@ -111,11 +111,12 @@ declare module 'astro:content' {
111
111
  type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
112
112
  ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
113
113
  >;
114
+ type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
114
115
  type InferLoaderSchema<
115
116
  C extends keyof DataEntryMap,
116
- L = Required<ContentConfig['collections'][C]>['loader'],
117
+ L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
117
118
  > = L extends { schema: import('astro/zod').ZodSchema }
118
- ? import('astro/zod').infer<Required<ContentConfig['collections'][C]>['loader']['schema']>
119
+ ? import('astro/zod').infer<L['schema']>
119
120
  : any;
120
121
 
121
122
  type DataEntryMap = {