astro 5.1.2 → 5.1.3

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.
@@ -99,15 +99,31 @@ class ContentLayer {
99
99
  return this.#queue.add(() => this.#doSync(options));
100
100
  }
101
101
  async #doSync(options) {
102
- const contentConfig = globalContentConfigObserver.get();
102
+ let contentConfig = globalContentConfigObserver.get();
103
103
  const logger = this.#logger.forkIntegrationLogger("content");
104
+ if (contentConfig?.status === "loading") {
105
+ contentConfig = await Promise.race([
106
+ new Promise((resolve) => {
107
+ const unsub = globalContentConfigObserver.subscribe((ctx) => {
108
+ unsub();
109
+ resolve(ctx);
110
+ });
111
+ }),
112
+ new Promise(
113
+ (resolve) => setTimeout(
114
+ () => resolve({ status: "error", error: new Error("Content config loading timed out") }),
115
+ 5e3
116
+ )
117
+ )
118
+ ]);
119
+ }
104
120
  if (contentConfig?.status === "error") {
105
121
  logger.error(`Error loading content config. Skipping sync.
106
122
  ${contentConfig.error.message}`);
107
123
  return;
108
124
  }
109
125
  if (contentConfig?.status !== "loaded") {
110
- logger.error("Content config not loaded, skipping sync");
126
+ logger.error(`Content config not loaded, skipping sync. Status was ${contentConfig?.status}`);
111
127
  return;
112
128
  }
113
129
  logger.info("Syncing content");
@@ -128,11 +144,11 @@ ${contentConfig.error.message}`);
128
144
  logger.info("Astro config changed");
129
145
  shouldClear = true;
130
146
  }
131
- if (currentConfigDigest && previousConfigDigest !== currentConfigDigest) {
147
+ if (previousConfigDigest && previousConfigDigest !== currentConfigDigest) {
132
148
  logger.info("Content config changed");
133
149
  shouldClear = true;
134
150
  }
135
- if (previousAstroVersion !== "5.1.2") {
151
+ if (previousAstroVersion && previousAstroVersion !== "5.1.3") {
136
152
  logger.info("Astro version changed");
137
153
  shouldClear = true;
138
154
  }
@@ -140,8 +156,8 @@ ${contentConfig.error.message}`);
140
156
  logger.info("Clearing content store");
141
157
  this.#store.clearAll();
142
158
  }
