astro 5.1.7 → 5.1.8

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.
@@ -27,7 +27,7 @@ function astroIntegrationActionsRouteHandler({
27
27
  throw error;
28
28
  }
29
29
  const stringifiedActionsImport = JSON.stringify(
30
- viteID(new URL("./actions/index.ts", params.config.srcDir))
30
+ viteID(new URL("./actions", params.config.srcDir))
31
31
  );
32
32
  settings.injectedTypes.push({
33
33
  filename: ACTIONS_TYPES_FILE,
@@ -15,6 +15,7 @@ import {
15
15
  globalContentConfigObserver,
16
16
  safeStringify
17
17
  } from "./utils.js";
18
+ import { createWatcherWrapper } from "./watcher.js";
18
19
  class ContentLayer {
19
20
  #logger;
20
21
  #store;
@@ -29,7 +30,9 @@ class ContentLayer {
29
30
  this.#logger = logger;
30
31
  this.#store = store;
31
32
  this.#settings = settings;
32
- this.#watcher = watcher;
33
+ if (watcher) {
34
+ this.#watcher = createWatcherWrapper(watcher);
35
+ }
33
36
  this.#queue = new PQueue({ concurrency: 1 });
34
37
  }
35
38
  /**
@@ -55,6 +58,7 @@ class ContentLayer {
55
58
  dispose() {
56
59
  this.#queue.clear();
57
60
  this.#unsubscribe?.();
61
+ this.#watcher?.removeAllTrackedListeners();
58
62
  }
59
63
  async #getGenerateDigest() {
60
64
  if (this.#generateDigest) {
@@ -148,7 +152,7 @@ ${contentConfig.error.message}`);
148
152
  logger.info("Content config changed");
149
153
  shouldClear = true;
150
154
  }
151
- if (previousAstroVersion && previousAstroVersion !== "5.1.7") {
155
+ if (previousAstroVersion && previousAstroVersion !== "5.1.8") {
152
156
  logger.info("Astro version changed");
153
157
  shouldClear = true;
154
158
  }
@@ -156,8 +160,8 @@ ${contentConfig.error.message}`);
156
160
  logger.info("Clearing content store");
157
161
  this.#store.clearAll();
158
162
  }
159
- if ("5.1.7") {
160
- await this.#store.metaStore().set("astro-version", "5.1.7");
163
+ if ("5.1.8") {
164
+ await this.#store.metaStore().set("astro-version", "5.1.8");
161
165
  }
162
166
  if (currentConfigDigest) {
163
167
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -165,6 +169,9 @@ ${contentConfig.error.message}`);
165
169
  if (astroConfigDigest) {
166
170
  await this.#store.metaStore().set("astro-config-digest", astroConfigDigest);
167
171
  }
172
+ if (!options?.loaders?.length) {
173
+ this.#watcher?.removeAllTrackedListeners();
174
+ }
168
175
  await Promise.all(
169
176
  Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
170
177
  if (collection.type !== CONTENT_LAYER_TYPE) {
@@ -71,6 +71,7 @@ function file(fileName, options) {
71
71
  }
72
72
  const filePath = fileURLToPath(url);
73
73
  await syncData(filePath, context);
74
+ watcher?.add(filePath);
74
75
  watcher?.on("change", async (changedPath) => {
75
76
  if (changedPath === filePath) {
76
77
  logger.info(`Reloading data from ${fileName}`);
@@ -219,6 +219,7 @@ function glob(globOptions) {
219
219
  if (!watcher) {
220
220
  return;
221
221
  }
222
+ watcher.add(filePath);
222
223
  const matchesGlob = (entry) => !entry.startsWith("../") && micromatch.isMatch(entry, globOptions.pattern);
223
224
  const basePath = fileURLToPath(baseDir);
224
225
  async function onChange(changedPath) {
@@ -468,7 +468,6 @@ This is an Astro bug, so please file an issue at https://github.com/withastro/as
468
468
  return;
469
469
  }
470
470
  const flattenedErrorPath = ctx.path.join(".");
471
- const collectionIsInStore = store.hasCollection(collection);
472
471
  if (typeof lookup === "object") {
473
472
  if (lookup.collection !== collection) {
474
473
  ctx.addIssue({
@@ -479,18 +478,7 @@ This is an Astro bug, so please file an issue at https://github.com/withastro/as
479
478
  }
480
479
  return lookup;
481
480
  }
482
- if (collectionIsInStore) {
483
- const entry2 = store.get(collection, lookup);
484
- if (!entry2) {
485
- ctx.addIssue({
486
- code: ZodIssueCode.custom,
487
- message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Entry ${lookup} does not exist.`
488
- });
489
- return;
490
- }
491
- return { id: lookup, collection };
492
- }
493
- if (!lookupMap[collection] && store.collections().size <= 1) {
481
+ if (!lookupMap[collection]) {
494
482
  return { id: lookup, collection };
495
483
  }
496
484
  const { type, entries } = lookupMap[collection];
@@ -13,14 +13,7 @@ async function attachContentServerListeners({
13
13
  }) {
14
14
  const contentPaths = getContentPaths(settings.config, fs);
15
15
  if (!settings.config.legacy?.collections) {
16
- const contentGenerator = await createContentTypesGenerator({
17
- fs,
18
- settings,
19
- logger,
20
- viteServer,
21
- contentConfigObserver: globalContentConfigObserver
22
- });
23
- await contentGenerator.init();
16
+ await attachListeners();
24
17
  } else if (fs.existsSync(contentPaths.contentDir)) {
25
18
  logger.debug(
26
19
  "content",
@@ -58,10 +51,9 @@ async function attachContentServerListeners({
58
51
  "addDir",
59
52
  (entry) => contentGenerator.queueEvent({ name: "addDir", entry })
60
53
  );
61
- viteServer.watcher.on(
62
- "change",
63
- (entry) => contentGenerator.queueEvent({ name: "change", entry })
64
- );
54
+ viteServer.watcher.on("change", (entry) => {
55
+ contentGenerator.queueEvent({ name: "change", entry });
56
+ });
65
57
  viteServer.watcher.on("unlink", (entry) => {
66
58
  contentGenerator.queueEvent({ name: "unlink", entry });
67
59
  });
@@ -226,7 +226,13 @@ async function createContentTypesGenerator({
226
226
  entry: pathToFileURL(rawEvent.entry),
227
227
  name: rawEvent.name
228
228
  };
229
- if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) return;
229
+ if (settings.config.legacy.collections) {
230
+ if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) {
231
+ return;
232
+ }
233
+ } else if (contentPaths.config.url.pathname !== event.entry.pathname) {
234
+ return;
235
+ }
230
236
  events.push(event);
231
237
  debounceTimeout && clearTimeout(debounceTimeout);
232
238
  const runEventsSafe = async () => {
@@ -0,0 +1,5 @@
1
+ import type { FSWatcher } from 'vite';
2
+ export type WrappedWatcher = FSWatcher & {
3
+ removeAllTrackedListeners(): void;
4
+ };
5
+ export declare function createWatcherWrapper(watcher: FSWatcher): WrappedWatcher;
@@ -0,0 +1,38 @@
1
+ function createWatcherWrapper(watcher) {
2
+ const listeners = /* @__PURE__ */ new Map();
3
+ const handler = {
4
+ get(target, prop, receiver) {
5
+ if (prop === "on") {
6
+ return function(event, callback) {
7
+ if (!listeners.has(event)) {
8
+ listeners.set(event, /* @__PURE__ */ new Set());
9
+ }
10
+ listeners.get(event).add(callback);
11
+ return Reflect.get(target, prop, receiver).call(target, event, callback);
12
+ };
13
+ }
14
+ if (prop === "off") {
15
+ return function(event, callback) {
16
+ listeners.get(event)?.delete(callback);
17
+ return Reflect.get(target, prop, receiver).call(target, event, callback);
18
+ };
19
+ }
20
+ if (prop === "removeAllTrackedListeners") {
21
+ return function() {
22
+ for (const [event, callbacks] of listeners.entries()) {
23
+ for (const callback of callbacks) {
24
+ target.off(event, callback);
25
+ }
26
+ callbacks.clear();
27
+ }
28
+ listeners.clear();
29
+ };
30
+ }
31
+ return Reflect.get(target, prop, receiver);
32
+ }
33
+ };
34
+ return new Proxy(watcher, handler);
35
+ }
36
+ export {
37
+ createWatcherWrapper
38
+ };
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.1.7";
1
+ const ASTRO_VERSION = "5.1.8";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
4
4
  const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
@@ -57,19 +57,20 @@ async function createContainer({
57
57
  ssrManifest: devSSRManifest
58
58
  }
59
59
  );
60
+ const viteServer = await vite.createServer(viteConfig);
60
61
  await syncInternal({
61
62
  settings,
62
63
  mode,
63
64
  logger,
64
65
  skip: {
65
- content: true,
66
+ content: !isRestart,
66
67
  cleanup: true
67
68
  },
68
69
  force: inlineConfig?.force,
69
70
  manifest,
70
- command: "dev"
71
+ command: "dev",
72
+ watcher: viteServer.watcher
71
73
  });
72
- const viteServer = await vite.createServer(viteConfig);
73
74
  const container = {
74
75
  inlineConfig: inlineConfig ?? {},
75
76
  fs,
@@ -22,7 +22,7 @@ async function dev(inlineConfig) {
22
22
  await telemetry.record([]);
23
23
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
24
24
  const logger = restart.container.logger;
25
- const currentVersion = "5.1.7";
25
+ const currentVersion = "5.1.8";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -1,6 +1,7 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import * as vite from "vite";
3
3
  import { globalContentLayer } from "../../content/content-layer.js";
4
+ import { attachContentServerListeners } from "../../content/server-listeners.js";
4
5
  import { eventCliSession, telemetry } from "../../events/index.js";
5
6
  import { SETTINGS_FILE } from "../../preferences/constants.js";
6
7
  import { createNodeLogger, createSettings, resolveConfig } from "../config/index.js";
@@ -101,6 +102,7 @@ async function createContainerWithAutomaticRestart({
101
102
  } else {
102
103
  restart.container = result;
103
104
  setupContainer();
105
+ await attachContentServerListeners(restart.container);
104
106
  if (server) {
105
107
  server.resolvedUrls = result.viteServer.resolvedUrls;
106
108
  }
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.1.7";
41
+ const version = "5.1.8";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -276,7 +276,7 @@ function printHelp({
276
276
  message.push(
277
277
  linebreak(),
278
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
279
- `v${"5.1.7"}`
279
+ `v${"5.1.8"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -1,5 +1,5 @@
1
1
  export type RedirectTemplate = {
2
- from: string;
2
+ from?: string;
3
3
  location: string | URL;
4
4
  status: number;
5
5
  };
@@ -6,7 +6,7 @@ function redirectTemplate({ status, location, from }) {
6
6
  <meta name="robots" content="noindex">
7
7
  <link rel="canonical" href="${location}">
8
8
  <body>
9
- <a href="${location}">Redirecting from <code>${from}</code> to <code>${location}</code></a>
9
+ <a href="${location}">Redirecting ${from ? `from <code>${from}</code> ` : ""}to <code>${location}</code></a>
10
10
  </body>`;
11
11
  }
12
12
  export {
@@ -483,6 +483,8 @@ async function createRouteManifest(params, logger, { dev = false } = {}) {
483
483
  }
484
484
  if (dev || settings.buildOutput === "server") {
485
485
  injectImageEndpoint(settings, { routes }, dev ? "dev" : "build");
486
+ }
487
+ if (dev || settings.config.adapter) {
486
488
  injectServerIslandRoute(settings.config, { routes });
487
489
  }
488
490
  await runHookRoutesResolved({ routes, settings, logger });
@@ -1,4 +1,5 @@
1
1
  import fsMod from 'node:fs';
2
+ import { type FSWatcher } from 'vite';
2
3
  import type { AstroSettings, ManifestData } from '../../types/astro.js';
3
4
  import type { AstroInlineConfig } from '../../types/public/config.js';
4
5
  import type { Logger } from '../logger/core.js';
@@ -13,6 +14,7 @@ export type SyncOptions = {
13
14
  };
14
15
  manifest: ManifestData;
15
16
  command: 'build' | 'dev' | 'sync';
17
+ watcher?: FSWatcher;
16
18
  };
17
19
  export default function sync(inlineConfig: AstroInlineConfig, { fs, telemetry: _telemetry }?: {
18
20
  fs?: typeof fsMod;
@@ -33,4 +35,4 @@ export declare function clearContentLayerCache({ settings, logger, fs, isDev, }:
33
35
  *
34
36
  * @experimental The JavaScript API is experimental
35
37
  */
36
- export declare function syncInternal({ mode, logger, fs, settings, skip, force, manifest, command, }: SyncOptions): Promise<void>;
38
+ export declare function syncInternal({ mode, logger, fs, settings, skip, force, manifest, command, watcher, }: SyncOptions): Promise<void>;
@@ -74,7 +74,8 @@ async function syncInternal({
74
74
  skip,
75
75
  force,
76
76
  manifest,
77
- command
77
+ command,
78
+ watcher
78
79
  }) {
79
80
  const isDev = command === "dev";
80
81
  if (force) {
@@ -98,8 +99,12 @@ async function syncInternal({
98
99
  const contentLayer = globalContentLayer.init({
99
100
  settings,
100
101
  logger,
101
- store
102
+ store,
103
+ watcher
102
104
  });
105
+ if (watcher) {
106
+ contentLayer.watchContentConfig();
107
+ }
103
108
  await contentLayer.sync();
104
109
  if (!skip?.cleanup) {
105
110
  contentLayer.dispose();
@@ -1,5 +1,6 @@
1
1
  import { transform } from "esbuild";
2
2
  import MagicString from "magic-string";
3
+ import { createFilter, isCSSRequest } from "vite";
3
4
  const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
4
5
  function getReferencedPrivateKeys(source, privateEnv) {
5
6
  const references = /* @__PURE__ */ new Set();
@@ -43,6 +44,7 @@ function importMetaEnv({ envLoader }) {
43
44
  let isDev;
44
45
  let devImportMetaEnvPrepend;
45
46
  let viteConfig;
47
+ const filter = createFilter(null, ["**/*.html", "**/*.htm", "**/*.json"]);
46
48
  return {
47
49
  name: "astro:vite-plugin-env",
48
50
  config(_, { command }) {
@@ -65,7 +67,7 @@ function importMetaEnv({ envLoader }) {
65
67
  }
66
68
  },
67
69
  transform(source, id, options) {
68
- if (!options?.ssr || !source.includes("import.meta.env")) {
70
+ if (!options?.ssr || !source.includes("import.meta.env") || !filter(id) || isCSSRequest(id) || viteConfig.assetsInclude(id)) {
69
71
  return;
70
72
  }
71
73
  privateEnv ??= envLoader.getPrivateEnv();
@@ -23,7 +23,12 @@ const perf = [
23
23
  selector: 'img:not([loading]), img[loading="eager"], iframe:not([loading]), iframe[loading="eager"]',
24
24
  match(element) {
25
25
  const htmlElement = element;
26
- const elementYPosition = htmlElement.getBoundingClientRect().y + window.scrollY;
26
+ let currentElement = element;
27
+ let elementYPosition = 0;
28
+ while (currentElement) {
29
+ elementYPosition += currentElement.offsetTop;
30
+ currentElement = currentElement.offsetParent;
31
+ }
27
32
  if (elementYPosition < window.innerHeight) return false;
28
33
  if (htmlElement.src.startsWith("data:")) return false;
29
34
  return true;
@@ -36,7 +41,12 @@ const perf = [
36
41
  selector: 'img[loading="lazy"], iframe[loading="lazy"]',
37
42
  match(element) {
38
43
  const htmlElement = element;
39
- const elementYPosition = htmlElement.getBoundingClientRect().y + window.scrollY;
44
+ let currentElement = element;
45
+ let elementYPosition = 0;
46
+ while (currentElement) {
47
+ elementYPosition += currentElement.offsetTop;
48
+ currentElement = currentElement.offsetParent;
49
+ }
40
50
  if (elementYPosition > window.innerHeight) return false;
41
51
  if (htmlElement.src.startsWith("data:")) return false;
42
52
  return true;
@@ -79,7 +79,7 @@ export type PaginateFunction = <PaginateData, AdditionalPaginateProps extends Pr
79
79
  page: Page<PaginateData>;
80
80
  } & OmitIndexSignature<AdditionalPaginateProps>>;
81
81
  }[];
82
- export type APIRoute<Props extends Record<string, any> = Record<string, any>, APIParams extends Record<string, string | undefined> = Record<string, string | undefined>> = (context: APIContext<Props, APIParams>) => Response | Promise<Response>;
82
+ export type APIRoute<APIProps extends Record<string, any> = Record<string, any>, APIParams extends Record<string, string | undefined> = Record<string, string | undefined>> = (context: APIContext<APIProps, APIParams>) => Response | Promise<Response>;
83
83
  export type RewritePayload = string | URL | Request;
84
84
  export type MiddlewareNext = (rewritePayload?: RewritePayload) => Promise<Response>;
85
85
  export type MiddlewareHandler = (context: APIContext, next: MiddlewareNext) => Promise<Response> | Response | Promise<void> | void;
@@ -3,7 +3,8 @@ import path from "node:path";
3
3
  import { appendForwardSlash } from "@astrojs/internal-helpers/path";
4
4
  import { bold } from "kleur/colors";
5
5
  import notFoundTemplate, { subpathNotUsedTemplate } from "../template/4xx.js";
6
- import { writeHtmlResponse } from "./response.js";
6
+ import { writeHtmlResponse, writeRedirectResponse } from "./response.js";
7
+ const manySlashes = /\/{2,}$/;
7
8
  function baseMiddleware(settings, logger) {
8
9
  const { config } = settings;
9
10
  const site = config.site ? new URL(config.base, config.site) : void 0;
@@ -12,6 +13,10 @@ function baseMiddleware(settings, logger) {
12
13
  const devRootReplacement = devRoot.endsWith("/") ? "/" : "";
13
14
  return function devBaseMiddleware(req, res, next) {
14
15
  const url = req.url;
16
+ if (manySlashes.test(url)) {
17
+ const destination = url.replace(manySlashes, "/");
18
+ return writeRedirectResponse(res, 301, destination);
19
+ }
15
20
  let pathname;
16
21
  try {
17
22
  pathname = decodeURI(new URL(url, "http://localhost").pathname);
@@ -4,5 +4,6 @@ import type { ModuleLoader } from '../core/module-loader/index.js';
4
4
  export declare function handle404Response(origin: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
5
5
  export declare function handle500Response(loader: ModuleLoader, res: http.ServerResponse, err: ErrorWithMetadata): Promise<void>;
6
6
  export declare function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string): void;
7
+ export declare function writeRedirectResponse(res: http.ServerResponse, statusCode: number, location: string): void;
7
8
  export declare function writeWebResponse(res: http.ServerResponse, webResponse: Response): Promise<void>;
8
9
  export declare function writeSSRResult(webRequest: Request, webResponse: Response, res: http.ServerResponse): Promise<void>;
@@ -2,6 +2,7 @@ import { Http2ServerResponse } from "node:http2";
2
2
  import { Readable } from "node:stream";
3
3
  import { getSetCookiesFromResponse } from "../core/cookies/index.js";
4
4
  import { getViteErrorPayload } from "../core/errors/dev/index.js";
5
+ import { redirectTemplate } from "../core/routing/3xx.js";
5
6
  import notFoundTemplate from "../template/4xx.js";
6
7
  async function handle404Response(origin, req, res) {
7
8
  const pathname = decodeURI(new URL(origin + req.url).pathname);
@@ -37,6 +38,16 @@ function writeHtmlResponse(res, statusCode, html) {
37
38
  res.write(html);
38
39
  res.end();
39
40
  }
41
+ function writeRedirectResponse(res, statusCode, location) {
42
+ const html = redirectTemplate({ status: statusCode, location });
43
+ res.writeHead(statusCode, {
44
+ Location: location,
45
+ "Content-Type": "text/html",
46
+ "Content-Length": Buffer.byteLength(html, "utf-8")
47
+ });
48
+ res.write(html);
49
+ res.end();
50
+ }
40
51
  async function writeWebResponse(res, webResponse) {
41
52
  const { status, headers, body, statusText } = webResponse;
42
53
  const setCookieHeaders = Array.from(getSetCookiesFromResponse(webResponse));
@@ -87,6 +98,7 @@ export {
87
98
  handle404Response,
88
99
  handle500Response,
89
100
  writeHtmlResponse,
101
+ writeRedirectResponse,
90
102
  writeSSRResult,
91
103
  writeWebResponse
92
104
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.1.7",
3
+ "version": "5.1.8",
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",
@@ -143,14 +143,14 @@
143
143
  "prompts": "^2.4.2",
144
144
  "rehype": "^13.0.2",
145
145
  "semver": "^7.6.3",
146
- "shiki": "^1.26.2",
146
+ "shiki": "^1.29.1",
147
147
  "tinyexec": "^0.3.2",
148
148
  "tsconfck": "^3.1.4",
149
149
  "ultrahtml": "^1.5.3",
150
150
  "unist-util-visit": "^5.0.0",
151
151
  "unstorage": "^1.14.4",
152
152
  "vfile": "^6.0.3",
153
- "vite": "^6.0.7",
153
+ "vite": "^6.0.9",
154
154
  "vitefu": "^1.0.5",
155
155
  "which-pm": "^3.0.0",
156
156
  "xxhash-wasm": "^1.1.0",
@@ -196,11 +196,11 @@
196
196
  "rehype-slug": "^6.0.0",
197
197
  "rehype-toc": "^3.0.2",
198
198
  "remark-code-titles": "^0.1.2",
199
- "rollup": "^4.30.1",
200
- "sass": "^1.83.1",
201
- "undici": "^7.2.1",
199
+ "rollup": "^4.31.0",
200
+ "sass": "^1.83.4",
201
+ "undici": "^7.2.3",
202
202
  "unified": "^11.0.5",
203
- "vitest": "^3.0.0-beta.4",
203
+ "vitest": "^3.0.2",
204
204
  "astro-scripts": "0.0.14"
205
205
  },
206
206
  "engines": {