astro 7.2.9 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/assets/build/generate.d.ts +1 -1
  2. package/dist/assets/build/generate.js +3 -1
  3. package/dist/assets/endpoint/dev.js +6 -5
  4. package/dist/assets/endpoint/generic.js +5 -4
  5. package/dist/assets/endpoint/node.js +3 -3
  6. package/dist/assets/endpoint/shared.d.ts +3 -1
  7. package/dist/assets/endpoint/shared.js +9 -3
  8. package/dist/assets/fonts/config.d.ts +2 -2
  9. package/dist/assets/internal.d.ts +2 -1
  10. package/dist/assets/internal.js +11 -10
  11. package/dist/assets/services/service.d.ts +8 -7
  12. package/dist/assets/services/sharp.js +5 -5
  13. package/dist/assets/vite-plugin-assets.js +22 -5
  14. package/dist/cli/add/index.js +3 -1
  15. package/dist/cli/index.js +1 -1
  16. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  17. package/dist/cli/preferences/index.js +32 -30
  18. package/dist/cli/preview/index.js +44 -1
  19. package/dist/cli/telemetry/index.d.ts +2 -1
  20. package/dist/cli/telemetry/index.js +2 -2
  21. package/dist/content/content-layer.js +3 -3
  22. package/dist/content/data-store-writer.d.ts +18 -5
  23. package/dist/content/data-store-writer.js +11 -3
  24. package/dist/content/index.d.ts +1 -1
  25. package/dist/content/index.js +5 -1
  26. package/dist/content/mutable-data-store.d.ts +13 -1
  27. package/dist/content/mutable-data-store.js +35 -5
  28. package/dist/content/runtime.d.ts +8 -3
  29. package/dist/content/runtime.js +43 -32
  30. package/dist/content/types-generator.js +2 -1
  31. package/dist/content/utils.d.ts +5 -35
  32. package/dist/content/utils.js +71 -65
  33. package/dist/content/vite-plugin-content-imports.js +2 -1
  34. package/dist/content/vite-plugin-content-virtual-mod.d.ts +11 -1
  35. package/dist/content/vite-plugin-content-virtual-mod.js +36 -3
  36. package/dist/core/app/entrypoints/index.d.ts +4 -2
  37. package/dist/core/app/entrypoints/index.js +13 -11
  38. package/dist/core/app/entrypoints/virtual/dev.js +2 -2
  39. package/dist/core/app/prerender.d.ts +32 -0
  40. package/dist/core/app/prerender.js +22 -0
  41. package/dist/core/build/default-prerenderer.js +5 -2
  42. package/dist/core/build/generate.d.ts +3 -1
  43. package/dist/core/build/generate.js +40 -62
  44. package/dist/core/build/plugins/plugin-manifest.js +1 -1
  45. package/dist/core/cache/handler.js +12 -1
  46. package/dist/core/cache/memory-provider.js +35 -8
  47. package/dist/core/cache/runtime/cache.js +3 -0
  48. package/dist/core/cache/types.d.ts +2 -0
  49. package/dist/core/config/schemas/base.d.ts +5 -5
  50. package/dist/core/config/schemas/relative.d.ts +7 -7
  51. package/dist/core/constants.js +1 -1
  52. package/dist/core/cookies/cookies.d.ts +2 -1
  53. package/dist/core/cookies/cookies.js +5 -4
  54. package/dist/core/dev/dev.js +5 -3
  55. package/dist/core/fetch/fetch-state.d.ts +1 -1
  56. package/dist/core/fetch/fetch-state.js +4 -23
  57. package/dist/core/logger/core.d.ts +2 -0
  58. package/dist/core/logger/core.js +14 -0
  59. package/dist/core/logger/vite-plugin.d.ts +13 -2
  60. package/dist/core/logger/vite-plugin.js +8 -7
  61. package/dist/core/messages/runtime.js +1 -1
  62. package/dist/core/middleware/index.js +14 -1
  63. package/dist/core/render-scope/collect.d.ts +24 -0
  64. package/dist/core/render-scope/collect.js +27 -0
  65. package/dist/core/render-scope/node-scope.d.ts +10 -0
  66. package/dist/core/render-scope/node-scope.js +8 -0
  67. package/dist/core/render-scope/record.d.ts +14 -0
  68. package/dist/core/render-scope/record.js +12 -0
  69. package/dist/core/render-scope/scope.d.ts +38 -0
  70. package/dist/core/render-scope/scope.js +28 -0
  71. package/dist/core/rewrites/handler.js +1 -1
  72. package/dist/core/routing/create-manifest.js +5 -1
  73. package/dist/core/server-islands/endpoint.js +4 -1
  74. package/dist/core/server-islands/vite-plugin-server-islands.js +2 -2
  75. package/dist/core/sync/index.js +1 -1
  76. package/dist/core/util.d.ts +0 -9
  77. package/dist/core/util.js +0 -34
  78. package/dist/core/viteUtils.d.ts +1 -13
  79. package/dist/core/viteUtils.js +3 -33
  80. package/dist/types/public/integrations.d.ts +20 -13
  81. package/dist/vite-plugin-app/createAstroServerApp.js +1 -1
  82. package/dist/vite-plugin-routes/index.js +22 -8
  83. package/package.json +6 -6
  84. package/templates/content/module.mjs +13 -5
  85. package/dist/core/build/incremental-content-collector.d.ts +0 -10
  86. package/dist/core/build/incremental-content-collector.js +0 -33
  87. package/dist/core/build/incremental-image-collector.d.ts +0 -11
  88. package/dist/core/build/incremental-image-collector.js +0 -32
