astro 6.0.0-alpha.3 → 6.0.0-alpha.5

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.
@@ -28,7 +28,7 @@ async function prepareAssetsGenerationEnv(app, totalCount) {
28
28
  const isServerOutput = settings.buildOutput === "server";
29
29
  let serverRoot, clientRoot;
30
30
  if (isServerOutput) {
31
- serverRoot = manifest.buildServerDir;
31
+ serverRoot = new URL(".prerender/", manifest.buildServerDir);
32
32
  clientRoot = manifest.buildClientDir;
33
33
  } else {
34
34
  serverRoot = getOutDirWithinCwd(manifest.outDir);
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.0-alpha.3";
3
+ version = "6.0.0-alpha.5";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -43,11 +43,6 @@ export type { ImageFunction };
43
43
  export type SchemaContext = {
44
44
  image: ImageFunction;
45
45
  };
46
- export type LiveCollectionConfig<L extends LiveLoader, S extends BaseSchema | undefined = undefined> = {
47
- type?: 'live';
48
- schema?: S;
49
- loader: L;
50
- };
51
46
  type LoaderConstraint<TData extends {
52
47
  id: string;
53
48
  }> = Loader | (() => Array<TData> | Promise<Array<TData>> | Record<string, Omit<TData, 'id'> & {
@@ -55,14 +50,35 @@ type LoaderConstraint<TData extends {
55
50
  }> | Promise<Record<string, Omit<TData, 'id'> & {
56
51
  id?: string;
57
52
  }>>);
58
- export type CollectionConfig<TSchema extends BaseSchema, TLoader extends LoaderConstraint<{
53
+ type ContentLayerConfig<S extends BaseSchema, TLoader extends LoaderConstraint<{
59
54
  id: string;
60
55
  }>> = {
61
56
  type?: 'content_layer';
62
- schema?: TSchema | ((context: SchemaContext) => TSchema);
57
+ schema?: S | ((context: SchemaContext) => S);
63
58
  loader: TLoader;
64
59
  };
60
+ type DataCollectionConfig<S extends BaseSchema> = {
61
+ type: 'data';
62
+ schema?: S | ((context: SchemaContext) => S);
63
+ };
64
+ type ContentCollectionConfig<S extends BaseSchema> = {
65
+ type?: 'content';
66
+ schema?: S | ((context: SchemaContext) => S);
67
+ loader?: never;
68
+ };
69
+ export type LiveCollectionConfig<L extends LiveLoader, S extends BaseSchema | undefined = undefined> = {
70
+ type?: 'live';
71
+ schema?: S;
72
+ loader: L;
73
+ };
74
+ export type CollectionConfig<S extends BaseSchema, TLoader extends LoaderConstraint<{
75
+ id: string;
76
+ }> = LoaderConstraint<{
77
+ id: string;
78
+ }>> = ContentCollectionConfig<S> | DataCollectionConfig<S> | ContentLayerConfig<S, TLoader>;
65
79
  export declare function defineLiveCollection<L extends LiveLoader, S extends BaseSchema | undefined = undefined>(config: LiveCollectionConfig<L, S>): LiveCollectionConfig<L, S>;
66
- export declare function defineCollection<TSchema extends BaseSchema, TLoader extends LoaderConstraint<{
80
+ export declare function defineCollection<S extends BaseSchema, TLoader extends LoaderConstraint<{
81
+ id: string;
82
+ }> = LoaderConstraint<{
67
83
  id: string;
68
- }>>(config: CollectionConfig<TSchema, TLoader>): CollectionConfig<TSchema, TLoader>;
84
+ }>>(config: CollectionConfig<S, TLoader>): CollectionConfig<S, TLoader>;
@@ -71,24 +71,20 @@ function defineCollection(config) {
71
71
  )
72
72
  });
73
73
  }
74
- if (!("loader" in config)) {
75
- throw new AstroError({
76
- ...AstroErrorData.ContentCollectionMissingLoader,
77
- message: AstroErrorData.ContentCollectionMissingLoader.message(importerFilename)
78
- });
79
- }
80
- if (config.type && config.type !== CONTENT_LAYER_TYPE) {
81
- throw new AstroError({
82
- ...AstroErrorData.ContentCollectionInvalidType,
83
- message: AstroErrorData.ContentCollectionInvalidType.message(config.type, importerFilename)
84
- });
85
- }
86
- if (typeof config.loader === "object" && typeof config.loader.load !== "function" && ("loadEntry" in config.loader || "loadCollection" in config.loader)) {
87
- throw new AstroUserError(
88
- `Live content collections must be defined in "src/live.config.ts" file. Check your collection definitions in "${importerFilename ?? "your content config file"}" to ensure you are not using a live loader.`
89
- );
74
+ if ("loader" in config) {
75
+ if (config.type && config.type !== CONTENT_LAYER_TYPE) {
76
+ throw new AstroUserError(
77
+ `A content collection is defined with legacy features (e.g. missing a \`loader\` or has a \`type\`). Check your collection definitions in ${importerFilename ?? "your content config file"} to ensure that all collections are defined using the current properties.`
78
+ );
79
+ }
80
+ if (typeof config.loader === "object" && typeof config.loader.load !== "function" && ("loadEntry" in config.loader || "loadCollection" in config.loader)) {
81
+ throw new AstroUserError(
82
+ `Live content collections must be defined in "src/live.config.ts" file. Check the loaders used in "${importerFilename ?? "your content config file"}" to ensure you are not using a live loader to define a build-time content collection.`
83
+ );
84
+ }
85
+ config.type = CONTENT_LAYER_TYPE;
90
86
  }
91
- config.type = CONTENT_LAYER_TYPE;
87
+ if (!config.type) config.type = "content";
92
88
  return config;
93
89
  }
94
90
  export {
@@ -133,14 +133,24 @@ class ContentLayer {
133
133
  )
134
134
  ]);
135
135
  }
136
- if (contentConfig?.status === "error") {
137
- logger.error(`Error loading content config. Skipping sync.
138
- ${contentConfig.error.message}`);
139
- return;
140
- }
141
- if (contentConfig?.status !== "loaded") {
142
- logger.error(`Content config not loaded, skipping sync. Status was ${contentConfig?.status}`);
143
- return;
136
+ switch (contentConfig?.status) {
137
+ case "loaded":
138
+ break;
139
+ case "error":
140
+ logger.error(
141
+ `Error loading content config. Skipping sync.
142
+ ${contentConfig.error.message}`
143
+ );
144
+ return;
145
+ case "does-not-exist":
146
+ return;
147
+ case "init":
148
+ case "loading":
149
+ case void 0:
150
+ logger.error(
151
+ `Content config not loaded, skipping sync. Status was ${contentConfig?.status}`
152
+ );
153
+ return;
144
154
  }
145
155
  logger.info("Syncing content");
146
156
  const {
@@ -164,7 +174,7 @@ ${contentConfig.error.message}`);
164
174
  logger.info("Content config changed");
165
175
  shouldClear = true;
166
176
  }
167
- if (previousAstroVersion && previousAstroVersion !== "6.0.0-alpha.3") {
177
+ if (previousAstroVersion && previousAstroVersion !== "6.0.0-alpha.5") {
168
178
  logger.info("Astro version changed");
169
179
  shouldClear = true;
170
180
  }
@@ -172,8 +182,8 @@ ${contentConfig.error.message}`);
172
182
  logger.info("Clearing content store");
173
183
  this.#store.clearAll();
174
184
  }
