astro 7.2.4 → 7.2.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.
Files changed (39) hide show
  1. package/bin/astro.mjs +15 -0
  2. package/client.d.ts +8 -4
  3. package/dist/assets/consts.d.ts +1 -1
  4. package/dist/assets/consts.js +1 -0
  5. package/dist/assets/fonts/infra/remote-runtime-font-file-url-resolver.d.ts +6 -4
  6. package/dist/assets/fonts/infra/remote-runtime-font-file-url-resolver.js +14 -7
  7. package/dist/assets/utils/node.js +3 -0
  8. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  9. package/dist/cli/server.js +13 -0
  10. package/dist/content/config.d.ts +2 -1
  11. package/dist/content/content-layer.js +3 -3
  12. package/dist/content/loaders/glob.js +3 -3
  13. package/dist/content/mutable-data-store.js +49 -2
  14. package/dist/core/build/plugins/plugin-incremental.js +8 -2
  15. package/dist/core/build/vite-build-config.js +15 -6
  16. package/dist/core/cache/memory-provider.js +6 -1
  17. package/dist/core/compile/compile.js +1 -1
  18. package/dist/core/constants.js +1 -1
  19. package/dist/core/create-vite.js +1 -1
  20. package/dist/core/dev/dev.js +1 -1
  21. package/dist/core/dev/lockfile.js +3 -0
  22. package/dist/core/errors/dev/utils.js +3 -3
  23. package/dist/core/fetch/fetch-state.js +3 -2
  24. package/dist/core/i18n/handler.js +5 -1
  25. package/dist/core/messages/runtime.js +1 -1
  26. package/dist/core/session/runtime.js +4 -2
  27. package/dist/core/session/types.d.ts +5 -5
  28. package/dist/core/util/normalized-url.d.ts +8 -0
  29. package/dist/core/util/normalized-url.js +10 -4
  30. package/dist/i18n/utils.js +3 -4
  31. package/dist/manifest/serialized.js +13 -0
  32. package/dist/runtime/server/astro-global.js +2 -2
  33. package/dist/runtime/server/render/csp.js +13 -5
  34. package/dist/transitions/swap-functions.js +4 -2
  35. package/dist/types/public/config.d.ts +1 -1
  36. package/dist/vite-plugin-routes/index.js +1 -1
  37. package/package.json +5 -5
  38. package/templates/content/module.mjs +3 -3
  39. package/types/content.d.ts +2 -2