@@ -5,6 +5,7 @@ import { resolveRoot } from "../../core/config/config.js";
5
5
  import { printHelp } from "../../core/messages/runtime.js";
6
6
  import previewServer from "../../core/preview/index.js";
7
7
  import { isRunByAgent } from "../agent.js";
8
+ import { isIgnoreLock } from "../dev/index.js";
8
9
  import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
9
10
  import { background, logs, previewServerCommand, status, stop } from "../server.js";
10
11
  async function preview({ flags }) {
@@ -24,6 +25,10 @@ async function preview({ flags }) {
24
25
  ["--host", `Listen on all addresses, including LAN and public addresses.`],
25
26
  ["--host <custom-address>", `Expose on a network IP address at <custom-address>`],
26
27
  ["--open", "Automatically open the app in the browser on server start"],
28
+ [
29
+ "--ignore-lock",
30
+ "Start the preview server even if another one is already running, without checking or writing the lock file."
31
+ ],
27
32
  [
28
33
  "--allowed-hosts",
29
34
  "Specify a comma-separated list of allowed hosts or allow any hostname."
@@ -41,6 +46,8 @@ async function preview({ flags }) {
41
46
  if (agentDetected) {
42
47
  flags.json = true;
43
48
  }
49
+ const ignoreLock = isIgnoreLock(flags);
50
+ const wantsBackground = !!flags.background || agentDetected;
44
51
  const logger = createLoggerFromFlags(flags);
45
52
  const subcommand = flags._[3]?.toString();
46
53
  if (subcommand === "stop") {
@@ -55,7 +62,29 @@ async function preview({ flags }) {
55
62
  await logs({ flags, logger, config: previewServerCommand });
56
63
  return;
57
64
  }
58
- if (flags.background || agentDetected) {
65
+ if (ignoreLock) {
66
+ if (flags.force) {
67
+ throw new Error(
68
+ [
69
+ "`--force` and `--ignore-lock` cannot be used together.",
70
+ "",
71
+ "`--force` replaces the existing preview server; `--ignore-lock` starts a new one alongside it without touching the lock file. Choose one."
72
+ ].join("\n")
73
+ );
74
+ }
75
+ }
76
+ if (ignoreLock && wantsBackground) {
77
+ const reason = flags.background ? "`--background`" : "an auto-detected AI agent environment, which runs the preview server in the background automatically";
78
+ throw new Error(
79
+ [
80
+ `\`--ignore-lock\` cannot be used together with ${reason}.`,
81
+ "",
82
+ "Background preview servers rely on the lock file so `astro preview stop`, `astro preview status`, and `astro preview logs` can find them.",
83
+ "Run the preview server in the foreground to use --ignore-lock."
84
+ ].join("\n")
85
+ );
86
+ }
87
+ if (wantsBackground) {
59
88
  await background({ flags, logger, config: previewServerCommand });
60
89
  return;
61
90
  }
@@ -69,6 +98,20 @@ Run \`astro preview --help\` to see available commands.`
69
98
  process.exit(1);
70
99
  }
71
100
  const root = pathToFileURL(resolveRoot(flags.root) + "/");
101
+ if (ignoreLock) {
102
+ const existingServer2 = await checkExistingServer(root, "preview");
103
+ if (existingServer2) {
104
+ logger.info(
105
+ "SKIP_FORMAT",
106
+ [
107
+ `Starting a new preview server alongside the one already running at ${existingServer2.url} (pid ${existingServer2.pid}).`,
108
+ "This instance is not tracked by `astro preview stop`, `astro preview status`, or `astro preview logs`."
109
+ ].join("\n")
110
+ );
111
+ }
112
+ const inlineConfig2 = flagsToAstroInlineConfig(flags);
113
+ return await previewServer(inlineConfig2);
114
+ }
72
115
  const existingServer = await checkExistingServer(root, "preview");
73
116
  if (existingServer) {
74
117
  const message = [
@@ -1,7 +1,8 @@
1
+ import type { AstroLogger } from '../../core/logger/core.js';
1
2
  import { type Flags } from '../flags.js';
2
3
  interface TelemetryOptions {
3
4
  flags: Flags;
4
5
  }
5
- export declare function notify(): Promise<void>;
6
+ export declare function notify(logger: AstroLogger): Promise<void>;
6
7
  export declare function update(subcommand: string, { flags }: TelemetryOptions): Promise<void>;
7
8
  export {};
@@ -1,9 +1,9 @@
1
1
  import * as msg from "../../core/messages/runtime.js";
2
2
  import { telemetry } from "../../events/index.js";
3
3
  import { createLoggerFromFlags } from "../flags.js";
4
- async function notify() {
4
+ async function notify(logger) {
5
5
  await telemetry.notify(() => {
6
- console.log(msg.telemetryNotice() + "\n");
6
+ logger.info("SKIP_FORMAT", msg.telemetryNotice() + "\n");
7
7
  return true;
8
8
  });
9
9
  }
@@ -196,7 +196,7 @@ ${contentConfig.error.message}`
196
196
  logger.info("Content config changed");
197
197
  shouldClear = true;
198
198
  }
199
- if (previousAstroVersion && previousAstroVersion !== "7.2.9") {
199
+ if (previousAstroVersion && previousAstroVersion !== "7.3.0") {
200
200
  logger.info("Astro version changed");
201
201
  shouldClear = true;
202
202
  }
@@ -204,8 +204,8 @@ ${contentConfig.error.message}`
204
204
  logger.info("Clearing content store");
205
205
  this.#store.clearAll();
206
206
  }
207
- if ("7.2.9") {
208
- this.#store.metaStore().set("astro-version", "7.2.9");
207
+ if ("7.3.0") {
208
+ this.#store.metaStore().set("astro-version", "7.3.0");
209
209
  }
210
210
  if (currentConfigDigest) {
211
211
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -8,8 +8,17 @@ export type DataStoreManifest = Record<string, string[]>;
8
8
  * (build/dev) and are never imported at runtime.
9
9
  */
10
10
  export interface DataStoreWriter {
11
- /** Serialize and persist the given collections. */
12
- write(collections: Map<string, Map<string, any>>): Promise<void>;
11
+ /**
12
+ * Serialize and persist the given collections.
13
+ * Resolves to `true` if the data on disk changed, or `false` if the write
14
+ * was skipped because the persisted data was already identical.
15
+ */
16
+ write(collections: Map<string, Map<string, any>>): Promise<boolean>;
17
+ /**
18
+ * The file whose write commits a store update: the store file itself, or
19
+ * the manifest for chunked stores.
20
+ */
21
+ readonly target: PathLike;
13
22
  }
14
23
  /**
15
24
  * Serialize collections to a deterministic devalue string.
@@ -35,15 +44,18 @@ export declare function chunkString(str: string, maxBytes: number): string[];
35
44
  * partial reads. If the file already contains identical data, the write is
36
45
  * skipped. Callers are responsible for serializing concurrent writes to the
37
46
  * same file.
47
+ *
48
+ * Returns `true` if the file was written, or `false` if the write was skipped.
38
49
  */
39
- export declare function writeFileAtomic(file: PathLike, data: string): Promise<void>;
50
+ export declare function writeFileAtomic(file: PathLike, data: string): Promise<boolean>;
40
51
  /**
41
52
  * A {@link DataStoreWriter} that serializes the whole store to a single file.
42
53
  */
43
54
  export declare class FileWriter implements DataStoreWriter {
44
55
  #private;
45
56
  constructor(file: PathLike);
46
- write(collections: Map<string, Map<string, any>>): Promise<void>;
57
+ get target(): PathLike;
58
+ write(collections: Map<string, Map<string, any>>): Promise<boolean>;
47
59
  }
48
60
  /**
49
61
  * A {@link DataStoreWriter} that splits the store across many content-addressed
@@ -60,5 +72,6 @@ export declare class FileWriter implements DataStoreWriter {
60
72
  export declare class ChunkedWriter implements DataStoreWriter {
61
73
  #private;
62
74
  constructor(dir: URL, chunkSize: number);
63
- write(collections: Map<string, Map<string, any>>): Promise<void>;
75
+ get target(): PathLike;
76
+ write(collections: Map<string, Map<string, any>>): Promise<boolean>;
64
77
  }
@@ -47,18 +47,22 @@ async function writeFileAtomic(file, data) {
47
47
  const tempFile = file instanceof URL ? new URL(`${file.href}.tmp`) : `${file}.tmp`;
48
48
  const oldData = await fs.readFile(file, "utf-8").catch(() => "");
49
49
  if (oldData === data) {
50
- return;
50
+ return false;
51
51
  }
52
52
  await fs.writeFile(tempFile, data);
53
53
  await fs.rename(tempFile, file);
54
+ return true;
54
55
  }
55
56
  class FileWriter {
56
57
  #file;
57
58
  constructor(file) {
58
59
  this.#file = file;
59
60
  }
61
+ get target() {
62
+ return this.#file;
63
+ }
60
64
  async write(collections) {
61
- await writeFileAtomic(this.#file, serializeDataStore(collections));
65
+ return await writeFileAtomic(this.#file, serializeDataStore(collections));
62
66
  }
63
67
  }
64
68
  class ChunkedWriter {
@@ -72,6 +76,9 @@ class ChunkedWriter {
72
76
  this.#manifestFile = new URL(`./${DATA_STORE_MANIFEST_FILE}`, dir);
73
77
  this.#chunkSize = chunkSize;
74
78
  }
79
+ get target() {
80
+ return this.#manifestFile;
81
+ }
75
82
  async write(collections) {
76
83
  if (!this.#hasher) {
77
84
  this.#hasher = await xxhash();
@@ -81,9 +88,10 @@ class ChunkedWriter {
81
88
  for (const [collectionName, entries] of sortCollections(collections)) {
82
89
  manifest[collectionName] = await this.#writeCollection(entries);
83
90
  }
84
- await writeFileAtomic(this.#manifestFile, JSON.stringify(manifest));
91
+ const didWrite = await writeFileAtomic(this.#manifestFile, JSON.stringify(manifest));
85
92
  this.#writtenFiles.add(DATA_STORE_MANIFEST_FILE);
86
93
  emptyDir(this.#dir, this.#writtenFiles);
94
+ return didWrite;
87
95
  }
88
96
  async #writeCollection(entries) {
89
97
  const parts = [];
@@ -3,4 +3,4 @@ export { createContentTypesGenerator } from './types-generator.js';
3
3
  export { getContentPaths } from './utils.js';
4
4
  export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js';
5
5
  export { astroContentImportPlugin } from './vite-plugin-content-imports.js';
6
- export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
6
+ export { astroContentVirtualModPlugin, attachDataStoreInvalidation, } from './vite-plugin-content-virtual-mod.js';
@@ -3,12 +3,16 @@ import { createContentTypesGenerator } from "./types-generator.js";
3
3
  import { getContentPaths } from "./utils.js";
4
4
  import { astroContentAssetPropagationPlugin } from "./vite-plugin-content-assets.js";
5
5
  import { astroContentImportPlugin } from "./vite-plugin-content-imports.js";
6
- import { astroContentVirtualModPlugin } from "./vite-plugin-content-virtual-mod.js";
6
+ import {
7
+ astroContentVirtualModPlugin,
8
+ attachDataStoreInvalidation
9
+ } from "./vite-plugin-content-virtual-mod.js";
7
10
  export {
8
11
  astroContentAssetPropagationPlugin,
9
12
  astroContentImportPlugin,
10
13
  astroContentVirtualModPlugin,
11
14
  attachContentServerListeners,
15
+ attachDataStoreInvalidation,
12
16
  createContentTypesGenerator,
13
17
  getContentPaths
14
18
  };
@@ -1,4 +1,5 @@
1
1
  import { type PathLike } from 'node:fs';
2
+ import type { AstroLogger } from '../core/logger/core.js';
2
3
  import { type DataEntry, ImmutableDataStore } from './data-store.js';
3
4
  /**
4
5
  * Extends the DataStore with the ability to change entries and write them to disk.
@@ -6,6 +7,17 @@ import { type DataEntry, ImmutableDataStore } from './data-store.js';
6
7
  */
7
8
  export declare class MutableDataStore extends ImmutableDataStore {
8
9
  #private;
10
+ /**
11
+ * Registers a listener called with the file path whenever this store writes a
12
+ * file to disk (the data store itself, or the asset/module import files).
13
+ * Writes that are skipped because the data on disk is already identical do
14
+ * not notify. The dev server uses this to invalidate the content virtual
15
+ * modules deterministically, instead of relying on the file watcher to
16
+ * observe the write — on some platforms (notably Windows) the watcher can
17
+ * miss the atomic rename that commits it.
18
+ * Returns a function that removes the listener.
19
+ */
20
+ onFileWritten(listener: (path: string) => void): () => void;
9
21
  set(collectionName: string, key: string, value: unknown): void;
10
22
  delete(collectionName: string, key: string): void;
11
23
  clear(collectionName: string): void;
@@ -42,7 +54,7 @@ export declare class MutableDataStore extends ImmutableDataStore {
42
54
  * manifest exists but can't be read (corrupt cache), it warns and starts
43
55
  * empty so loaders rebuild it, rather than failing the sync.
44
56
  */
45
- static fromDir(dirPath: URL, chunkSize: number): Promise<MutableDataStore>;
57
+ static fromDir(dirPath: URL, chunkSize: number, logger: Pick<AstroLogger, 'warn'>): Promise<MutableDataStore>;
46
58
  }
47
59
  export interface DataStore {
48
60
  get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) => DataEntry<TData> | undefined;
@@ -30,6 +30,32 @@ class MutableDataStore extends ImmutableDataStore {
30
30
  #moduleImports = /* @__PURE__ */ new Map();
31
31
  #writeInProgress = false;
32
32
  #writeQueued = false;
33
+ #fileWrittenListeners = /* @__PURE__ */ new Set();
34
+ /**
35
+ * Registers a listener called with the file path whenever this store writes a
36
+ * file to disk (the data store itself, or the asset/module import files).
37
+ * Writes that are skipped because the data on disk is already identical do
38
+ * not notify. The dev server uses this to invalidate the content virtual
39
+ * modules deterministically, instead of relying on the file watcher to
40
+ * observe the write — on some platforms (notably Windows) the watcher can
41
+ * miss the atomic rename that commits it.
42
+ * Returns a function that removes the listener.
43
+ */
44
+ onFileWritten(listener) {
45
+ this.#fileWrittenListeners.add(listener);
46
+ return () => {
47
+ this.#fileWrittenListeners.delete(listener);
48
+ };
49
+ }
50
+ #notifyFileWritten(path) {
51
+ if (this.#fileWrittenListeners.size === 0) {
52
+ return;
53
+ }
54
+ const normalized = path instanceof URL ? fileURLToPath(path) : path.toString();
55
+ for (const listener of this.#fileWrittenListeners) {
56
+ listener(normalized);
57
+ }
58
+ }
33
59
  set(collectionName, key, value) {
34
60
  const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
35
61
  collection.set(String(key), value);
@@ -275,6 +301,7 @@ ${lines.join(",\n")}]);
275
301
  }
276
302
  await fs.writeFile(tempFile, data);
277
303
  await fs.rename(tempFile, filePath);
304
+ this.#notifyFileWritten(filePath);
278
305
  } finally {
279
306
  this.#writing.delete(fileKey);
280
307
  if (this.#pending.has(fileKey)) {
@@ -415,7 +442,10 @@ ${lines.join(",\n")}]);
415
442
  try {
416
443
  this.#dirty = false;
417
444
  this.#writeInProgress = true;
418
- await this.#writer.write(this._collections);
445
+ const didWrite = await this.#writer.write(this._collections);
446
+ if (didWrite) {
447
+ this.#notifyFileWritten(this.#writer.target);
448
+ }
419
449
  } catch (err) {
420
450
  throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
421
451
  } finally {
@@ -471,7 +501,7 @@ ${lines.join(",\n")}]);
471
501
  * manifest exists but can't be read (corrupt cache), it warns and starts
472
502
  * empty so loaders rebuild it, rather than failing the sync.
473
503
  */
474
- static async fromDir(dirPath, chunkSize) {
504
+ static async fromDir(dirPath, chunkSize, logger) {
475
505
  const manifestFile = new URL(`./${DATA_STORE_MANIFEST_FILE}`, dirPath);
476
506
  if (existsSync(manifestFile)) {
477
507
  try {
@@ -489,9 +519,9 @@ ${lines.join(",\n")}]);
489
519
  store2.#writer = new ChunkedWriter(dirPath, chunkSize);
490
520
  return store2;
491
521
  } catch (err) {
492
- console.warn(
493
- `[content] Could not read the chunked data store at ${fileURLToPath(dirPath)}, rebuilding from scratch.`,
494
- err
522
+ logger.warn(
523
+ "content",
524
+ `Could not read the chunked data store at ${fileURLToPath(dirPath)}, rebuilding from scratch. ${err instanceof Error ? err.message : err}`
495
525
  );
496
526
  }
497
527
  }
@@ -8,14 +8,16 @@ import type { LIVE_CONTENT_TYPE } from './consts.js';
8
8
  import { type DataEntry } from './data-store.js';
9
9
  import { LiveCollectionCacheHintError, LiveCollectionError, LiveCollectionValidationError, LiveEntryNotFoundError } from './loaders/errors.js';
10
10
  import type { LiveLoader } from './loaders/types.js';
11
+ import type { AstroLogger } from '../core/logger/core.js';
11
12
  export { LiveCollectionError, LiveCollectionCacheHintError, LiveEntryNotFoundError, LiveCollectionValidationError, };
12
13
  type LiveCollectionConfigMap = Record<string, {
13
14
  loader: LiveLoader;
14
15
  type: typeof LIVE_CONTENT_TYPE;
15
16
  schema?: zCore.$ZodType;
16
17
  }>;
17
- export declare function createGetCollection({ liveCollections, }: {
18
+ export declare function createGetCollection({ liveCollections, logger, }: {
18
19
  liveCollections: LiveCollectionConfigMap;
20
+ logger: AstroLogger;
19
21
  }): (collection: string, filter?: ((entry: any) => unknown) | Record<string, unknown>) => Promise<{
20
22
  data: Record<string, unknown>;
21
23
  collection: string;
@@ -50,8 +52,9 @@ type EntryLookupObject = {
50
52
  collection: string;
51
53
  slug: string;
52
54
  };
53
- export declare function createGetEntry({ liveCollections }: {
55
+ export declare function createGetEntry({ liveCollections, logger, }: {
54
56
  liveCollections: LiveCollectionConfigMap;
57
+ logger: AstroLogger;
55
58
  }): (collectionOrLookupObject: string | EntryLookupObject, lookup?: string | Record<string, unknown>) => Promise<ContentEntryResult | DataEntryResult | undefined>;
56
59
  export declare function createGetEntries(getEntry: ReturnType<typeof createGetEntry>): (entries: {
57
60
  collection: string;
@@ -73,7 +76,9 @@ type RenderResult = {
73
76
  };
74
77
  export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>, imageImports?: (string | number)[][]): T;
75
78
  export declare function resolveEntryData<T extends Record<string, unknown>>(entry: DataEntry<T>, imageAssetMap?: Map<string, ImageMetadata>): T;
76
- export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
79
+ export declare function createRenderEntry({ logger }: {
80
+ logger: AstroLogger;
81
+ }): (entry: DataEntry) => Promise<RenderResult>;
77
82
  export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<string, number>>, z.ZodString, z.ZodObject<{
78
83
  id: z.ZodString;
79
84
  collection: z.ZodString;
@@ -2,7 +2,7 @@ import { escape } from "html-escaper";
2
2
  import * as z from "zod/v4";
3
3
  import { createSvgComponent } from "../assets/runtime.js";
4
4
  import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
5
- import { recordContentEntryRender } from "../core/build/incremental-content-collector.js";
5
+ import { recordContentEntryRender } from "../core/render-scope/record.js";
6
6
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
7
7
  import { isRemotePath, prependForwardSlash } from "../core/path.js";
8
8
  import {
@@ -61,7 +61,8 @@ async function parseLiveEntry(entry, schema, collection) {
61
61
  }
62
62
  }
63
63
  function createGetCollection({
64
- liveCollections
64
+ liveCollections,
65
+ logger
65
66
  }) {
66
67
  return async function getCollection(collection, filter) {
67
68
  if (collection in liveCollections) {
@@ -89,7 +90,8 @@ function createGetCollection({
89
90
  }
90
91
  return result;
91
92
  } else {
92
- console.warn(
93
+ logger.warn(
94
+ "content",
93
95
  `The collection ${JSON.stringify(
94
96
  collection
95
97
  )} does not exist or is empty. Please check your content config file for errors.`
@@ -98,7 +100,10 @@ function createGetCollection({
98
100
  }
99
101
  };
100
102
  }
101
- function createGetEntry({ liveCollections }) {
103
+ function createGetEntry({
104
+ liveCollections,
105
+ logger
106
+ }) {
102
107
  return async function getEntry(collectionOrLookupObject, lookup) {
103
108
  let collection, lookupId;
104
109
  if (typeof collectionOrLookupObject === "string") {
@@ -129,7 +134,7 @@ function createGetEntry({ liveCollections }) {
129
134
  if (await store.hasCollection(collection)) {
130
135
  const entry = await store.get(collection, lookupId);
131
136
  if (!entry) {
132
- console.warn(`Entry ${collection} \u2192 ${lookupId} was not found.`);
137
+ logger.warn("content", `Entry ${collection} \u2192 ${lookupId} was not found.`);
133
138
  return;
134
139
  }
135
140
  const { default: imageAssetMap } = await import("astro:asset-imports");
@@ -140,12 +145,14 @@ function createGetEntry({ liveCollections }) {
140
145
  collection
141
146
  };
142
147
  warnForPropertyAccess(
148
+ logger,
143
149
  result.data,
144
150
  "slug",
145
151
  `[content] Attempted to access deprecated property on "${collection}" entry.
146
152
  The "slug" property is no longer automatically added to entries. Please use the "id" property instead.`
147
153
  );
148
154
  warnForPropertyAccess(
155
+ logger,
149
156
  result,
150
157
  "render",
151
158
  `[content] Invalid attempt to access "render()" method on "${collection}" entry.
@@ -156,13 +163,13 @@ To render an entry, use "render(entry)" from "astro:content".`
156
163
  return void 0;
157
164
  };
158
165
  }
159
- function warnForPropertyAccess(entry, prop, message) {
166
+ function warnForPropertyAccess(logger, entry, prop, message) {
160
167
  if (!(prop in entry)) {
161
168
  let _value = void 0;
162
169
  Object.defineProperty(entry, prop, {
163
170
  get() {
164
171
  if (_value === void 0) {
165
- console.error(message);
172
+ logger.error("content", message);
166
173
  }
167
174
  return _value;
168
175
  },
@@ -349,7 +356,9 @@ async function updateImageReferencesInBody(html, fileName) {
349
356
  return Object.entries({
350
357
  ...attributes,
351
358
  src: image.src,
352
- srcset: image.srcSet.attribute,
359
+ // An empty `srcset` is invalid HTML, so only emit it when there are
360
+ // actual candidates. This matches `vite-plugin-markdown/images.ts`.
361
+ ...image.srcSet.values.length > 0 ? { srcset: image.srcSet.attribute } : {},
353
362
  // This attribute is used by the toolbar audit
354
363
  ...import.meta.env.DEV ? { "data-image-component": "true" } : {}
355
364
  }).filter(([, value]) => value != null).map(([key, value]) => value === "" ? `${key}=""` : `${key}="${escape(String(value))}"`).join(" ");
@@ -402,30 +411,32 @@ function updateImageReferencesInData(data, fileName, imageAssetMap, imageImports
402
411
  function resolveEntryData(entry, imageAssetMap) {
403
412
  return updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap, entry.imageImports);
404
413
  }
405
- async function renderEntry(entry) {
406
- if (!entry) {
407
- throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
408
- }
409
- recordContentEntryRender(entry.filePath);
410
- if (entry.deferredRender) {
411
- try {
412
- const { default: contentModules } = await import("astro:content-module-imports");
413
- const renderEntryImport = contentModules.get(entry.filePath);
414
- return render({
415
- collection: "",
416
- id: entry.id,
417
- renderEntryImport
418
- });
419
- } catch (e) {
420
- console.error(e);
414
+ function createRenderEntry({ logger }) {
415
+ return async function renderEntry(entry) {
416
+ if (!entry) {
417
+ throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
421
418
  }
422
- }
423
- const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) : entry?.rendered?.html;
424
- const Content = createComponent(() => serverRender`${unescapeHTML(html)}`);
425
- return {
426
- Content,
427
- headings: entry?.rendered?.metadata?.headings ?? [],
428
- remarkPluginFrontmatter: entry?.rendered?.metadata?.frontmatter ?? {}
419
+ recordContentEntryRender(entry.filePath);
420
+ if (entry.deferredRender) {
421
+ try {
422
+ const { default: contentModules } = await import("astro:content-module-imports");
423
+ const renderEntryImport = contentModules.get(entry.filePath);
424
+ return render({
425
+ collection: "",
426
+ id: entry.id,
427
+ renderEntryImport
428
+ });
429
+ } catch (e) {
430
+ logger.error("content", `${e}`);
431
+ }
432
+ }
433
+ const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) : entry?.rendered?.html;
434
+ const Content = createComponent(() => serverRender`${unescapeHTML(html)}`);
435
+ return {
436
+ Content,
437
+ headings: entry?.rendered?.metadata?.headings ?? [],
438
+ remarkPluginFrontmatter: entry?.rendered?.metadata?.frontmatter ?? {}
439
+ };
429
440
  };
430
441
  }
431
442
  async function render({
@@ -580,9 +591,9 @@ export {
580
591
  createGetLiveCollection,
581
592
  createGetLiveEntry,
582
593
  createReference,
594
+ createRenderEntry,
583
595
  defineCollection,
584
596
  defineLiveCollection,
585
- renderEntry,
586
597
  resolveEntryData,
587
598
  updateImageReferencesInData
588
599
  };
@@ -85,7 +85,8 @@ async function createContentTypesGenerator({
85
85
  await reloadContentConfigObserver({
86
86
  fs,
87
87
  settings,
88
- environment: viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro]
88
+ environment: viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro],
89
+ logger
89
90
  });
90
91
  return { shouldGenerateTypes: true };
91
92
  }
@@ -12,38 +12,7 @@ export declare const loaderReturnSchema: z.ZodUnion<readonly [z.ZodArray<z.ZodOb
12
12
  }, z.core.$loose>>, z.ZodRecord<z.ZodString, z.ZodObject<{
13
13
  id: z.ZodOptional<z.ZodString>;
14
14
  }, z.core.$loose>>]>;
15
- declare const collectionConfigParser: z.ZodUnion<readonly [z.ZodObject<{
16
- type: z.ZodOptional<z.ZodLiteral<"content">>;
17
- schema: z.ZodOptional<z.ZodAny>;
18
- loader: z.ZodOptional<z.ZodNever>;
19
- }, z.core.$strip>, z.ZodObject<{
20
- type: z.ZodOptional<z.ZodLiteral<"data">>;
21
- schema: z.ZodOptional<z.ZodAny>;
22
- loader: z.ZodOptional<z.ZodNever>;
23
- }, z.core.$strip>, z.ZodObject<{
24
- type: z.ZodLiteral<"content_layer">;
25
- schema: z.ZodOptional<z.ZodAny>;
26
- loader: z.ZodUnion<readonly [z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>, z.ZodObject<{
27
- name: z.ZodString;
28
- load: z.ZodFunction<z.ZodTuple<readonly [z.ZodCustom<LoaderContext, LoaderContext>], null>, z.ZodCustom<void | {
29
- schema?: any;
30
- types?: string;
31
- }, void | {
32
- schema?: any;
33
- types?: string;
34
- }>>;
35
- schema: z.ZodOptional<z.ZodPipe<z.ZodAny, z.ZodTransform<any, any>>>;
36
- createSchema: z.ZodOptional<z.ZodFunction<z.ZodTuple<readonly [], null>, z.ZodPromise<z.ZodObject<{
37
- schema: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
38
- types: z.ZodString;
39
- }, z.core.$strip>>>>;
40
- }, z.core.$strip>]>;
41
- }, z.core.$strip>, z.ZodObject<{
42
- type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"live">>>;
43
- schema: z.ZodOptional<z.ZodAny>;
44
- loader: z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>;
45
- }, z.core.$strip>]>;
46
- declare const contentConfigParser: z.ZodObject<{
15
+ declare function contentConfigParser(logger: AstroLogger): z.ZodObject<{
47
16
  collections: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
48
17
  type: z.ZodOptional<z.ZodLiteral<"content">>;
49
18
  schema: z.ZodOptional<z.ZodAny>;
@@ -66,7 +35,7 @@ declare const contentConfigParser: z.ZodObject<{
66
35
  }>>;
67
36
  schema: z.ZodOptional<z.ZodPipe<z.ZodAny, z.ZodTransform<any, any>>>;
68
37
  createSchema: z.ZodOptional<z.ZodFunction<z.ZodTuple<readonly [], null>, z.ZodPromise<z.ZodObject<{
69
- schema: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
38
+ schema: z.ZodCustom<z.ZodSchema<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodSchema<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
70
39
  types: z.ZodString;
71
40
  }, z.core.$strip>>>>;
72
41
  }, z.core.$strip>]>;
@@ -76,10 +45,10 @@ declare const contentConfigParser: z.ZodObject<{
76
45
  loader: z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>;
77
46
  }, z.core.$strip>]>>;
78
47
  }, z.core.$strip>;
79
- export type CollectionConfig = z.infer<typeof collectionConfigParser>;
80
- export type ContentConfig = z.infer<typeof contentConfigParser> & {
48
+ export type ContentConfig = z.infer<ReturnType<typeof contentConfigParser>> & {
81
49
  digest?: string;
82
50
  };
51
+ export type CollectionConfig = ContentConfig['collections'][string];
83
52
  type EntryInternal = {
84
53
  rawData: string | undefined;
85
54
  filePath: string;
@@ -138,6 +107,7 @@ export declare function reloadContentConfigObserver({ observer, ...loadContentCo
138
107
  settings: AstroSettings;
139
108
  environment: RunnableDevEnvironment;
140
109
  observer?: ContentObservable;
110
+ logger: AstroLogger;
141
111
  }): Promise<void>;
142
112
  type ContentCtx = {
143
113
  status: 'init';