175
- if ("6.0.0-alpha.3") {
176
- await this.#store.metaStore().set("astro-version", "6.0.0-alpha.3");
185
+ if ("6.0.0-alpha.5") {
186
+ await this.#store.metaStore().set("astro-version", "6.0.0-alpha.5");
177
187
  }
178
188
  if (currentConfigDigest) {
179
189
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -184,19 +194,24 @@ ${contentConfig.error.message}`);
184
194
  if (!options?.loaders?.length) {
185
195
  this.#watcher?.removeAllTrackedListeners();
186
196
  }
197
+ const backwardsCompatEnabled = this.#settings.config.legacy?.collectionsBackwardsCompat ?? false;
187
198
  await Promise.all(
188
199
  Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
189
- if (collection.type !== CONTENT_LAYER_TYPE) {
200
+ if (collection.type !== CONTENT_LAYER_TYPE && !backwardsCompatEnabled) {
201
+ return;
202
+ }
203
+ if (collection.type !== CONTENT_LAYER_TYPE && !("loader" in collection)) {
190
204
  return;
191
205
  }
192
206
  let { schema } = collection;
193
- if (!schema && typeof collection.loader === "object") {
207
+ const loaderName = "loader" in collection ? collection.loader.name : "content";
208
+ if (!schema && "loader" in collection && typeof collection.loader === "object") {
194
209
  schema = collection.loader.schema;
195
210
  if (!schema && collection.loader.createSchema) {
196
211
  ({ schema } = await collection.loader.createSchema());
197
212
  }
198
213
  }
199
- if (options?.loaders && (typeof collection.loader !== "object" || !options.loaders.includes(collection.loader.name))) {
214
+ if (options?.loaders && "loader" in collection && (typeof collection.loader !== "object" || !options.loaders.includes(collection.loader.name))) {
200
215
  return;
201
216
  }
202
217
  const context = await this.#getLoaderContext({
@@ -214,16 +229,18 @@ ${contentConfig.error.message}`);
214
229
  { ...collection, schema },
215
230
  false
216
231
  ),
217
- loaderName: collection.loader.name,
232
+ loaderName,
218
233
  refreshContextData: options?.context
219
234
  });
220
- if (typeof collection.loader === "function") {
221
- return simpleLoader(collection.loader, context);
222
- }
223
- if (!collection.loader.load) {
224
- throw new Error(`Collection loader for ${name} does not have a load method`);
235
+ if ("loader" in collection) {
236
+ if (typeof collection.loader === "function") {
237
+ return simpleLoader(collection.loader, context);
238
+ }
239
+ if (!collection.loader?.load) {
240
+ throw new Error(`Collection loader for ${name} does not have a load method`);
241
+ }
242
+ return collection.loader.load(context);
225
243
  }
226
- return collection.loader.load(context);
227
244
  })
228
245
  );
229
246
  await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
@@ -18,9 +18,12 @@ interface GlobOptions {
18
18
  **/
19
19
  generateId?: (options: GenerateIdOptions) => string;
20
20
  }
21
+ export declare const secretLegacyFlag: unique symbol;
21
22
  /**
22
23
  * Loads multiple entries, using a glob pattern to match files.
23
24
  * @param pattern A glob pattern to match files, relative to the content directory.
24
25
  */
25
- export declare function glob(globOptions: GlobOptions): Loader;
26
+ export declare function glob(globOptions: GlobOptions & {
27
+ [secretLegacyFlag]?: boolean;
28
+ }): Loader;
26
29
  export {};
@@ -6,11 +6,19 @@ import colors from "piccolore";
6
6
  import picomatch from "picomatch";
7
7
  import { glob as tinyglobby } from "tinyglobby";
8
8
  import { getContentEntryIdAndSlug, posixRelative } from "../utils.js";