143
- if ("5.1.2") {
144
- await this.#store.metaStore().set("astro-version", "5.1.2");
159
+ if ("5.1.3") {
160
+ await this.#store.metaStore().set("astro-version", "5.1.3");
145
161
  }
146
162
  if (currentConfigDigest) {
147
163
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -506,7 +506,7 @@ export declare function getContentEntryIdAndSlug({ entry, contentDir, collection
506
506
  id: string;
507
507
  slug: string;
508
508
  };
509
- export declare function getEntryType(entryPath: string, paths: Pick<ContentPaths, 'config' | 'contentDir'>, contentFileExts: string[], dataFileExts: string[]): 'content' | 'data' | 'config' | 'ignored';
509
+ export declare function getEntryType(entryPath: string, paths: Pick<ContentPaths, 'config' | 'contentDir' | 'root'>, contentFileExts: string[], dataFileExts: string[]): 'content' | 'data' | 'config' | 'ignored';
510
510
  export declare function safeParseFrontmatter(source: string, id?: string): import("@astrojs/markdown-remark").ParseFrontmatterResult;
511
511
  /**
512
512
  * The content config is loaded separately from other `src/` files.
@@ -549,6 +549,7 @@ type Observable<C> = {
549
549
  export type ContentObservable = Observable<ContentCtx>;
550
550
  export declare function contentObservable(initialCtx: ContentCtx): ContentObservable;
551
551
  export type ContentPaths = {
552
+ root: URL;
552
553
  contentDir: URL;
553
554
  assetsDir: URL;
554
555
  typesTemplate: URL;
@@ -558,7 +559,7 @@ export type ContentPaths = {
558
559
  url: URL;
559
560
  };
560
561
  };
561
- export declare function getContentPaths({ srcDir, legacy }: Pick<AstroConfig, 'root' | 'srcDir' | 'legacy'>, fs?: typeof fsMod): ContentPaths;
562
+ export declare function getContentPaths({ srcDir, legacy, root }: Pick<AstroConfig, 'root' | 'srcDir' | 'legacy'>, fs?: typeof fsMod): ContentPaths;
562
563
  /**
563
564
  * Check for slug in content entry frontmatter and validate the type,
564
565
  * falling back to the `generatedSlug` if none is found.
@@ -295,13 +295,20 @@ function getRelativeEntryPath(entry, collection, contentDir) {
295
295
  const relativeToCollection = path.relative(collection, relativeToContent);
296
296
  return relativeToCollection;
297
297
  }
298
+ function isParentDirectory(parent, child) {
299
+ const relative = path.relative(fileURLToPath(parent), fileURLToPath(child));
300
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
301
+ }
298
302
  function getEntryType(entryPath, paths, contentFileExts, dataFileExts) {
299
303
  const { ext } = path.parse(entryPath);
300
304
  const fileUrl = pathToFileURL(entryPath);
305
+ const dotAstroDir = new URL("./.astro/", paths.root);
301
306
  if (fileUrl.href === paths.config.url.href) {
302
307
  return "config";
303
308
  } else if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir)) {
304
309
  return "ignored";
310
+ } else if (isParentDirectory(dotAstroDir, fileUrl)) {
311
+ return "ignored";
305
312
  } else if (contentFileExts.includes(ext)) {
306
313
  return "content";
307
314
  } else if (dataFileExts.includes(ext)) {
@@ -509,10 +516,11 @@ function contentObservable(initialCtx) {
509
516
  subscribe
510
517
  };
511
518
  }
512
- function getContentPaths({ srcDir, legacy }, fs = fsMod) {
519
+ function getContentPaths({ srcDir, legacy, root }, fs = fsMod) {
513
520
  const configStats = search(fs, srcDir, legacy?.collections);
514
521
  const pkgBase = new URL("../../", import.meta.url);
515
522
  return {
523
+ root: new URL("./", root),
516
524
  contentDir: new URL("./content/", srcDir),
517
525
  assetsDir: new URL("./assets/", srcDir),
518
526
  typesTemplate: new URL("templates/content/types.d.ts", pkgBase),
@@ -41,12 +41,16 @@ function astroContentVirtualModPlugin({
41
41
  fs
42
42
  }) {
43
43
  let dataStoreFile;
44
+ let devServer;
44
45
  return {
45
46
  name: "astro-content-virtual-mod-plugin",
46
47
  enforce: "pre",
47
48
  config(_, env) {
48
49
  dataStoreFile = getDataStoreFile(settings, env.command === "serve");
49
50
  },
51
+ buildStart() {
52
+ devServer?.watcher.add(fileURLToPath(dataStoreFile));
53
+ },
50
54
  async resolveId(id) {
51
55
  if (id === VIRTUAL_MODULE_ID) {
52
56
  return RESOLVED_VIRTUAL_MODULE_ID;
@@ -137,8 +141,8 @@ function astroContentVirtualModPlugin({
137
141
  }
138
142
  },
139
143
  configureServer(server) {
144
+ devServer = server;
140
145
  const dataStorePath = fileURLToPath(dataStoreFile);
141
- server.watcher.add(dataStorePath);
142
146
  function invalidateDataStore() {
143
147
  const module = server.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
144
148
  if (module) {
@@ -207,7 +207,7 @@ class App {
207
207
  this.#logger.error(null, err.stack || err.message || String(err));
208
208
  return this.#renderError(request, { locals, status: 500, error: err, clientAddress });
209
209
  } finally {
210
- session?.[PERSIST_SYMBOL]();
210
+ await session?.[PERSIST_SYMBOL]();
211
211
  }
212
212
  if (REROUTABLE_STATUS_CODES.includes(response.status) && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
213
213
  return this.#renderError(request, {
@@ -302,7 +302,7 @@ class App {
302
302
  });
303
303
  }
304
304
  } finally {
305
- session?.[PERSIST_SYMBOL]();
305
+ await session?.[PERSIST_SYMBOL]();
306
306
  }
307
307
  }
308
308
  const response = this.#mergeResponses(new Response(null, { status }), originalResponse);
@@ -19,6 +19,7 @@ import { AstroError, AstroErrorData } from "../errors/index.js";
19
19
  import { levels, timerMessage } from "../logger/core.js";
20
20
  import { apply as applyPolyfill } from "../polyfill.js";
21
21
  import { createRouteManifest } from "../routing/index.js";
22
+ import { getServerIslandRouteData } from "../server-islands/endpoint.js";
22
23
  import { clearContentLayerCache } from "../sync/index.js";
23
24
  import { ensureProcessNodeEnv } from "../util.js";
24
25
  import { collectPagesData } from "./page-data.js";
@@ -157,7 +158,7 @@ class AstroBuilder {
157
158
  await runHookBuildDone({
158
159
  settings: this.settings,
159
160
  pages: pageNames,
160
- routes: Object.values(allPages).flat().map((pageData) => pageData.route),
161
+ routes: Object.values(allPages).flat().map((pageData) => pageData.route).concat(hasServerIslands ? getServerIslandRouteData(this.settings.config) : []),
161
162
  logging: this.logger
162
163
  });
163
164
  if (this.logger.level && levels[this.logger.level()] <= levels["info"]) {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.1.2";
1
+ const ASTRO_VERSION = "5.1.3";
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";
@@ -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.2";
25
+ const currentVersion = "5.1.3";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -50,23 +50,6 @@ async function dev(inlineConfig) {
50
50
  } catch {
51
51
  }
52
52
  }
53
- const devServerAddressInfo = await startContainer(restart.container);
54
- logger.info(
55
- "SKIP_FORMAT",
56
- msg.serverStart({
57
- startupTime: performance.now() - devStart,
58
- resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
59
- host: restart.container.settings.config.server.host,
60
- base: restart.container.settings.config.base
61
- })
62
- );
63
- if (isPrerelease) {
64
- logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
65
- }
66
- if (restart.container.viteServer.config.server?.fs?.strict === false) {
67
- logger.warn("SKIP_FORMAT", msg.fsStrictWarning());
68
- }
69
- await attachContentServerListeners(restart.container);
70
53
  let store;
71
54
  try {
72
55
  const dataStoreFile = getDataStoreFile(restart.container.settings, true);
@@ -79,6 +62,7 @@ async function dev(inlineConfig) {
79
62
  if (!store) {
80
63
  store = new MutableDataStore();
81
64
  }
65
+ await attachContentServerListeners(restart.container);
82
66
  const config = globalContentConfigObserver.get();
83
67
  if (config.status === "error") {
84
68
  logger.error("content", config.error.message);
@@ -92,6 +76,24 @@ async function dev(inlineConfig) {
92
76
  });
93
77
  contentLayer.watchContentConfig();
94
78
  await contentLayer.sync();
79
+ } else {
80
+ logger.warn("content", "Content config not loaded");
81
+ }
82
+ const devServerAddressInfo = await startContainer(restart.container);
83
+ logger.info(
84
+ "SKIP_FORMAT",
85
+ msg.serverStart({
86
+ startupTime: performance.now() - devStart,
87
+ resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
88
+ host: restart.container.settings.config.server.host,
89
+ base: restart.container.settings.config.base
90
+ })
91
+ );
92
+ if (isPrerelease) {
93
+ logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
94
+ }
95
+ if (restart.container.viteServer.config.server?.fs?.strict === false) {
96
+ logger.warn("SKIP_FORMAT", msg.fsStrictWarning());
95
97
  }
96
98
  logger.info(null, green("watching for file changes..."));
97
99
  return {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.1.2";
41
+ const version = "5.1.3";
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.2"}`
279
+ `v${"5.1.3"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -19,7 +19,6 @@ async function getProps(opts) {
19
19
  ssr: serverLike,
20
20
  base
21
21
  });
22
- if (!staticPaths.length) return {};
23
22
  const params = getParams(route, decodeURI(pathname));
24
23
  const matchedStaticPath = findPathItemByKey(staticPaths, params, route, logger);
25
24
  if (!matchedStaticPath && (serverLike ? route.prerender : true)) {
@@ -111,7 +111,7 @@ function createFileBasedRoutes({ settings, cwd, fsMod }, logger) {
111
111
  const segment = isDir ? basename : name;
112
112
  validateSegment(segment, file);
113
113
  const parts = getParts(segment, file);
114
- const isIndex = isDir ? false : basename.startsWith("index.");
114
+ const isIndex = isDir ? false : basename.substring(0, basename.lastIndexOf(".")) === "index";
115
115
  const routeSuffix = basename.slice(basename.indexOf("."), -ext.length);
116
116
  const isPage = validPageExtensions.has(ext);
117
117
  items.push({
@@ -101,6 +101,7 @@ function createEndpoint(manifest) {
101
101
  for (const prop in data.slots) {
102
102
  slots[prop] = createSlotValueFromString(data.slots[prop]);
103
103
  }
104
+ result.response.headers.set("X-Robots-Tag", "noindex");
104
105
  if (isAstroComponentFactory(Component)) {
105
106
  const ServerIsland = Component;
106
107
  Component = function(...args) {
@@ -1,3 +1,4 @@
1
+ import MagicString from "magic-string";
1
2
  const VIRTUAL_ISLAND_MAP_ID = "@astro-server-islands";
2
3
  const RESOLVED_VIRTUAL_ISLAND_MAP_ID = "\0" + VIRTUAL_ISLAND_MAP_ID;
3
4
  const serverIslandPlaceholder = "'$$server-islands$$'";
@@ -67,6 +68,12 @@ function vitePluginServerIslands({ settings, logger }) {
67
68
  },
68
69
  renderChunk(code) {
69
70
  if (code.includes(serverIslandPlaceholder)) {
71
+ if (referenceIdMap.size === 0) {
72
+ return {
73
+ code: code.replace(serverIslandPlaceholder, "new Map();"),
74
+ map: null
75
+ };
76
+ }
70
77
  let mapSource = "new Map([";
71
78
  for (let [resolvedPath, referenceId] of referenceIdMap) {
72
79
  const fileName = this.getFileName(referenceId);
@@ -76,7 +83,12 @@ function vitePluginServerIslands({ settings, logger }) {
76
83
  }
77
84
  mapSource += "\n]);";
78
85
  referenceIdMap.clear();
79
- return code.replace(serverIslandPlaceholder, mapSource);
86
+ const ms = new MagicString(code);
87
+ ms.replace(serverIslandPlaceholder, mapSource);
88
+ return {
89
+ code: ms.toString(),
90
+ map: ms.generateMap({ hires: "boundary" })
91
+ };
80
92
  }
81
93
  }
82
94
  };
@@ -1,4 +1,4 @@
1
- import { stringify, unflatten } from "devalue";
1
+ import { stringify as rawStringify, unflatten as rawUnflatten } from "devalue";
2
2
  import {
3
3
  builtinDrivers,
4
4
  createStorage
@@ -8,6 +8,17 @@ import { AstroError } from "./errors/index.js";
8
8
  const PERSIST_SYMBOL = Symbol();
9
9
  const DEFAULT_COOKIE_NAME = "astro-session";
10
10
  const VALID_COOKIE_REGEX = /^[\w-]+$/;
11
+ const unflatten = (parsed, _) => {
12
+ return rawUnflatten(parsed, {
13
+ URL: (href) => new URL(href)
14
+ });
15
+ };
16
+ const stringify = (data, _) => {
17
+ return rawStringify(data, {
18
+ // Support URL objects
19
+ URL: (val) => val instanceof URL && val.href
20
+ });
21
+ };
11
22
  class AstroSession {
12
23
  // The cookies object.
13
24
  #cookies;
@@ -108,8 +119,9 @@ class AstroSession {
108
119
  message: "The session key was not provided."
109
120
  });
110
121
  }
122
+ let cloned;
111
123
  try {
112
- stringify(value);
124
+ cloned = unflatten(JSON.parse(stringify(value)));
113
125
  } catch (err) {
114
126
  throw new AstroError(
115
127
  {
@@ -128,7 +140,7 @@ class AstroSession {
128
140
  const lifetime = ttl ?? this.#config.ttl;
129
141
  const expires = typeof lifetime === "number" ? Date.now() + lifetime * 1e3 : lifetime;
130
142
  this.#data.set(key, {
131
- data: value,
143
+ data: cloned,
132
144
  expires
133
145
  });
134
146
  this.#dirty = true;
@@ -173,10 +185,7 @@ class AstroSession {
173
185
  const key = this.#ensureSessionID();
174
186
  let serialized;
175
187
  try {
176
- serialized = stringify(data, {
177
- // Support URL objects
178
- URL: (val) => val instanceof URL && val.href
179
- });
188
+ serialized = stringify(data);
180
189
  } catch (err) {
181
190
  throw new AstroError(
182
191
  {
@@ -233,10 +242,7 @@ class AstroSession {
233
242
  return this.#data;
234
243
  }
235
244
  try {
236
- const storedMap = unflatten(raw, {
237
- // Revive URL objects
238
- URL: (href) => new URL(href)
239
- });
245
+ const storedMap = unflatten(raw);
240
246
  if (!(storedMap instanceof Map)) {
241
247
  await this.#destroySafe();
242
248
  throw new AstroError({
@@ -107,8 +107,7 @@ async function syncInternal({
107
107
  if (paths.config.exists || // Legacy collections don't require a config file
108
108
  settings.config.legacy?.collections && fs.existsSync(paths.contentDir)) {
109
109
  settings.injectedTypes.push({
110
- filename: CONTENT_TYPES_FILE,
111
- content: ""
110
+ filename: CONTENT_TYPES_FILE
112
111
  });
113
112
  }
114
113
  }
@@ -125,7 +124,9 @@ function writeInjectedTypes(settings, fs) {
125
124
  for (const { filename, content } of settings.injectedTypes) {
126
125
  const filepath = fileURLToPath(new URL(filename, settings.dotAstroDir));
127
126
  fs.mkdirSync(dirname(filepath), { recursive: true });
128
- fs.writeFileSync(filepath, content, "utf-8");
127
+ if (content) {
128
+ fs.writeFileSync(filepath, content, "utf-8");
129
+ }
129
130
  references.push(normalizePath(relative(fileURLToPath(settings.dotAstroDir), filepath)));
130
131
  }
131
132
  const astroDtsContent = `${CLIENT_TYPES_REFERENCE}
@@ -57,7 +57,7 @@ export interface AstroSettings {
57
57
  latestAstroVersion: string | undefined;
58
58
  serverIslandMap: NonNullable<SSRManifest['serverIslandMap']>;
59
59
  serverIslandNameMap: NonNullable<SSRManifest['serverIslandNameMap']>;
60
- injectedTypes: Array<InjectedType>;
60
+ injectedTypes: Array<Omit<InjectedType, 'content'> & Partial<Pick<InjectedType, 'content'>>>;
61
61
  /**
62
62
  * Determine if the build output should be a static, dist folder or a adapter-based server output
63
63
  * undefined when unknown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.1.2",
3
+ "version": "5.1.3",
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",
@@ -198,9 +198,9 @@
198
198
  "remark-code-titles": "^0.1.2",
199
199
  "rollup": "^4.27.4",
200
200
  "sass": "^1.81.0",
201
- "undici": "^6.21.0",
201
+ "undici": "^7.2.0",
202
202
  "unified": "^11.0.5",
203
- "vitest": "^2.1.6",
203
+ "vitest": "^3.0.0-beta.3",
204
204
  "astro-scripts": "0.0.14"
205
205
  },
206
206
  "engines": {
@@ -89,27 +89,29 @@ declare module 'astro:content' {
89
89
  input: CollectionConfig<S>,
90
90
  ): CollectionConfig<S>;
91
91
 
92
- /** Run `astro sync` to generate high fidelity types */
92
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
93
93
  export const getEntryBySlug: (...args: any[]) => any;
94
- /** Run `astro sync` to generate high fidelity types */
94
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
95
95
  export const getDataEntryById: (...args: any[]) => any;
96
- /** Run `astro sync` to generate high fidelity types */
96
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
97
97
  export const getCollection: (...args: any[]) => any;
98
- /** Run `astro sync` to generate high fidelity types */
98
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
99
99
  export const getEntry: (...args: any[]) => any;
100
- /** Run `astro sync` to generate high fidelity types */
100
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
101
101
  export const getEntries: (...args: any[]) => any;
102
- /** Run `astro sync` to generate high fidelity types */
102
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
103
103
  export const reference: (...args: any[]) => any;
104
- /** Run `astro sync` to generate high fidelity types */
104
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
105
105
  export type CollectionKey = any;
106
- /** Run `astro sync` to generate high fidelity types */
106
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
107
107
  // biome-ignore lint/correctness/noUnusedVariables: stub generic type to match generated type
108
108
  export type CollectionEntry<C> = any;
109
- /** Run `astro sync` to generate high fidelity types */
109
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
110
110
  export type ContentCollectionKey = any;
111
- /** Run `astro sync` to generate high fidelity types */
111
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
112
112
  export type DataCollectionKey = any;
113
- /** Run `astro sync` to generate high fidelity types */
113
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
114
114
  export type ContentConfig = any;
115
+ /** Run `astro dev` or `astro sync` to generate high fidelity types */
116
+ export const render: (entry: any) => any;
115
117
  }