package/bin/astro.mjs CHANGED
@@ -1,6 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ import module from 'node:module';
5
+
6
+ // In CI writing the cache is (most of the time) harmful, as it'll never get re-used and just slows down the CLI.
7
+ if (!process.env.CI) {
8
+ try {
9
+ module.enableCompileCache?.();
10
+ // Long-running commands like `astro dev` never reach the flush that happens on process exit.
11
+ setTimeout(() => {
12
+ try {
13
+ module.flushCompileCache?.();
14
+ } catch {}
15
+ }, 10_000).unref();
16
+ } catch {}
17
+ }
18
+
4
19
  const CI_INSTRUCTIONS = {
5
20
  NETLIFY: 'https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript',
6
21
  GITHUB_ACTIONS:
package/client.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  /// <reference path="./types/transitions.d.ts" />
8
8
 
9
9
  interface ImportMetaEnv {
10
- // TODO: remove in Astro 7
10
+ // TODO: remove in Astro 8
11
11
  /**
12
12
  * The prefix for Astro-generated asset links if the build.assetsPrefix config option is set. This can be used to create asset links not handled by Astro.
13
13
  * @deprecated This will be removed in a future major version of Astro. Use `build.assetsPrefix` from `astro:config/server` instead.
@@ -98,6 +98,10 @@ declare module '*.png' {
98
98
  const metadata: ImageMetadata;
99
99
  export default metadata;
100
100
  }
101
+ declare module '*.apng' {
102
+ const metadata: ImageMetadata;
103
+ export default metadata;
104
+ }
101
105
  declare module '*.tiff' {
102
106
  const metadata: ImageMetadata;
103
107
  export default metadata;
@@ -149,11 +153,11 @@ declare module 'astro:components' {
149
153
  export * from 'astro/components';
150
154
  }
151
155
 
152
- // TODO: remove in Astro 7
156
+ // TODO: remove in Astro 8
153
157
  /**
154
158
  * @deprecated
155
159
  * `import { z } from 'astro:schema'` is deprecated and will be removed
156
- * in Astro 7. Use `import { z } from 'astro/zod'` instead.
160
+ * in Astro 8. Use `import { z } from 'astro/zod'` instead.
157
161
  */
158
162
  declare module 'astro:schema' {
159
163
  export * from 'astro/zod';
@@ -162,7 +166,7 @@ declare module 'astro:schema' {
162
166
  /**
163
167
  * @deprecated
164
168
  * `import { z } from 'astro:schema'` is deprecated and will be removed
165
- * in Astro 7. Use `import { z } from 'astro/zod'` instead.
169
+ * in Astro 8. Use `import { z } from 'astro/zod'` instead.
166
170
  */
167
171
  export const z = zod.z;
168
172
  }
@@ -5,7 +5,7 @@ export declare const VIRTUAL_GET_IMAGE_ID = "virtual:astro:get-image";
5
5
  export declare const RESOLVED_VIRTUAL_GET_IMAGE_ID: string;
6
6
  export declare const VIRTUAL_IMAGE_STYLES_ID = "virtual:astro:image-styles.css";
7
7
  export declare const RESOLVED_VIRTUAL_IMAGE_STYLES_ID: string;
8
- export declare const VALID_INPUT_FORMATS: readonly ["jpeg", "jpg", "png", "tiff", "webp", "gif", "svg", "avif"];
8
+ export declare const VALID_INPUT_FORMATS: readonly ["jpeg", "jpg", "png", "apng", "tiff", "webp", "gif", "svg", "avif"];
9
9
  /**
10
10
  * Valid formats that our base services support.
11
11
  * Certain formats can be imported (namely SVGs) but will not be processed.
@@ -9,6 +9,7 @@ const VALID_INPUT_FORMATS = [
9
9
  "jpeg",
10
10
  "jpg",
11
11
  "png",
12
+ "apng",
12
13
  "tiff",
13
14
  "webp",
14
15
  "gif",
@@ -5,9 +5,11 @@ import type { RuntimeFontFileUrlResolver } from '../definitions.js';
5
5
  * During prerendering, a temporary Node HTTP server is started to
6
6
  * serve font files.
7
7
  *
8
- * We send request to the provided server address. `requestUrl` on
9
- * `fetch` is not implemented because we have the information from
10
- * within the Vite plugin already.
8
+ * When possible, the resolver uses a statically known server
9
+ * {@link address}. When the address is not yet available (e.g. the
10
+ * virtual module was evaluated before the HTTP server started
11
+ * listening — see #17722), the resolver falls back to deriving the
12
+ * origin from the caller-supplied {@link requestUrl}.
11
13
  */
12
14
  export declare class RemoteRuntimeFontFileUrlResolver implements RuntimeFontFileUrlResolver {
13
15
  #private;
@@ -15,5 +17,5 @@ export declare class RemoteRuntimeFontFileUrlResolver implements RuntimeFontFile
15
17
  urls: Set<string>;
16
18
  address: AddressInfo | null;
17
19
  });
18
- resolve(url: string): string | null;
20
+ resolve(url: string, requestUrl: URL | undefined): string | null;
19
21
  }
@@ -8,18 +8,25 @@ class RemoteRuntimeFontFileUrlResolver {
8
8
  this.#urls = urls;
9
9
  this.#address = address;
10
10
  }
11
- resolve(url) {
11
+ resolve(url, requestUrl) {
12
12
  if (!this.#urls.has(url)) {
13
13
  return null;
14
14
  }
15
- if (!this.#address) {
16
- throw new Error("Server address unavailable, this should not happen. Open an issue.");
17
- }
18
15
  if (!url.startsWith("/")) {
19
- url = new URL(url).pathname;
16
+ if (this.#address) {
17
+ url = new URL(url).pathname;
18
+ } else {
19
+ return url;
20
+ }
21
+ }
22
+ if (this.#address) {
23
+ const host = this.#address.family === "IPv6" ? `[${this.#address.address}]` : this.#address.address;
24
+ return `http://${host}:${this.#address.port}${url}`;
25
+ }
26
+ if (requestUrl) {
27
+ return `${requestUrl.origin}${url}`;
20
28
  }
21
- const host = this.#address.family === "IPv6" ? `[${this.#address.address}]` : this.#address.address;
22
- return `http://${host}:${this.#address.port}${url}`;
29
+ throw new Error("Server address unavailable, this should not happen. Open an issue.");
23
30
  }
24
31
  }
25
32
  export {
@@ -80,6 +80,9 @@ async function emitImageMetadata(id, fileEmitter) {
80
80
  throw err;
81
81
  }
82
82
  const fileMetadata = await imageMetadata(fileData, id);
83
+ if (path.extname(id).toLowerCase() === ".apng") {
84
+ fileMetadata.format = "apng";
85
+ }
83
86
  const emittedImage = {
84
87
  src: "",
85
88
  ...fileMetadata
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "7.2.4";
3
+ version = "7.2.6";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -75,6 +75,19 @@ function buildBackgroundArgs(command, flags) {
75
75
  if (flags.root) args.push("--root", String(flags.root));
76
76
  if (flags.allowedHosts) args.push("--allowed-hosts", String(flags.allowedHosts));
77
77
  if (flags.json) args.push("--json");
78
+ if (flags.mode) args.push("--mode", String(flags.mode));
79
+ if (flags.site) args.push("--site", String(flags.site));
80
+ if (flags.base) args.push("--base", String(flags.base));
81
+ if (flags.outDir) args.push("--out-dir", String(flags.outDir));
82
+ if (flags.verbose) args.push("--verbose");
83
+ if (flags.silent) args.push("--silent");
84
+ if (flags.open != null) {
85
+ if (typeof flags.open === "string") {
86
+ args.push("--open", flags.open);
87
+ } else if (flags.open) {
88
+ args.push("--open");
89
+ }
90
+ }
78
91
  return args;
79
92
  }
80
93
  async function stopExistingServer(root, command, pid) {
@@ -13,7 +13,8 @@ type ImageFunction = () => z.ZodObject<{
13
13
  zCore.$ZodLiteral<'webp'>,
14
14
  zCore.$ZodLiteral<'gif'>,
15
15
  zCore.$ZodLiteral<'svg'>,
16
- zCore.$ZodLiteral<'avif'>
16
+ zCore.$ZodLiteral<'avif'>,
17
+ zCore.$ZodLiteral<'apng'>
17
18
  ]>;
18
19
  }>;
19
20
  export interface DataEntry {
@@ -196,7 +196,7 @@ ${contentConfig.error.message}`
196
196
  logger.info("Content config changed");
197
197
  shouldClear = true;
198
198
  }
199
- if (previousAstroVersion && previousAstroVersion !== "7.2.4") {
199
+ if (previousAstroVersion && previousAstroVersion !== "7.2.6") {
200
200
  logger.info("Astro version changed");
201
201
  shouldClear = true;
202
202
  }
@@ -204,8 +204,8 @@ ${contentConfig.error.message}`
204
204
  logger.info("Clearing content store");
205
205
  this.#store.clearAll();
206
206
  }
207
- if ("7.2.4") {
208
- this.#store.metaStore().set("astro-version", "7.2.4");
207
+ if ("7.2.6") {
208
+ this.#store.metaStore().set("astro-version", "7.2.6");
209
209
  }
210
210
  if (currentConfigDigest) {
211
211
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -12,7 +12,7 @@ function generateIdDefault({ entry, base, data }, isLegacy) {
12
12
  if (data.slug) {
13
13
  return String(data.slug);
14
14
  }
15
- const entryURL = new URL(encodeURI(entry), base);
15
+ const entryURL = new URL("./" + encodeURI(entry), base);
16
16
  if (isLegacy) {
17
17
  const { id } = getContentEntryIdAndSlug({
18
18
  entry: entryURL,
@@ -69,7 +69,7 @@ function glob(globOptions) {
69
69
  logger.warn(`No entry type found for ${entry}`);
70
70
  return;
71
71
  }
72
- const fileUrl = new URL(encodeURI(entry), base);
72
+ const fileUrl = new URL("./" + encodeURI(entry), base);
73
73
  const contents = await fs.readFile(fileUrl, "utf-8").catch((err) => {
74
74
  logger.error(`Error reading ${entry}: ${err.message}`);
75
75
  return;
@@ -212,7 +212,7 @@ function glob(globOptions) {
212
212
  ["config.js", "config.ts", "config.mjs"].map((file) => new URL(file, contentDir).href)
213
213
  );
214
214
  function isConfigFile(file) {
215
- const fileUrl = new URL(file, baseDir);
215
+ const fileUrl = new URL("./" + encodeURI(file), baseDir);
216
216
  return configFiles.has(fileUrl.href);
217
217
  }
218
218
  await Promise.all(
@@ -42,17 +42,20 @@ class MutableDataStore extends ImmutableDataStore {
42
42
  collection.delete(String(key));
43
43
  this.#saveToDiskDebounced();
44
44
  this.#writeAssetsImportsDebounced();
45
+ this.#writeModulesImportsDebounced();
45
46
  }
46
47
  }
47
48
  clear(collectionName) {
48
49
  this._collections.delete(collectionName);
49
50
  this.#saveToDiskDebounced();
50
51
  this.#writeAssetsImportsDebounced();
52
+ this.#writeModulesImportsDebounced();
51
53
  }
52
54
  clearAll() {
53
55
  this._collections.clear();
54
56
  this.#saveToDiskDebounced();
55
57
  this.#writeAssetsImportsDebounced();
58
+ this.#writeModulesImportsDebounced();
56
59
  }
57
60
  addAssetImport(assetImport, filePath) {
58
61
  const id = imageSrcToImportId(assetImport, filePath);
@@ -93,6 +96,26 @@ class MutableDataStore extends ImmutableDataStore {
93
96
  }
94
97
  }
95
98
  }
99
+ /**
100
+ * Rebuilds #moduleImports from the current entries in _collections.
101
+ * This ensures stale module entries are removed when content files are
102
+ * deleted or renamed, preventing Vite from attempting to resolve
103
+ * non-existent files listed in content-modules.mjs.
104
+ */
105
+ #rebuildModuleImports() {
106
+ this.#moduleImports.clear();
107
+ for (const collection of this._collections.values()) {
108
+ for (const entry of collection.values()) {
109
+ const typedEntry = entry;
110
+ if (typedEntry.deferredRender && typedEntry.filePath) {
111
+ const id = contentModuleToId(typedEntry.filePath);
112
+ if (id) {
113
+ this.#moduleImports.set(typedEntry.filePath, id);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
96
119
  async writeAssetImports(filePath) {
97
120
  this.#assetsFile = filePath;
98
121
  this.#rebuildAssetImports();
@@ -130,12 +153,14 @@ export default new Map([${exports.join(", ")}]);
130
153
  }
131
154
  async writeModuleImports(filePath) {
132
155
  this.#modulesFile = filePath;
156
+ this.#rebuildModuleImports();
133
157
  if (this.#moduleImports.size === 0) {
134
158
  try {
135
159
  await this.#writeFileAtomic(filePath, "export default new Map();");
136
160
  } catch (err) {
137
161
  throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
138
162
  }
163
+ return;
139
164
  }
140
165
  if (!this.#modulesDirty && existsSync(filePath)) {
141
166
  return;
@@ -264,7 +289,17 @@ ${lines.join(",\n")}]);
264
289
  entries: () => this.entries(collectionName),
265
290
  values: () => this.values(collectionName),
266
291
  keys: () => this.keys(collectionName),
267
- set: ({ id: key, data, body, filePath, deferredRender, digest, rendered, assetImports }) => {
292
+ set: ({
293
+ id: key,
294
+ data,
295
+ body,
296
+ filePath,
297
+ deferredRender,
298
+ digest,
299
+ rendered,
300
+ assetImports,
301
+ imageImports: incomingImageImports
302
+ }) => {
268
303
  if (!key) {
269
304
  throw new Error(`ID must be a non-empty string`);
270
305
  }
@@ -277,11 +312,23 @@ ${lines.join(",\n")}]);
277
312
  }
278
313
  const foundAssets = new Set(assetImports);
279
314
  const imageImports = [];
315
+ const seenImageImportPaths = /* @__PURE__ */ new Set();
316
+ const recordImageImport = (imagePath) => {
317
+ const pathKey = JSON.stringify(imagePath);
318
+ if (seenImageImportPaths.has(pathKey)) {
319
+ return;
320
+ }
321
+ seenImageImportPaths.add(pathKey);
322
+ imageImports.push(imagePath);
323
+ };
324
+ for (const existingImagePath of incomingImageImports ?? []) {
325
+ recordImageImport([...existingImagePath]);
326
+ }
280
327
  forEach(data, function(ctx, val) {
281
328
  if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
282
329
  const src = val.replace(IMAGE_IMPORT_PREFIX, "");
283
330
  foundAssets.add(src);
284
- imageImports.push(ctx.path.map((segment) => segment));
331
+ recordImageImport(ctx.path.map((segment) => segment));
285
332
  ctx.update(src);
286
333
  }
287
334
  });
@@ -1,10 +1,11 @@
1
1
  import crypto from "node:crypto";
2
+ import nodeFs from "node:fs";
2
3
  import { FONTS_SERVER_ADDRESS_PLACEHOLDER } from "../../../assets/fonts/constants.js";
3
4
  import { PROPAGATED_ASSET_FLAG } from "../../../content/consts.js";
4
5
  import { hasContentFlag } from "../../../content/utils.js";
5
6
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
6
7
  import { removeQueryString } from "../../path.js";
7
- import { rootRelativePath } from "../../viteUtils.js";
8
+ import { CSS_LANGS_RE, rootRelativePath } from "../../viteUtils.js";
8
9
  import { moduleIsTopLevelPage } from "../graph.js";
9
10
  import { isContentDataIncrementalModule } from "../incremental-metadata.js";
10
11
  import { getPageDataByViteID } from "../internal.js";
@@ -38,8 +39,13 @@ function hashModules(graph, sortedIds) {
38
39
  hasher.update(id);
39
40
  hasher.update("\n");
40
41
  const code = graph.getModuleInfo(id)?.code;
41
- if (code != null) {
42
+ if (code != null && code.length > 0) {
42
43
  hasher.update(resolveAssetPlaceholders(graph, code));
44
+ } else if (CSS_LANGS_RE.test(id)) {
45
+ try {
46
+ hasher.update(nodeFs.readFileSync(removeQueryString(id), "utf-8"));
47
+ } catch {
48
+ }
43
49
  }
44
50
  hasher.update("\n");
45
51
  }
@@ -19,6 +19,12 @@ const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
19
19
  function createViteBuildConfig(opts) {
20
20
  const { settings, viteConfig, routes, plugins, builder, isRolldownInput } = opts;
21
21
  const legacyAdapter = !settings.adapter || isLegacyAdapter(settings.adapter);
22
+ const {
23
+ [ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: userSsr,
24
+ [ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: userPrerender,
25
+ [ASTRO_VITE_ENVIRONMENT_NAMES.client]: userClient,
26
+ ...userEnvironments
27
+ } = viteConfig.environments ?? {};
22
28
  return {
23
29
  ...viteConfig,
24
30
  logLevel: viteConfig.logLevel ?? "error",
@@ -100,8 +106,9 @@ function createViteBuildConfig(opts) {
100
106
  envPrefix: viteConfig.envPrefix ?? "PUBLIC_",
101
107
  base: settings.config.base,
102
108
  environments: {
103
- ...viteConfig.environments ?? {},
109
+ ...userEnvironments,
104
110
  [ASTRO_VITE_ENVIRONMENT_NAMES.prerender]: {
111
+ ...userPrerender,
105
112
  build: {
106
113
  emitAssets: true,
107
114
  outDir: fileURLToPath(getPrerenderOutputDirectory(settings)),
@@ -112,20 +119,21 @@ function createViteBuildConfig(opts) {
112
119
  output: {
113
120
  entryFileNames: `${PRERENDER_ENTRY_FILENAME_PREFIX}.[hash].mjs`,
114
121
  format: "esm",
115
- ...viteConfig.environments?.prerender?.build?.rolldownOptions?.output
122
+ ...userPrerender?.build?.rolldownOptions?.output
116
123
  }
117
124
  },
118
125
  ssr: true
119
126
  }
120
127
  },
121
128
  [ASTRO_VITE_ENVIRONMENT_NAMES.client]: {
129
+ ...userClient,
122
130
  build: {
123
131
  emitAssets: true,
124
132
  target: "esnext",
125
133
  outDir: fileURLToPath(getClientOutputDirectory(settings)),
126
134
  copyPublicDir: true,
127
- sourcemap: viteConfig.environments?.client?.build?.sourcemap ?? viteConfig.build?.sourcemap ?? false,
128
- minify: viteConfig.environments?.client?.build?.minify ?? viteConfig.build?.minify ?? true,
135
+ sourcemap: userClient?.build?.sourcemap ?? viteConfig.build?.sourcemap ?? false,
136
+ minify: userClient?.build?.minify ?? viteConfig.build?.minify ?? true,
129
137
  rolldownOptions: {
130
138
  preserveEntrySignatures: "exports-only",
131
139
  output: {
@@ -148,17 +156,18 @@ function createViteBuildConfig(opts) {
148
156
  }
149
157
  return `${settings.config.build.assets}/[name].[hash][extname]`;
150
158
  },
151
- ...viteConfig.environments?.client?.build?.rolldownOptions?.output
159
+ ...userClient?.build?.rolldownOptions?.output
152
160
  }
153
161
  }
154
162
  }
155
163
  },
156
164
  [ASTRO_VITE_ENVIRONMENT_NAMES.ssr]: {
165
+ ...userSsr,
157
166
  build: {
158
167
  outDir: fileURLToPath(getServerOutputDirectory(settings)),
159
168
  rolldownOptions: {
160
169
  output: {
161
- ...viteConfig.environments?.ssr?.build?.rolldownOptions?.output
170
+ ...userSsr?.build?.rolldownOptions?.output
162
171
  }
163
172
  }
164
173
  }
@@ -1,4 +1,5 @@
1
1
  import picomatch from "picomatch";
2
+ import { getCookiesFromResponse } from "../cookies/response.js";
2
3
  import { AstroError } from "../errors/errors.js";
3
4
  import { CacheQueryConfigConflict } from "../errors/errors-data.js";
4
5
  function parseCdnCacheControl(header) {
@@ -110,8 +111,12 @@ function matchesVary(request, entry) {
110
111
  }
111
112
  return true;
112
113
  }
114
+ function hasAtLeastOneCookie(cookies) {
115
+ return cookies ? !cookies.headers().next().done : false;
116
+ }
113
117
  function hasSetCookieHeader(response) {
114
- return response.headers.has("set-cookie");
118
+ if (response.headers.has("set-cookie")) return true;
119
+ return hasAtLeastOneCookie(getCookiesFromResponse(response));
115
120
  }
116
121
  function warnSkippedSetCookie(url) {
117
122
  console.warn(
@@ -31,7 +31,7 @@ async function compile({
31
31
  normalizedFilename: normalizeFilename(filename, astroConfig.root),
32
32
  sourcemap: "both",
33
33
  internalURL: "astro/compiler-runtime",
34
- // TODO: remove in Astro v7
34
+ // TODO: remove in Astro v8
35
35
  astroGlobalArgs: JSON.stringify(astroConfig.site),
36
36
  scopedStyleStrategy: astroConfig.scopedStyleStrategy,
37
37
  resultScopedSlot: true,
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "7.2.4";
1
+ const ASTRO_VERSION = "7.2.6";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const ASTRO_ERROR_HEADER = "X-Astro-Error";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404.astro";
@@ -223,7 +223,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
223
223
  find: "astro:middleware",
224
224
  replacement: "astro/virtual-modules/middleware.js"
225
225
  },
226
- // TODO: remove in Astro 7
226
+ // TODO: remove in Astro 8
227
227
  {
228
228
  find: "astro:schema",
229
229
  replacement: "astro/zod"
@@ -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 = "7.2.4";
29
+ const currentVersion = "7.2.6";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -48,6 +48,9 @@ function isAstroCommand(command) {
48
48
  return ASTRO_COMMAND_PATTERN.test(command);
49
49
  }
50
50
  async function isLockFileProcessAlive(data, find = findProcess) {
51
+ if (data.pid === process.pid) {
52
+ return false;
53
+ }
51
54
  if (!isProcessAlive(data.pid)) {
52
55
  return false;
53
56
  }
@@ -12,8 +12,8 @@ import { normalizeLF } from "../utils.js";
12
12
  function collectErrorMetadata(e, rootFolder) {
13
13
  const err = AggregateError.is(e) || Array.isArray(e.errors) ? e.errors : [e];
14
14
  err.forEach((error) => {
15
- if (e.stack) {
16
- const stackInfo = collectInfoFromStacktrace(e);
15
+ if (error.stack) {
16
+ const stackInfo = collectInfoFromStacktrace(error);
17
17
  try {
18
18
  error.stack = stripVTControlCharacters(stackInfo.stack);
19
19
  } catch {
@@ -40,7 +40,7 @@ function collectErrorMetadata(e, rootFolder) {
40
40
  } catch {
41
41
  }
42
42
  }
43
- error.hint = generateHint(e);
43
+ error.hint = generateHint(error);
44
44
  if (error.message) {
45
45
  try {
46
46
  error.message = stripVTControlCharacters(error.message);
@@ -27,6 +27,7 @@ import { getParams, getProps } from "../render/index.js";
27
27
  import { executeRewrite } from "../rewrites/handler.js";
28
28
  import { isRoute404or500, isRouteServerIsland } from "../routing/match.js";
29
29
  import { MultiLevelEncodingError, validateAndDecodePathname } from "../util/pathname.js";
30
+ import { setPathname } from "../util/normalized-url.js";
30
31
  import { getOriginPathname, setOriginPathname } from "../routing/rewrite.js";
31
32
  import { computePathnameFromDomain } from "../i18n/domain.js";
32
33
  import { getCustom404Route, routeHasHtmlExtension } from "../routing/helpers.js";
@@ -210,8 +211,8 @@ class FetchState {
210
211
  const url = new URL(request.url);
211
212
  const publicPathname = this.#normalizePathname(url.pathname);
212
213
  const pathname = this.#computePathname(publicPathname);
213
- url.pathname = publicPathname;
214
- url.pathname = collapseDuplicateSlashes(url.pathname);
214
+ setPathname(url, publicPathname);
215
+ setPathname(url, collapseDuplicateSlashes(url.pathname));
215
216
  const domainPathname = computePathnameFromDomain(
216
217
  request,
217
218
  url,
@@ -102,7 +102,11 @@ async function finalizeI18n(compiled, state, response) {
102
102
  headers: { Location: fallbackDecision.pathname + url.search }
103
103
  });
104
104
  case "rewrite":
105
- return await state.rewrite(fallbackDecision.pathname + url.search);
105
+ try {
106
+ return await state.rewrite(fallbackDecision.pathname + url.search);
107
+ } catch {
108
+ break;
109
+ }
106
110
  case "none":
107
111
  break;
108
112
  }
@@ -270,7 +270,7 @@ function printHelp({
270
270
  message.push(
271
271
  linebreak(),
272
272
  ` ${bgGreen(black(` ${commandName} `))} ${green(
273
- `v${"7.2.4"}`
273
+ `v${"7.2.6"}`
274
274
  )} ${headline}`
275
275
  );
276
276
  }
@@ -5,6 +5,7 @@ import { createStorage } from "unstorage";
5
5
  const PERSIST_SYMBOL = /* @__PURE__ */ Symbol();
6
6
  const DEFAULT_COOKIE_NAME = "astro-session";
7
7
  const VALID_COOKIE_REGEX = /^[\w-]+$/;
8
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8
9
  const unflatten = (parsed, _) => {
9
10
  return rawUnflatten(parsed, {
10
11
  URL: (href) => new URL(href)
@@ -170,7 +171,8 @@ class AstroSession {
170
171
  * Destroys the session, clearing the cookie and storage if it exists.
171
172
  */
172
173
  destroy() {
173
- const sessionId = this.#sessionID ?? this.#cookies.get(this.#cookieName)?.value;
174
+ const cookieValue = this.#cookies.get(this.#cookieName)?.value;
175
+ const sessionId = this.#sessionID ?? (cookieValue && UUID_REGEX.test(cookieValue) ? cookieValue : void 0);
174
176
  if (sessionId) {
175
177
  this.#toDestroy.add(sessionId);
176
178
  }
@@ -340,7 +342,7 @@ class AstroSession {
340
342
  #ensureSessionID() {
341
343
  if (!this.#sessionID) {
342
344
  const cookieValue = this.#cookies.get(this.#cookieName)?.value;
343
- if (cookieValue) {
345
+ if (cookieValue && UUID_REGEX.test(cookieValue)) {
344
346
  this.#sessionID = cookieValue;
345
347
  this.#sessionIDFromCookie = true;
346
348
  } else {
@@ -34,30 +34,30 @@ export interface BaseSessionConfig {
34
34
  interface DriverConfig<TDriver extends SessionDriverConfig> extends BaseSessionConfig {
35
35
  /** Config object for a session driver */
36
36
  driver: TDriver;
37
- /** @deprecated Pass options to the driver function directly. This will be removed in Astro 7 */
37
+ /** @deprecated Pass options to the driver function directly. This will be removed in Astro 8 */
38
38
  options?: never;
39
39
  }
40
40
  interface UnstorageConfig<TDriver extends keyof BuiltinDriverOptions | undefined, TOptions = TDriver extends keyof BuiltinDriverOptions ? NoInfer<BuiltinDriverOptions[TDriver]> : undefined> extends BaseSessionConfig {
41
41
  /**
42
42
  * Entrypoint for an unstorage session driver
43
- * @deprecated Use `import { sessionDrivers } from 'astro/config'` instead. This will be removed in Astro 7
43
+ * @deprecated Use `import { sessionDrivers } from 'astro/config'` instead. This will be removed in Astro 8
44
44
  */
45
45
  driver?: TDriver;
46
46
  /**
47
47
  * Options for the unstorage driver
48
- * @deprecated Use `import { sessionDrivers } from 'astro/config'` instead. This will be removed in Astro 7
48
+ * @deprecated Use `import { sessionDrivers } from 'astro/config'` instead. This will be removed in Astro 8
49
49
  */
50
50
  options?: TOptions;
51
51
  }
52
52
  interface CustomConfig extends BaseSessionConfig {
53
53
  /**
54
54
  * Entrypoint for a custom session driver
55
- * @deprecated Use the object shape (type `SessionDriverConfig`). This will be removed in Astro 7
55
+ * @deprecated Use the object shape (type `SessionDriverConfig`). This will be removed in Astro 8
56
56
  */
57
57
  driver?: string;
58
58
  /**
59
59
  * Options for the custom session driver
60
- * @deprecated Use the object shape (type `SessionDriverConfig`). This will be removed in Astro 7
60
+ * @deprecated Use the object shape (type `SessionDriverConfig`). This will be removed in Astro 8
61
61
  */
62
62
  options?: Record<string, unknown>;
63
63
  }
@@ -3,8 +3,16 @@
3
3
  * Decodes and validates the pathname, collapses duplicate slashes.
4
4
  */
5
5
  export declare function createNormalizedUrl(requestUrl: string): URL;
6
+ /**
7
+ * Assigns `url.pathname` only when the value differs.
8
+ * The setter re-parses the whole URL, so a no-op write is still expensive.
9
+ */
10
+ export declare function setPathname(url: URL, pathname: string): void;
6
11
  /**
7
12
  * Normalizes an already-parsed URL in place: decodes and validates the
8
13
  * pathname, collapses duplicate slashes. Returns the same URL object.
14
+ *
15
+ * Collapse runs after the decode is written back: the pathname setter
16
+ * rewrites `\` to `/`, so a decoded backslash only becomes `//` once assigned.
9
17
  */
10
18
  export declare function normalizeUrl(url: URL): URL;
@@ -3,19 +3,25 @@ import { validateAndDecodePathname } from "./pathname.js";
3
3
  function createNormalizedUrl(requestUrl) {
4
4
  return normalizeUrl(new URL(requestUrl));
5
5
  }
6
+ function setPathname(url, pathname) {
7
+ if (url.pathname !== pathname) {
8
+ url.pathname = pathname;
9
+ }
10
+ }
6
11
  function normalizeUrl(url) {
7
12
  try {
8
- url.pathname = validateAndDecodePathname(url.pathname);
13
+ setPathname(url, validateAndDecodePathname(url.pathname));
9
14
  } catch {
10
15
  try {
11
- url.pathname = decodeURI(url.pathname);
16
+ setPathname(url, decodeURI(url.pathname));
12
17
  } catch {
13
18
  }
14
19
  }
15
- url.pathname = collapseDuplicateSlashes(url.pathname);
20
+ setPathname(url, collapseDuplicateSlashes(url.pathname));
16
21
  return url;
17
22
  }
18
23
  export {
19
24
  createNormalizedUrl,
20
- normalizeUrl
25
+ normalizeUrl,
26
+ setPathname
21
27
  };
@@ -43,10 +43,9 @@ function sortAndFilterLocales(browserLocaleList, locales) {
43
43
  }
44
44
  return true;
45
45
  }).sort((a, b) => {
46
- if (a.qualityValue && b.qualityValue) {
47
- return Math.sign(b.qualityValue - a.qualityValue);
48
- }
49
- return 0;
46
+ const qa = a.locale === "*" ? a.qualityValue ?? 0 : a.qualityValue ?? 1;
47
+ const qb = b.locale === "*" ? b.qualityValue ?? 0 : b.qualityValue ?? 1;
48
+ return qb - qa;
50
49
  });
51
50
  }
52
51
  function computePreferredLocale(request, locales) {
@@ -56,6 +56,19 @@ function serializedManifestPlugin({
56
56
  return {
57
57
  name: SERIALIZED_MANIFEST_ID,
58
58
  enforce: "pre",
59
+ // Dependency optimization runs as a nested Rolldown build that cannot load Vite virtual
60
+ // modules. Keep this import external so the server module graph resolves it below.
61
+ configEnvironment(environmentName) {
62
+ if (command === "dev" && (environmentName === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environmentName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environmentName === ASTRO_VITE_ENVIRONMENT_NAMES.prerender)) {
63
+ return {
64
+ optimizeDeps: {
65
+ rolldownOptions: {
66
+ external: [AMBIENT_MANIFEST_SPECIFIER]
67
+ }
68
+ }
69
+ };
70
+ }
71
+ },
59
72
  configureServer(server) {
60
73
  server.watcher.on("add", (path) => reloadManifest(path, server));
61
74
  server.watcher.on("unlink", (path) => reloadManifest(path, server));
@@ -8,14 +8,14 @@ function createError(name) {
8
8
  }
9
9
  function createAstro(site) {
10
10
  return {
11
- // TODO: throw in Astro 7
11
+ // TODO: throw in Astro 8
12
12
  get site() {
13
13
  console.warn(
14
14
  `Astro.site inside getStaticPaths is deprecated and will be removed in a future major version of Astro. Use import.meta.env.SITE instead`
15
15
  );
16
16
  return site ? new URL(site) : void 0;
17
17
  },
18
- // TODO: throw in Astro 7
18
+ // TODO: throw in Astro 8
19
19
  get generator() {
20
20
  console.warn(
21
21
  `Astro.generator inside getStaticPaths is deprecated and will be removed in a future major version of Astro.`
@@ -28,12 +28,14 @@ function renderCspContent(result) {
28
28
  const scriptElemActive = isEnabled(script.element);
29
29
  const styleElemActive = isEnabled(style.element);
30
30
  const strictDynamicSuffix = scriptDirective.strictDynamic ? ` 'strict-dynamic'` : "";
31
+ const scriptDefaultHasUnsafeInline = hasUnsafeInline(script.default.resources);
32
+ const styleDefaultHasUnsafeInline = hasUnsafeInline(style.default.resources);
31
33
  const scriptBaselineTokens = [
32
- ...scriptElemActive ? [] : [...finalScriptHashes],
34
+ ...scriptElemActive || scriptDefaultHasUnsafeInline ? [] : [...finalScriptHashes],
33
35
  ...scriptDirective.strictDynamic ? [`'strict-dynamic'`] : []
34
36
  ];
35
37
  const scriptSrc = `script-src ${scriptResources} ${scriptBaselineTokens.join(" ")};`;
36
- const styleSrc = `style-src ${styleResources} ${(styleElemActive ? [] : [...finalStyleHashes]).join(" ")};`;
38
+ const styleSrc = `style-src ${styleResources} ${(styleElemActive || styleDefaultHasUnsafeInline ? [] : [...finalStyleHashes]).join(" ")};`;
37
39
  const scriptSrcElem = scriptElemActive ? renderSpecificDirective(
38
40
  "script-src-elem",
39
41
  script.element.resources,
@@ -73,13 +75,19 @@ function renderCspContent(result) {
73
75
  styleSrcAttr
74
76
  ].filter(Boolean).join(" ");
75
77
  }
78
+ function hasUnsafeInline(resources) {
79
+ return resources.includes("'unsafe-inline'");
80
+ }
76
81
  function isEnabled(sources) {
77
82
  return sources.resources.length > 0 || sources.hashes.length > 0;
78
83
  }
79
84
  function renderSpecificDirective(name, resources, defaultResource, sharedHashes, ownHashes, suffix = "") {
80
- const hashes = new Set(sharedHashes);
81
- for (const hash of ownHashes) {
82
- hashes.add(`'${hash}'`);
85
+ const unsafeInline = hasUnsafeInline(resources);
86
+ const hashes = new Set(unsafeInline ? void 0 : sharedHashes);
87
+ if (!unsafeInline) {
88
+ for (const hash of ownHashes) {
89
+ hashes.add(`'${hash}'`);
90
+ }
83
91
  }
84
92
  let finalResources;
85
93
  if (resources.length > 0) {
@@ -93,6 +93,7 @@ function swapHeadElements(doc) {
93
93
  function swapBodyElement(newElement, oldElement) {
94
94
  const persistPairs = [];
95
95
  const docEl = oldElement.ownerDocument.documentElement;
96
+ const liveMedia = new Set(oldElement.querySelectorAll("video, audio"));
96
97
  const moveBefore = typeof docEl.moveBefore === "function" ? (parent, node, child) => parent.moveBefore(node, child) : null;
97
98
  for (const el of oldElement.querySelectorAll(`[${PERSIST_ATTR}]`)) {
98
99
  const id = el.getAttribute(PERSIST_ATTR);
@@ -119,10 +120,11 @@ function swapBodyElement(newElement, oldElement) {
119
120
  }
120
121
  }
121
122
  attachShadowRoots(newElement);
122
- reifyMediaElements(newElement);
123
+ reifyMediaElements(newElement, liveMedia);
123
124
  }
124
- function reifyMediaElements(root) {
125
+ function reifyMediaElements(root, liveMedia) {
125
126
  for (const media of root.querySelectorAll("video, audio")) {
127
+ if (liveMedia.has(media)) continue;
126
128
  const fresh = document.createElement(media.localName);
127
129
  for (const attr of media.attributes) {
128
130
  fresh.setAttribute(attr.name, attr.value);
@@ -749,7 +749,7 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
749
749
  * - External scripts and external styles are not supported out of the box, but you can [provide your own hashes](https://docs.astro.build/en/reference/configuration-reference/#securitycspscriptdirectivehashes).
750
750
  * - [Astro's view transitions](https://docs.astro.build/en/guides/view-transitions/) using the `<ClientRouter />` are not supported, but you can [consider migrating to the browser native View Transition API](https://events-3bg.pages.dev/jotter/astro-view-transitions/) instead if you are not using Astro's enhancements to the native View Transitions and Navigation APIs.
751
751
  * - Shiki isn't currently supported. By design, Shiki functions use inline styles that cannot work with Astro CSP implementation. Consider [using `<Prism />`](https://docs.astro.build/en/guides/syntax-highlighting/#prism-) when your project requires both CSP and syntax highlighting.
752
- * - `unsafe-inline` directives are incompatible with Astro's CSP implementation. By default, Astro will emit hashes for all its bundled scripts (e.g. client islands) and all modern browsers will automatically reject `unsafe-inline` when it occurs in a directive with a hash or nonce.
752
+ * - When `'unsafe-inline'` is included as a resource in a directive, Astro will not emit hashes on that directive. Per the CSP spec, browsers ignore `'unsafe-inline'` when a hash or nonce is present in the same directive, so Astro suppresses hashes to preserve `'unsafe-inline'` behavior. Note that this reduces the security of that directive to the level of `'unsafe-inline'`.
753
753
  *
754
754
  * :::note
755
755
  * Due to the nature of the Vite dev server, this feature isn't supported while working in `dev` mode. Instead, you can test this in your Astro project using `build` and `preview`.
@@ -137,7 +137,7 @@ async function astroPluginRoutes({
137
137
  return true;
138
138
  });
139
139
  const code = `
140
- import { deserializeRouteInfo } from 'astro/app';
140
+ import { deserializeRouteInfo } from 'astro/app/manifest';
141
141
  const serializedData = ${JSON.stringify(filteredRoutes)};
142
142
  const routes = serializedData.map(deserializeRouteInfo);
143
143
  export { routes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "7.2.4",
3
+ "version": "7.2.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",
@@ -103,7 +103,7 @@
103
103
  "README.md"
104
104
  ],
105
105
  "dependencies": {
106
- "@astrojs/compiler-rs": "^0.3.2",
106
+ "@astrojs/compiler-rs": "^0.4.0",
107
107
  "@capsizecss/unpack": "^4.0.0",
108
108
  "@clack/prompts": "^1.1.0",
109
109
  "@oslojs/encoding": "^1.1.0",
@@ -115,7 +115,7 @@
115
115
  "common-ancestor-path": "^2.0.0",
116
116
  "cookie": "^2.0.1",
117
117
  "devalue": "^5.8.1",
118
- "diff": "^8.0.3",
118
+ "diff": "^9.0.0",
119
119
  "dset": "^3.1.4",
120
120
  "es-module-lexer": "^2.0.0",
121
121
  "esbuild": "^0.28.0",
@@ -154,7 +154,7 @@
154
154
  "yargs-parser": "^22.0.0",
155
155
  "zod": "^4.3.6",
156
156
  "@astrojs/internal-helpers": "0.10.4",
157
- "@astrojs/markdown-satteri": "0.3.7",
157
+ "@astrojs/markdown-satteri": "0.3.8",
158
158
  "@astrojs/telemetry": "3.3.3"
159
159
  },
160
160
  "optionalDependencies": {
@@ -193,8 +193,8 @@
193
193
  "typescript": "^6.0.3",
194
194
  "undici": "^7.22.0",
195
195
  "vitest": "^4.1.0",
196
- "@astrojs/check": "0.9.10",
197
196
  "@astrojs/markdown-remark": "7.2.4",
197
+ "@astrojs/check": "0.9.10",
198
198
  "astro-scripts": "0.0.14"
199
199
  },
200
200
  "engines": {
@@ -13,7 +13,7 @@ export {
13
13
  defineLiveCollection,
14
14
  renderEntry as render,
15
15
  } from 'astro/content/runtime';
16
- // TODO: remove in Astro 7
16
+ // TODO: remove in Astro 8
17
17
  export { z } from 'astro/zod';
18
18
 
19
19
  /* @@LIVE_CONTENT_CONFIG@@ */
@@ -38,8 +38,8 @@ export const getLiveEntry = createGetLiveEntry({
38
38
  liveCollections,
39
39
  });
40
40
 
41
- // TODO: remove in Astro 7
41
+ // TODO: remove in Astro 8
42
42
  export const getEntryBySlug = createDeprecatedFunction('getEntryBySlug');
43
43
 
44
- // TODO: remove in Astro 7
44
+ // TODO: remove in Astro 8
45
45
  export const getDataEntryById = createDeprecatedFunction('getDataEntryById');
@@ -10,11 +10,11 @@ declare module 'astro:content' {
10
10
  } from 'astro/content/config';
11
11
  export { defineLiveCollection, defineCollection } from 'astro/content/config';
12
12
 
13
- // TODO: remove in Astro 7
13
+ // TODO: remove in Astro 8
14
14
  /**
15
15
  * @deprecated
16
16
  * `import { z } from 'astro:content'` is deprecated and will be removed
17
- * in Astro 7. Use `import { z } from 'astro/zod'` instead.
17
+ * in Astro 8. Use `import { z } from 'astro/zod'` instead.
18
18
  */
19
19
  export const z = zod.z;
20
20