9
- function generateIdDefault({ entry, base, data }) {
9
+ function generateIdDefault({ entry, base, data }, isLegacy) {
10
10
  if (data.slug) {
11
11
  return data.slug;
12
12
  }
13
13
  const entryURL = new URL(encodeURI(entry), base);
14
+ if (isLegacy) {
15
+ const { id } = getContentEntryIdAndSlug({
16
+ entry: entryURL,
17
+ contentDir: base,
18
+ collection: ""
19
+ });
20
+ return id;
21
+ }
14
22
  const { slug } = getContentEntryIdAndSlug({
15
23
  entry: entryURL,
16
24
  contentDir: base,
@@ -24,6 +32,7 @@ function checkPrefix(pattern, prefix) {
24
32
  }
25
33
  return pattern.startsWith(prefix);
26
34
  }
35
+ const secretLegacyFlag = Symbol("astro.legacy-glob");
27
36
  function glob(globOptions) {
28
37
  if (checkPrefix(globOptions.pattern, "../")) {
29
38
  throw new Error(
@@ -35,11 +44,21 @@ function glob(globOptions) {
35
44
  "Glob patterns cannot start with `/`. Set the `base` option to a parent directory or use a relative path instead."
36
45
  );
37
46
  }
38
- const generateId = globOptions?.generateId ?? generateIdDefault;
47
+ const isLegacy = !!globOptions[secretLegacyFlag];
48
+ const generateId = globOptions?.generateId ?? ((opts) => generateIdDefault(opts, isLegacy));
39
49
  const fileToIdMap = /* @__PURE__ */ new Map();
40
50
  return {
41
51
  name: "glob-loader",
42
- load: async ({ config, logger, watcher, parseData, store, generateDigest, entryTypes }) => {
52
+ load: async ({
53
+ config,
54
+ collection,
55
+ logger,
56
+ watcher,
57
+ parseData,
58
+ store,
59
+ generateDigest,
60
+ entryTypes
61
+ }) => {
43
62
  const renderFunctionByContentType = /* @__PURE__ */ new WeakMap();
44
63
  const untouchedEntries = new Set(store.keys());
45
64
  async function syncData(entry, base, entryType, oldId) {
@@ -84,13 +103,16 @@ function glob(globOptions) {
84
103
  data,
85
104
  filePath: filePath2
86
105
  });
87
- if (entryType.getRenderFunction) {
88
- let render = renderFunctionByContentType.get(entryType);
89
- if (store.has(id)) {
106
+ if (existingEntry && existingEntry.filePath && existingEntry.filePath !== relativePath2) {
107
+ const oldFilePath = new URL(existingEntry.filePath, config.root);
108
+ if (existsSync(oldFilePath)) {
90
109
  logger.warn(
91
110
  `Duplicate id "${id}" found in ${filePath2}. Later items with the same id will overwrite earlier ones.`
92
111
  );
93
112
  }
113
+ }
114
+ if (entryType.getRenderFunction) {
115
+ let render = renderFunctionByContentType.get(entryType);
94
116
  if (!render) {
95
117
  render = await entryType.getRenderFunction(config);
96
118
  renderFunctionByContentType.set(entryType, render);
@@ -130,7 +152,12 @@ function glob(globOptions) {
130
152
  }
131
153
  fileToIdMap.set(filePath2, id);
132
154
  }
133
- const baseDir = globOptions.base ? new URL(globOptions.base, config.root) : config.root;
155
+ let baseDir;
156
+ if (isLegacy && !globOptions.base) {
157
+ baseDir = new URL(`./src/content/${collection}`, config.root);
158
+ } else {
159
+ baseDir = globOptions.base ? new URL(globOptions.base, config.root) : config.root;
160
+ }
134
161
  if (!baseDir.pathname.endsWith("/")) {
135
162
  baseDir.pathname = `${baseDir.pathname}/`;
136
163
  }
@@ -229,5 +256,6 @@ function glob(globOptions) {
229
256
  };
230
257
  }
231
258
  export {
232
- glob
259
+ glob,
260
+ secretLegacyFlag
233
261
  };
@@ -35,7 +35,11 @@ async function createContentTypesGenerator({
35
35
  viteServer
36
36
  }) {
37
37
  const collectionEntryMap = {};
38
- const contentPaths = getContentPaths(settings.config, fs);
38
+ const contentPaths = getContentPaths(
39
+ settings.config,
40
+ fs,
41
+ settings.config.legacy?.collectionsBackwardsCompat
42
+ );
39
43
  const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
40
44
  const contentEntryExts = [...contentEntryConfigByExt.keys()];
41
45
  const dataEntryExts = getDataEntryExts(settings);
@@ -288,10 +292,10 @@ async function typeForCollection(collection, collectionKey) {
288
292
  if (collection?.schema) {
289
293
  return { type: `InferEntrySchema<${collectionKey}>` };
290
294
  }
291
- if (!collection?.type || typeof collection.loader === "function") {
295
+ if (!collection?.type || typeof collection.loader === "function" || !collection.loader) {
292
296
  return { type: "any" };
293
297
  }
294
- if (collection.loader.schema) {
298
+ if (typeof collection.loader === "object" && collection.loader.schema) {
295
299
  return { type: `InferLoaderSchema<${collectionKey}>` };
296
300
  }
297
301
  const result = await getCreateSchemaResult(collection, collectionKey);
@@ -323,7 +327,7 @@ async function writeContentFiles({
323
327
  fs.mkdirSync(collectionSchemasDir, { recursive: true });
324
328
  for (const [collection, config] of Object.entries(contentConfig?.collections ?? {})) {
325
329
  collectionEntryMap[JSON.stringify(collection)] ??= {
326
- type: config.type,
330
+ type: config.type ?? "unknown",
327
331
  entries: {}
328
332
  };
329
333
  }
@@ -388,8 +392,8 @@ async function writeContentFiles({
388
392
  hasSchema: Boolean(
389
393
  // Is there a user provided schema or
390
394
  collectionConfig?.schema || // Is it a loader object and
391
- typeof collectionConfig?.loader !== "function" && // Is it a loader static schema or
392
- (collectionConfig?.loader.schema || // is it a loader dynamic schema
395
+ typeof collectionConfig?.loader === "object" && // Is it a loader static schema or
396
+ (collectionConfig.loader.schema || // is it a loader dynamic schema
393
397
  createSchemaResultCache.has(collectionKey))
394
398
  ),
395
399
  name: key
@@ -14,6 +14,14 @@ export declare const loaderReturnSchema: z.ZodUnion<readonly [z.ZodArray<z.ZodOb
14
14
  id: z.ZodOptional<z.ZodString>;
15
15
  }, z.core.$loose>>]>;
16
16
  declare const collectionConfigParser: z.ZodUnion<readonly [z.ZodObject<{
17
+ type: z.ZodOptional<z.ZodLiteral<"content">>;
18
+ schema: z.ZodOptional<z.ZodAny>;
19
+ loader: z.ZodOptional<z.ZodNever>;
20
+ }, z.core.$strip>, z.ZodObject<{
21
+ type: z.ZodOptional<z.ZodLiteral<"data">>;
22
+ schema: z.ZodOptional<z.ZodAny>;
23
+ loader: z.ZodOptional<z.ZodNever>;
24
+ }, z.core.$strip>, z.ZodObject<{
17
25
  type: z.ZodLiteral<"content_layer">;
18
26
  schema: z.ZodOptional<z.ZodAny>;
19
27
  loader: z.ZodUnion<readonly [z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>, z.ZodObject<{
@@ -38,6 +46,14 @@ declare const collectionConfigParser: z.ZodUnion<readonly [z.ZodObject<{
38
46
  }, z.core.$strip>]>;
39
47
  declare const contentConfigParser: z.ZodObject<{
40
48
  collections: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
49
+ type: z.ZodOptional<z.ZodLiteral<"content">>;
50
+ schema: z.ZodOptional<z.ZodAny>;
51
+ loader: z.ZodOptional<z.ZodNever>;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodOptional<z.ZodLiteral<"data">>;
54
+ schema: z.ZodOptional<z.ZodAny>;
55
+ loader: z.ZodOptional<z.ZodNever>;
56
+ }, z.core.$strip>, z.ZodObject<{
41
57
  type: z.ZodLiteral<"content_layer">;
42
58
  schema: z.ZodOptional<z.ZodAny>;
43
59
  loader: z.ZodUnion<readonly [z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>, z.ZodObject<{
@@ -158,7 +174,7 @@ export type ContentPaths = {
158
174
  url: URL;
159
175
  };
160
176
  };
161
- export declare function getContentPaths({ srcDir, root }: Pick<AstroConfig, 'root' | 'srcDir'>, fs?: typeof fsMod): ContentPaths;
177
+ export declare function getContentPaths({ srcDir, root }: Pick<AstroConfig, 'root' | 'srcDir'>, fs?: typeof fsMod, legacyCollectionsBackwardsCompat?: boolean): ContentPaths;
162
178
  /**
163
179
  * Check for slug in content entry frontmatter and validate the type,
164
180
  * falling back to the `generatedSlug` if none is found.
@@ -8,6 +8,7 @@ import xxhash from "xxhash-wasm";
8
8
  import * as z from "zod/v4";
9
9
  import { AstroError, AstroErrorData, errorMap, MarkdownError } from "../core/errors/index.js";
10
10
  import { isYAMLException } from "../core/errors/utils.js";
11
+ import { appendForwardSlash } from "../core/path.js";
11
12
  import { normalizePath } from "../core/viteUtils.js";
12
13
  import {
13
14
  CONTENT_LAYER_TYPE,
@@ -17,6 +18,7 @@ import {
17
18
  LIVE_CONTENT_TYPE,
18
19
  PROPAGATED_ASSET_FLAG
19
20
  } from "./consts.js";
21
+ import { glob, secretLegacyFlag } from "./loaders/glob.js";
20
22
  import { createImage } from "./runtime-assets.js";
21
23
  const entryTypeSchema = z.object({
22
24
  id: z.string({
@@ -36,6 +38,16 @@ const loaderReturnSchema = z.union([
36
38
  )
37
39
  ]);
38
40
  const collectionConfigParser = z.union([
41
+ z.object({
42
+ type: z.literal("content").optional(),
43
+ schema: z.any().optional(),
44
+ loader: z.never().optional()
45
+ }),
46
+ z.object({
47
+ type: z.literal("data").optional(),
48
+ schema: z.any().optional(),
49
+ loader: z.never().optional()
50
+ }),
39
51
  z.object({
40
52
  type: z.literal(CONTENT_LAYER_TYPE),
41
53
  schema: z.any().optional(),
@@ -349,7 +361,11 @@ async function loadContentConfig({
349
361
  settings,
350
362
  environment
351
363
  }) {
352
- const contentPaths = getContentPaths(settings.config, fs);
364
+ const contentPaths = getContentPaths(
365
+ settings.config,
366
+ fs,
367
+ settings.config.legacy?.collectionsBackwardsCompat
368
+ );
353
369
  if (!contentPaths.config.exists) {
354
370
  return void 0;
355
371
  }
@@ -383,6 +399,74 @@ ${message}
383
399
  return void 0;
384
400
  }
385
401
  }
402
+ async function autogenerateCollections({
403
+ config,
404
+ settings,
405
+ fs
406
+ }) {
407
+ if (!config) {
408
+ return config;
409
+ }
410
+ if (!settings.config.legacy?.collectionsBackwardsCompat) {
411
+ return config;
412
+ }
413
+ const contentDir = new URL("./content/", settings.config.srcDir);
414
+ const collections = config.collections ?? {};
415
+ const contentExts = getContentEntryExts(settings);
416
+ const dataExts = getDataEntryExts(settings);
417
+ const contentPattern = globWithUnderscoresIgnored("", contentExts);
418
+ const dataPattern = globWithUnderscoresIgnored("", dataExts);
419
+ let usesContentLayer = false;
420
+ for (const collectionName of Object.keys(collections)) {
421
+ const collection = collections[collectionName];
422
+ if (collection?.type === CONTENT_LAYER_TYPE || collection?.type === LIVE_CONTENT_TYPE) {
423
+ usesContentLayer = true;
424
+ continue;
425
+ }
426
+ const isDataCollection = collection?.type === "data";
427
+ const base = new URL(`${collectionName}/`, contentDir);
428
+ collections[collectionName] = {
429
+ ...collection,
430
+ type: CONTENT_LAYER_TYPE,
431
+ loader: glob({
432
+ base,
433
+ pattern: isDataCollection ? dataPattern : contentPattern,
434
+ [secretLegacyFlag]: true
435
+ })
436
+ };
437
+ }
438
+ if (!usesContentLayer && fs.existsSync(contentDir)) {
439
+ const orphanedCollections = [];
440
+ for (const entry of await fs.promises.readdir(contentDir, { withFileTypes: true })) {
441
+ const collectionName = entry.name;
442
+ if (["_", "."].includes(collectionName.at(0) ?? "")) {
443
+ continue;
444
+ }
445
+ if (entry.isDirectory() && !(collectionName in collections)) {
446
+ orphanedCollections.push(collectionName);
447
+ const base = new URL(`${collectionName}/`, contentDir);
448
+ collections[collectionName] = {
449
+ type: CONTENT_LAYER_TYPE,
450
+ loader: glob({
451
+ base,
452
+ pattern: contentPattern,
453
+ [secretLegacyFlag]: true
454
+ })
455
+ };
456
+ }
457
+ }
458
+ if (orphanedCollections.length > 0) {
459
+ console.warn(
460
+ `
461
+ Auto-generating collections for folders in "src/content/" that are not defined as collections.
462
+ This is deprecated, so you should define these collections yourself in "src/content.config.ts".
463
+ The following collections have been auto-generated: ${orphanedCollections.map((name) => colors.green(name)).join(", ")}
464
+ `
465
+ );
466
+ }
467
+ }
468
+ return { ...config, collections };
469
+ }
386
470
  async function reloadContentConfigObserver({
387
471
  observer = globalContentConfigObserver,
388
472
  ...loadContentConfigOpts
@@ -390,6 +474,10 @@ async function reloadContentConfigObserver({
390
474
  observer.set({ status: "loading" });
391
475
  try {
392
476
  let config = await loadContentConfig(loadContentConfigOpts);
477
+ config = await autogenerateCollections({
478
+ config,
479
+ ...loadContentConfigOpts
480
+ });
393
481
  if (config) {
394
482
  observer.set({ status: "loaded", config });
395
483
  } else {
@@ -424,20 +512,38 @@ function contentObservable(initialCtx) {
424
512
  subscribe
425
513
  };
426
514
  }
427
- function getContentPaths({ srcDir, root }, fs = fsMod) {
515
+ function getContentPaths({ srcDir, root }, fs = fsMod, legacyCollectionsBackwardsCompat = false) {
516
+ const pkgBase = new URL("../../", import.meta.url);
428
517
  const configStats = searchConfig(fs, srcDir);
429
518
  if (!configStats.exists) {
430
519
  const legacyConfigStats = searchLegacyConfig(fs, srcDir);
431
520
  if (legacyConfigStats.exists) {
432
- const relativePath = path.relative(fileURLToPath(root), fileURLToPath(legacyConfigStats.url));
433
- throw new AstroError({
434
- ...AstroErrorData.LegacyContentConfigError,
435
- message: AstroErrorData.LegacyContentConfigError.message(relativePath)
436
- });
521
+ if (!legacyCollectionsBackwardsCompat) {
522
+ const relativePath = path.relative(
523
+ fileURLToPath(root),
524
+ fileURLToPath(legacyConfigStats.url)
525
+ );
526
+ throw new AstroError({
527
+ ...AstroErrorData.LegacyContentConfigError,
528
+ message: AstroErrorData.LegacyContentConfigError.message(relativePath)
529
+ });
530
+ }
531
+ return getContentPathsWithConfig(root, srcDir, pkgBase, legacyConfigStats, fs);
437
532
  }
438
533
  }
439
534
  const liveConfigStats = searchLiveConfig(fs, srcDir);
440
- const pkgBase = new URL("../../", import.meta.url);
535
+ return {
536
+ root: new URL("./", root),
537
+ contentDir: new URL("./content/", srcDir),
538
+ assetsDir: new URL("./assets/", srcDir),
539
+ typesTemplate: new URL("templates/content/types.d.ts", pkgBase),
540
+ virtualModTemplate: new URL("templates/content/module.mjs", pkgBase),
541
+ config: configStats,
542
+ liveConfig: liveConfigStats
543
+ };
544
+ }
545
+ function getContentPathsWithConfig(root, srcDir, pkgBase, configStats, fs) {
546
+ const liveConfigStats = searchLiveConfig(fs, srcDir);
441
547
  return {
442
548
  root: new URL("./", root),
443
549
  contentDir: new URL("./content/", srcDir),
@@ -499,6 +605,21 @@ async function getEntrySlug({
499
605
  });
500
606
  return parseEntrySlug({ generatedSlug, frontmatterSlug, id, collection });
501
607
  }
608
+ function getExtGlob(exts) {
609
+ return exts.length === 1 ? (
610
+ // Wrapping {...} breaks when there is only one extension
611
+ exts[0]
612
+ ) : `{${exts.join(",")}}`;
613
+ }
614
+ function globWithUnderscoresIgnored(relContentDir, exts) {
615
+ const extGlob = getExtGlob(exts);
616
+ const contentDir = relContentDir.length > 0 ? appendForwardSlash(relContentDir) : relContentDir;
617
+ return [
618
+ `${contentDir}**/*${extGlob}`,
619
+ `!${contentDir}**/_*/**/*${extGlob}`,
620
+ `!${contentDir}**/_*${extGlob}`
621
+ ];
622
+ }
502
623
  function hasAssetPropagationFlag(id) {
503
624
  try {
504
625
  return new URL(id, "file://").searchParams.has(PROPAGATED_ASSET_FLAG);
@@ -1,6 +1,6 @@
1
- import type * as vite from 'vite';
2
1
  import { type Plugin } from 'vite';
3
2
  import type { BuildInternals } from '../core/build/internal.js';
3
+ import type { ExtractedChunk } from '../core/build/static-build.js';
4
4
  import type { AstroSettings } from '../types/astro.js';
5
5
  export declare function astroContentAssetPropagationPlugin({ settings, }: {
6
6
  settings: AstroSettings;
@@ -10,8 +10,7 @@ export declare function astroContentAssetPropagationPlugin({ settings, }: {
10
10
  * Finds chunks with LINKS_PLACEHOLDER and STYLES_PLACEHOLDER, and replaces them
11
11
  * with actual styles from propagatedStylesMap.
12
12
  */
13
- export declare function contentAssetsBuildPostHook(base: string, internals: BuildInternals, { ssrOutputs, prerenderOutputs, mutate, }: {
14
- ssrOutputs: vite.Rollup.RollupOutput[];
15
- prerenderOutputs: vite.Rollup.RollupOutput[];
16
- mutate: (chunk: vite.Rollup.OutputChunk, envs: ['server'], code: string) => void;
13
+ export declare function contentAssetsBuildPostHook(base: string, internals: BuildInternals, { chunks, mutate, }: {
14
+ chunks: ExtractedChunk[];
15
+ mutate: (fileName: string, code: string, prerender: boolean) => void;
17
16
  }): Promise<void>;
@@ -148,17 +148,10 @@ async function getStylesForURL(filePath, environment) {
148
148
  };
149
149
  }
150
150
  async function contentAssetsBuildPostHook(base, internals, {
151
- ssrOutputs,
152
- prerenderOutputs,
151
+ chunks,
153
152
  mutate
154
153
  }) {
155
- const outputs = ssrOutputs.flatMap((o) => o.output).concat(
156
- ...(Array.isArray(prerenderOutputs) ? prerenderOutputs : [prerenderOutputs]).flatMap(
157
- (o) => o.output
158
- )
159
- );
160
- for (const chunk of outputs) {
161
- if (chunk.type !== "chunk") continue;
154
+ for (const chunk of chunks) {
162
155
  if (!chunk.code.includes(LINKS_PLACEHOLDER)) continue;
163
156
  const entryStyles = /* @__PURE__ */ new Set();
164
157
  const entryLinks = /* @__PURE__ */ new Set();
@@ -189,7 +182,7 @@ async function contentAssetsBuildPostHook(base, internals, {
189
182
  } else {
190
183
  newCode = newCode.replace(JSON.stringify(LINKS_PLACEHOLDER), "[]");
191
184
  }
192
- mutate(chunk, ["server"], newCode);
185
+ mutate(chunk.fileName, newCode, chunk.prerender);
193
186
  }
194
187
  }
195
188
  export {
@@ -40,7 +40,11 @@ function astroContentImportPlugin({
40
40
  settings,
41
41
  logger
42
42
  }) {
43
- const contentPaths = getContentPaths(settings.config, fs);
43
+ const contentPaths = getContentPaths(
44
+ settings.config,
45
+ fs,
46
+ settings.config.legacy?.collectionsBackwardsCompat
47
+ );
44
48
  const contentEntryExts = getContentEntryExts(settings);
45
49
  const dataEntryExts = getDataEntryExts(settings);
46
50
  const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
@@ -46,7 +46,11 @@ function astroContentVirtualModPlugin({
46
46
  enforce: "pre",
47
47
  config(_, env) {
48
48
  dataStoreFile = getDataStoreFile(settings, env.command === "serve");
49
- const contentPaths = getContentPaths(settings.config);
49
+ const contentPaths = getContentPaths(
50
+ settings.config,
51
+ void 0,
52
+ settings.config.legacy?.collectionsBackwardsCompat
53
+ );
50
54
  if (contentPaths.liveConfig.exists) {
51
55
  liveConfig = normalizePath(fileURLToPath(contentPaths.liveConfig.url));
52
56
  }
@@ -178,7 +182,11 @@ async function generateContentEntryFile({
178
182
  settings,
179
183
  isClient
180
184
  }) {
181
- const contentPaths = getContentPaths(settings.config);
185
+ const contentPaths = getContentPaths(
186
+ settings.config,
187
+ void 0,
188
+ settings.config.legacy?.collectionsBackwardsCompat
189
+ );
182
190
  const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
183
191
  let virtualModContents;
184
192
  if (isClient) {
@@ -1,5 +1,4 @@
1
- import type { OutputChunk } from 'rollup';
2
- import type * as vite from 'vite';
1
+ import type { ExtractedChunk } from '../static-build.js';
3
2
  import type { BuildInternals } from '../internal.js';
4
3
  import type { StaticBuildOptions } from '../types.js';
5
4
  /**
@@ -27,8 +26,7 @@ export declare const MANIFEST_REPLACE = "@@ASTRO_MANIFEST_REPLACE@@";
27
26
  * Post-build hook that injects the computed manifest into bundled chunks.
28
27
  * Finds the serialized manifest chunk and replaces the placeholder token with real data.
29
28
  */
30
- export declare function manifestBuildPostHook(options: StaticBuildOptions, internals: BuildInternals, { ssrOutputs, prerenderOutputs, mutate, }: {
31
- ssrOutputs: vite.Rollup.RollupOutput[];
32
- prerenderOutputs: vite.Rollup.RollupOutput[];
33
- mutate: (chunk: OutputChunk, envs: ['server'], code: string) => void;
29
+ export declare function manifestBuildPostHook(options: StaticBuildOptions, internals: BuildInternals, { chunks, mutate, }: {
30
+ chunks: ExtractedChunk[];
31
+ mutate: (fileName: string, code: string, prerender: boolean) => void;
34
32
  }): Promise<void>;
@@ -29,30 +29,14 @@ import { sessionConfigToManifest } from "../../session/utils.js";
29
29
  const MANIFEST_REPLACE = "@@ASTRO_MANIFEST_REPLACE@@";
30
30
  const replaceExp = new RegExp(`['"]${MANIFEST_REPLACE}['"]`, "g");
31
31
  async function manifestBuildPostHook(options, internals, {
32
- ssrOutputs,
33
- prerenderOutputs,
32
+ chunks,
34
33
  mutate
35
34
  }) {
36
35
  const manifest = await createManifest(options, internals);
37
- if (ssrOutputs.length > 0) {
38
- let manifestEntryChunk;
39
- for (const output of ssrOutputs) {
40
- for (const chunk of output.output) {
41
- if (chunk.type === "asset") {
42
- continue;
43
- }
44
- if (chunk.code && chunk.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID)) {
45
- manifestEntryChunk = chunk;
46
- break;
47
- }
48
- }
49
- if (manifestEntryChunk) {
50
- break;
51
- }
52
- }
53
- if (!manifestEntryChunk) {
54
- throw new Error(`Did not find serialized manifest chunk for SSR`);
55
- }
36
+ const ssrManifestChunk = chunks.find(
37
+ (c) => !c.prerender && c.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID)
38
+ );
39
+ if (ssrManifestChunk) {
56
40
  const shouldPassMiddlewareEntryPoint = options.settings.adapter?.adapterFeatures?.edgeMiddleware;
57
41
  await runHookBuildSsr({
58
42
  config: options.settings.config,
@@ -60,29 +44,15 @@ async function manifestBuildPostHook(options, internals, {
60
44
  logger: options.logger,
61
45
  middlewareEntryPoint: shouldPassMiddlewareEntryPoint ? internals.middlewareEntryPoint : void 0
62
46
  });
63
- const code = injectManifest(manifest, manifestEntryChunk);
64
- mutate(manifestEntryChunk, ["server"], code);
47
+ const code = injectManifest(manifest, ssrManifestChunk.code);
48
+ mutate(ssrManifestChunk.fileName, code, false);
65
49
  }
66
- if (prerenderOutputs?.length > 0) {
67
- let prerenderManifestChunk;
68
- for (const output of prerenderOutputs) {
69
- for (const chunk of output.output) {
70
- if (chunk.type === "asset") {
71
- continue;
72
- }
73
- if (chunk.code && chunk.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID)) {
74
- prerenderManifestChunk = chunk;
75
- break;
76
- }
77
- }
78
- if (prerenderManifestChunk) {
79
- break;
80
- }
81
- }
82
- if (prerenderManifestChunk) {
83
- const prerenderCode = injectManifest(manifest, prerenderManifestChunk);
84
- mutate(prerenderManifestChunk, ["server"], prerenderCode);
85
- }
50
+ const prerenderManifestChunk = chunks.find(
51
+ (c) => c.prerender && c.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID)
52
+ );
53
+ if (prerenderManifestChunk) {
54
+ const code = injectManifest(manifest, prerenderManifestChunk.code);
55
+ mutate(prerenderManifestChunk.fileName, code, true);
86
56
  }
87
57
  }
88
58
  async function createManifest(buildOpts, internals) {
@@ -99,8 +69,7 @@ async function createManifest(buildOpts, internals) {
99
69
  const manifest = await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey);
100
70
  return manifest;
101
71
  }
102
- function injectManifest(manifest, chunk) {
103
- const code = chunk.code;
72
+ function injectManifest(manifest, code) {
104
73
  return code.replace(replaceExp, () => {
105
74
  return JSON.stringify(manifest);
106
75
  });
@@ -1,6 +1,16 @@
1
1
  import { type BuildInternals } from '../../core/build/internal.js';
2
2
  import type { RouteData } from '../../types/public/internal.js';
3
3
  import type { StaticBuildOptions } from './types.js';
4
+ /**
5
+ * Minimal chunk data extracted from RollupOutput for deferred manifest/content injection.
6
+ * Allows releasing full RollupOutput objects early to reduce memory usage.
7
+ */
8
+ export interface ExtractedChunk {
9
+ fileName: string;
10
+ code: string;
11
+ moduleIds: string[];
12
+ prerender: boolean;
13
+ }
4
14
  export declare function viteBuild(opts: StaticBuildOptions): Promise<{
5
15
  internals: BuildInternals;
6
16
  prerenderOutputDir: URL;
@@ -4,6 +4,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import colors from "piccolore";
5
5
  import { glob } from "tinyglobby";
6
6
  import * as vite from "vite";
7
+ import { LINKS_PLACEHOLDER } from "../../content/consts.js";
7
8
  import { contentAssetsBuildPostHook } from "../../content/vite-plugin-content-assets.js";
8
9
  import { createBuildInternals } from "../../core/build/internal.js";
9
10
  import { emptyDir, removeEmptyDirs } from "../../core/fs/index.js";
@@ -27,6 +28,25 @@ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from "./util.
27
28
  import { NOOP_MODULE_ID } from "./plugins/plugin-noop.js";
28
29
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
29
30
  const PRERENDER_ENTRY_FILENAME_PREFIX = "prerender-entry";
31
+ function extractRelevantChunks(outputs, prerender) {
32
+ const extracted = [];
33
+ for (const output of outputs) {
34
+ for (const chunk of output.output) {
35
+ if (chunk.type === "asset") continue;
36
+ const needsContentInjection = chunk.code.includes(LINKS_PLACEHOLDER);
37
+ const needsManifestInjection = chunk.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID);
38
+ if (needsContentInjection || needsManifestInjection) {
39
+ extracted.push({
40
+ fileName: chunk.fileName,
41
+ code: chunk.code,
42
+ moduleIds: [...chunk.moduleIds],
43
+ prerender
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return extracted;
49
+ }
30
50
  async function viteBuild(opts) {
31
51
  const { allPages, settings } = opts;
32
52
  settings.timer.start("SSR build");
@@ -45,16 +65,13 @@ async function viteBuild(opts) {
45
65
  }
46
66
  const ssrTime = performance.now();
47
67
  opts.logger.info("build", `Building ${settings.buildOutput} entrypoints...`);
48
- const { ssrOutput, prerenderOutput, clientOutput } = await buildEnvironments(opts, internals);
68
+ const { extractedChunks } = await buildEnvironments(opts, internals);
49
69
  opts.logger.info(
50
70
  "build",
51
71
  colors.green(`\u2713 Completed in ${getTimeStat(ssrTime, performance.now())}.`)
52
72
  );
53
73
  settings.timer.end("SSR build");
54
- const ssrOutputs = viteBuildReturnToRollupOutputs(ssrOutput);
55
- const clientOutputs = viteBuildReturnToRollupOutputs(clientOutput ?? []);
56
- const prerenderOutputs = viteBuildReturnToRollupOutputs(prerenderOutput);
57
- await runManifestInjection(opts, internals, ssrOutputs, clientOutputs, prerenderOutputs);
74
+ await runManifestInjection(opts, internals, extractedChunks);
58
75
  const prerenderOutputDir = new URL("./.prerender/", getServerOutputDirectory(settings));
59
76
  return { internals, prerenderOutputDir };
60
77
  }
@@ -208,16 +225,23 @@ async function buildEnvironments(opts, internals) {
208
225
  logger: opts.logger
209
226
  });
210
227
  const builder = await vite.createBuilder(updatedViteBuildConfig);
211
- const ssrOutput = settings.buildOutput === "static" ? [] : await builder.build(builder.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]);
212
- const prerenderOutput = await builder.build(builder.environments.prerender);
228
+ let ssrOutput = settings.buildOutput === "static" ? [] : await builder.build(builder.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]);
229
+ const ssrChunks = extractRelevantChunks(viteBuildReturnToRollupOutputs(ssrOutput), false);
230
+ ssrOutput = void 0;
231
+ let prerenderOutput = await builder.build(builder.environments.prerender);
213
232
  extractPrerenderEntryFileName(internals, prerenderOutput);
233
+ const prerenderChunks = extractRelevantChunks(
234
+ viteBuildReturnToRollupOutputs(prerenderOutput),
235
+ true
236
+ );
237
+ prerenderOutput = void 0;
214
238
  internals.clientInput = getClientInput(internals, settings);
215
239
  if (!internals.clientInput.size) {
216
240
  internals.clientInput.add(NOOP_MODULE_ID);
217
241
  }
218
242
  builder.environments.client.config.build.rollupOptions.input = Array.from(internals.clientInput);
219
- const clientOutput = await builder.build(builder.environments.client);
220
- return { ssrOutput, prerenderOutput, clientOutput };
243
+ await builder.build(builder.environments.client);
244
+ return { extractedChunks: [...ssrChunks, ...prerenderChunks] };
221
245
  }
222
246
  function getPrerenderEntryFileName(prerenderOutput) {
223
247
  const outputs = viteBuildReturnToRollupOutputs(prerenderOutput);
@@ -238,41 +262,25 @@ function getPrerenderEntryFileName(prerenderOutput) {
238
262
  function extractPrerenderEntryFileName(internals, prerenderOutput) {
239
263
  internals.prerenderEntryFileName = getPrerenderEntryFileName(prerenderOutput);
240
264
  }
241
- async function runManifestInjection(opts, internals, ssrOutputs, _clientOutputs, prerenderOutputs) {
265
+ async function runManifestInjection(opts, internals, chunks) {
242
266
  const mutations = /* @__PURE__ */ new Map();
243
- const mutate = (chunk, targets, newCode) => {
244
- chunk.code = newCode;
245
- mutations.set(chunk.fileName, {
246
- targets,
247
- code: newCode
248
- });
267
+ const mutate = (fileName, newCode, prerender) => {
268
+ mutations.set(fileName, { code: newCode, prerender });
249
269
  };
250
- await manifestBuildPostHook(opts, internals, {
251
- ssrOutputs,
252
- prerenderOutputs,
253
- mutate
254
- });
255
- await contentAssetsBuildPostHook(opts.settings.config.base, internals, {
256
- ssrOutputs,
257
- prerenderOutputs,
258
- mutate
259
- });
260
- await writeMutatedChunks(opts, mutations, prerenderOutputs);
270
+ await manifestBuildPostHook(opts, internals, { chunks, mutate });
271
+ await contentAssetsBuildPostHook(opts.settings.config.base, internals, { chunks, mutate });
272
+ await writeMutatedChunks(opts, mutations);
261
273
  }
262
- async function writeMutatedChunks(opts, mutations, prerenderOutputs) {
274
+ async function writeMutatedChunks(opts, mutations) {
263
275
  const { settings } = opts;
264
276
  const config = settings.config;
265
- const build = settings.config.build;
266
277
  const serverOutputDir = getServerOutputDirectory(settings);
267
278
  for (const [fileName, mutation] of mutations) {
268
279
  let root;
269
- const isPrerender = prerenderOutputs.some(
270
- (output) => output.output.some((chunk) => chunk.type !== "asset" && chunk.fileName === fileName)
271
- );
272
- if (isPrerender) {
280
+ if (mutation.prerender) {
273
281
  root = new URL("./.prerender/", serverOutputDir);
274
282
  } else if (settings.buildOutput === "server") {
275
- root = mutation.targets.includes("server") ? build.server : build.client;
283
+ root = config.build.server;
276
284
  } else {
277
285
  root = getOutDirWithinCwd(config.outDir);
278
286
  }
@@ -57,7 +57,9 @@ export declare const ASTRO_CONFIG_DEFAULTS: {
57
57
  integrations: never[];
58
58
  markdown: Required<import("@astrojs/markdown-remark").AstroMarkdownOptions>;
59
59
  vite: {};
60
- legacy: {};
60
+ legacy: {
61
+ collectionsBackwardsCompat: false;
62
+ };
61
63
  redirects: {};
62
64
  security: {
63
65
  checkOrigin: true;
@@ -521,7 +523,9 @@ export declare const AstroConfigSchema: z.ZodObject<{
521
523
  chromeDevtoolsWorkspace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
522
524
  svgo: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<SvgoConfig, SvgoConfig>]>>>;
523
525
  }, z.core.$strict>>;
524
- legacy: z.ZodDefault<z.ZodObject<{}, z.core.$strip>>;
526
+ legacy: z.ZodPrefault<z.ZodObject<{
527
+ collectionsBackwardsCompat: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
528
+ }, z.core.$strip>>;
525
529
  }, z.core.$strip>;
526
530
  export type AstroConfigType = z.infer<typeof AstroConfigSchema>;
527
531
  export {};
@@ -41,7 +41,9 @@ const ASTRO_CONFIG_DEFAULTS = {
41
41
  integrations: [],
42
42
  markdown: markdownConfigDefaults,
43
43
  vite: {},
44
- legacy: {},
44
+ legacy: {
45
+ collectionsBackwardsCompat: false
46
+ },
45
47
  redirects: {},
46
48
  security: {
47
49
  checkOrigin: true,
@@ -276,7 +278,9 @@ const AstroConfigSchema = z.object({
276
278
  chromeDevtoolsWorkspace: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.chromeDevtoolsWorkspace),
277
279
  svgo: z.union([z.boolean(), z.custom((value) => value && typeof value === "object")]).optional().default(ASTRO_CONFIG_DEFAULTS.experimental.svgo)
278
280
  }).prefault({}),
279
- legacy: z.object({}).default({})
281
+ legacy: z.object({
282
+ collectionsBackwardsCompat: z.boolean().optional().default(false)
283
+ }).prefault({})
280
284
  });
281
285
  export {
282
286
  ASTRO_CONFIG_DEFAULTS,
@@ -415,7 +415,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
415
415
  chromeDevtoolsWorkspace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
416
416
  svgo: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<import("svgo").Config, import("svgo").Config>]>>>;
417
417
  }, z.core.$strict>>;
418
- legacy: z.ZodDefault<z.ZodObject<{}, z.core.$strip>>;
418
+ legacy: z.ZodPrefault<z.ZodObject<{
419
+ collectionsBackwardsCompat: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
420
+ }, z.core.$strip>>;
419
421
  root: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<import("url").URL, string>>;
420
422
  srcDir: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<import("url").URL, string>>;
421
423
  compressHTML: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -633,7 +635,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
633
635
  optimizedFallbacks?: boolean | undefined;
634
636
  })[] | undefined;
635
637
  };
636
- legacy: Record<string, never>;
638
+ legacy: {
639
+ collectionsBackwardsCompat: boolean;
640
+ };
637
641
  root: import("url").URL;
638
642
  srcDir: import("url").URL;
639
643
  compressHTML: boolean;
@@ -890,7 +894,9 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
890
894
  optimizedFallbacks?: boolean | undefined;
891
895
  })[] | undefined;
892
896
  };
893
- legacy: Record<string, never>;
897
+ legacy: {
898
+ collectionsBackwardsCompat: boolean;
899
+ };
894
900
  root: import("url").URL;
895
901
  srcDir: import("url").URL;
896
902
  compressHTML: boolean;
@@ -17,7 +17,11 @@ import {
17
17
  import { AstroTimer } from "./timer.js";
18
18
  import { loadTSConfig } from "./tsconfig.js";
19
19
  function createBaseSettings(config, logLevel) {
20
- const { contentDir } = getContentPaths(config);
20
+ const { contentDir } = getContentPaths(
21
+ config,
22
+ void 0,
23
+ config.legacy?.collectionsBackwardsCompat
24
+ );
21
25
  const dotAstroDir = new URL(".astro/", config.root);
22
26
  const preferences = createPreferences(config, dotAstroDir);
23
27
  return {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.0.0-alpha.3";
1
+ const ASTRO_VERSION = "6.0.0-alpha.5";
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";
@@ -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 = "6.0.0-alpha.3";
25
+ const currentVersion = "6.0.0-alpha.5";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -11,7 +11,8 @@ const errorMap = (issue) => {
11
11
  typeOrLiteralErrByPath.set(flattenedErrorPath, {
12
12
  code: unionError.code,
13
13
  received: unionError.received,
14
- expected: [unionError.expected]
14
+ expected: [unionError.expected],
15
+ message: unionError.message
15
16
  });
16
17
  }
17
18
  }
@@ -65,7 +66,8 @@ const errorMap = (issue) => {
65
66
  getTypeOrLiteralMsg({
66
67
  code: issue.code,
67
68
  received: typeof issue.input,
68
- expected: [issue.expected]
69
+ expected: [issue.expected],
70
+ message: issue.message
69
71
  })
70
72
  )
71
73
  };
@@ -74,7 +76,8 @@ const errorMap = (issue) => {
74
76
  }
75
77
  };
76
78
  const getTypeOrLiteralMsg = (error) => {
77
- if (typeof error.received === "undefined" || error.received === "undefined") return "Required";
79
+ if (typeof error.received === "undefined" || error.received === "undefined")
80
+ return error.message ?? "Required";
78
81
  const expectedDeduped = new Set(error.expected);
79
82
  switch (error.code) {
80
83
  case "invalid_type":
@@ -28,7 +28,7 @@ function serverStart({
28
28
  host,
29
29
  base
30
30
  }) {
31
- const version = "6.0.0-alpha.3";
31
+ const version = "6.0.0-alpha.5";
32
32
  const localPrefix = `${dim("\u2503")} Local `;
33
33
  const networkPrefix = `${dim("\u2503")} Network `;
34
34
  const emptyPrefix = " ".repeat(11);
@@ -265,7 +265,7 @@ function printHelp({
265
265
  message.push(
266
266
  linebreak(),
267
267
  ` ${bgGreen(black(` ${commandName} `))} ${green(
268
- `v${"6.0.0-alpha.3"}`
268
+ `v${"6.0.0-alpha.5"}`
269
269
  )} ${headline}`
270
270
  );
271
271
  }
@@ -109,7 +109,11 @@ async function syncInternal({
109
109
  }
110
110
  settings.timer.end("Sync content layer");
111
111
  } else {
112
- const paths = getContentPaths(settings.config, fs);
112
+ const paths = getContentPaths(
113
+ settings.config,
114
+ fs,
115
+ settings.config.legacy?.collectionsBackwardsCompat
116
+ );
113
117
  if (paths.config.exists || paths.liveConfig.exists) {
114
118
  settings.injectedTypes.push({
115
119
  filename: CONTENT_TYPES_FILE
@@ -210,7 +214,11 @@ async function syncContentCollections(settings, { mode, logger, fs }) {
210
214
  }
211
215
  let configFile;
212
216
  try {
213
- const contentPaths = getContentPaths(settings.config, fs);
217
+ const contentPaths = getContentPaths(
218
+ settings.config,
219
+ fs,
220
+ settings.config.legacy?.collectionsBackwardsCompat
221
+ );
214
222
  if (contentPaths.config.exists) {
215
223
  const matches = /\/(src\/.+)/.exec(contentPaths.config.url.href);
216
224
  if (matches) {
@@ -2249,7 +2249,25 @@ export interface AstroUserConfig<TLocales extends Locales = never, TDriver exten
2249
2249
  * These flags allow you to opt in to some deprecated or otherwise outdated behavior of Astro
2250
2250
  * in the latest version, so that you can continue to upgrade and take advantage of new Astro releases.
2251
2251
  */
2252
- legacy?: Record<string, never>;
2252
+ legacy?: {
2253
+ /**
2254
+ * Enable backwards compatibility for v4 content collections.
2255
+ *
2256
+ * When enabled, restores the following v4 behaviors:
2257
+ * - Allows legacy config file location: `src/content/config.ts`
2258
+ * - Allows collections without explicit loaders (automatically wraps with glob loader)
2259
+ * - Supports `type: 'content'` and `type: 'data'` without loaders
2260
+ * - Preserves legacy entry API: `entry.slug` and `entry.render()`
2261
+ * - Uses path-based entry IDs instead of slug-based IDs
2262
+ *
2263
+ * This is a temporary migration helper for projects upgrading to v6.
2264
+ * Migrate collections to the Content Layer API, then disable this flag.
2265
+ *
2266
+ * @type {boolean}
2267
+ * @default false
2268
+ */
2269
+ collectionsBackwardsCompat?: boolean;
2270
+ };
2253
2271
  /**
2254
2272
  *
2255
2273
  * @kind heading
@@ -46,7 +46,7 @@ const getViteResolveAlias = (settings) => {
46
46
  for (const resolvedValue of resolvedValues) {
47
47
  const resolved = resolvedValue.replace("*", id);
48
48
  if (fs.existsSync(resolved)) {
49
- return resolved;
49
+ return normalizePath(resolved);
50
50
  }
51
51
  }
52
52
  return null;
@@ -5,6 +5,18 @@ import { getVirtualModulePageName } from "./util.js";
5
5
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
6
6
  const VIRTUAL_PAGES_MODULE_ID = "virtual:astro:pages";
7
7
  const VIRTUAL_PAGES_RESOLVED_MODULE_ID = "\0" + VIRTUAL_PAGES_MODULE_ID;
8
+ function getRoutesForEnvironment(routes, isPrerender) {
9
+ const result = /* @__PURE__ */ new Set();
10
+ for (const route of routes) {
11
+ if (route.prerender === isPrerender) {
12
+ result.add(route);
13
+ }
14
+ if (route.redirectRoute) {
15
+ result.add(route.redirectRoute);
16
+ }
17
+ }
18
+ return result;
19
+ }
8
20
  function pluginPages({ routesList }) {
9
21
  return {
10
22
  name: "@astro/plugin-pages",
@@ -28,7 +40,11 @@ function pluginPages({ routesList }) {
28
40
  const imports = [];
29
41
  const pageMap = [];
30
42
  let i = 0;
31
- for (const route of routesList.routes) {
43
+ const envName = this.environment.name;
44
+ const isSSR = envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
45
+ const isPrerender = envName === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
46
+ const routes = isSSR || isPrerender ? getRoutesForEnvironment(routesList.routes, isPrerender) : new Set(routesList.routes);
47
+ for (const route of routes) {
32
48
  if (routeIsRedirect(route)) {
33
49
  continue;
34
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.0.0-alpha.3",
3
+ "version": "6.0.0-alpha.5",
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",
@@ -194,8 +194,8 @@
194
194
  "undici": "^6.22.0",
195
195
  "unified": "^11.0.5",
196
196
  "vitest": "^3.2.4",
197
- "@astrojs/check": "0.9.6-alpha.0",
198
- "astro-scripts": "0.0.14"
197
+ "astro-scripts": "0.0.14",
198
+ "@astrojs/check": "0.9.6-alpha.0"
199
199
  },
200
200
  "engines": {
201
201
  "node": "^20.19.5 || >=22.12.0",