astro 7.1.6 → 7.2.1

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 (122) hide show
  1. package/dist/assets/build/generate.d.ts +8 -1
  2. package/dist/assets/build/generate.js +21 -1
  3. package/dist/assets/fonts/providers/index.js +12 -2
  4. package/dist/assets/types.d.ts +13 -0
  5. package/dist/assets/utils/resolveImports.d.ts +0 -1
  6. package/dist/assets/utils/resolveImports.js +1 -4
  7. package/dist/assets/vite-plugin-assets.js +8 -0
  8. package/dist/cli/add/index.js +1 -1
  9. package/dist/cli/agent.d.ts +1 -0
  10. package/dist/cli/agent.js +11 -0
  11. package/dist/cli/dev/background.d.ts +3 -16
  12. package/dist/cli/dev/background.js +7 -93
  13. package/dist/cli/dev/index.d.ts +12 -0
  14. package/dist/cli/dev/index.js +30 -23
  15. package/dist/cli/dev/logs.js +2 -64
  16. package/dist/cli/dev/status.d.ts +2 -9
  17. package/dist/cli/dev/status.js +6 -24
  18. package/dist/cli/dev/stop.d.ts +2 -6
  19. package/dist/cli/dev/stop.js +6 -14
  20. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  21. package/dist/cli/install-package.js +1 -1
  22. package/dist/cli/preview/index.js +77 -3
  23. package/dist/cli/server.d.ts +60 -0
  24. package/dist/cli/server.js +274 -0
  25. package/dist/content/content-layer.js +63 -3
  26. package/dist/content/loaders/file.js +1 -1
  27. package/dist/content/loaders/glob.js +3 -2
  28. package/dist/content/mutable-data-store.js +3 -3
  29. package/dist/content/runtime.d.ts +3 -0
  30. package/dist/content/runtime.js +8 -2
  31. package/dist/content/types-generator.js +1 -0
  32. package/dist/content/vite-plugin-content-assets.js +6 -1
  33. package/dist/content/vite-plugin-content-imports.js +12 -3
  34. package/dist/content/vite-plugin-content-virtual-mod.js +28 -16
  35. package/dist/core/app/entrypoints/index.d.ts +2 -0
  36. package/dist/core/app/entrypoints/index.js +14 -0
  37. package/dist/core/app/types.d.ts +2 -0
  38. package/dist/core/build/config-hash/index.d.ts +8 -0
  39. package/dist/core/build/config-hash/index.js +23 -0
  40. package/dist/core/build/config-hash/input.d.ts +199 -0
  41. package/dist/core/build/config-hash/input.js +43 -0
  42. package/dist/core/build/generate.d.ts +3 -1
  43. package/dist/core/build/generate.js +149 -14
  44. package/dist/core/build/incremental-content-collector.d.ts +10 -0
  45. package/dist/core/build/incremental-content-collector.js +33 -0
  46. package/dist/core/build/incremental-image-collector.d.ts +11 -0
  47. package/dist/core/build/incremental-image-collector.js +32 -0
  48. package/dist/core/build/incremental-metadata.d.ts +9 -0
  49. package/dist/core/build/incremental-metadata.js +17 -0
  50. package/dist/core/build/incremental.d.ts +123 -0
  51. package/dist/core/build/incremental.js +178 -0
  52. package/dist/core/build/index.d.ts +3 -0
  53. package/dist/core/build/index.js +6 -2
  54. package/dist/core/build/internal.d.ts +19 -0
  55. package/dist/core/build/lockfile/finder.d.ts +22 -0
  56. package/dist/core/build/lockfile/finder.js +45 -0
  57. package/dist/core/build/lockfile/hasher.d.ts +16 -0
  58. package/dist/core/build/lockfile/hasher.js +46 -0
  59. package/dist/core/build/lockfile/index.d.ts +9 -0
  60. package/dist/core/build/lockfile/index.js +16 -0
  61. package/dist/core/build/plugins/index.js +3 -1
  62. package/dist/core/build/plugins/plugin-incremental.d.ts +14 -0
  63. package/dist/core/build/plugins/plugin-incremental.js +152 -0
  64. package/dist/core/build/plugins/plugin-manifest.js +11 -2
  65. package/dist/core/build/types.d.ts +2 -0
  66. package/dist/core/config/schemas/base.d.ts +3 -2
  67. package/dist/core/config/schemas/base.js +1 -0
  68. package/dist/core/config/schemas/defaults.d.ts +1 -0
  69. package/dist/core/config/schemas/defaults.js +1 -0
  70. package/dist/core/config/schemas/relative.d.ts +7 -4
  71. package/dist/core/config/settings.js +1 -1
  72. package/dist/core/constants.js +1 -1
  73. package/dist/core/create-vite.js +2 -1
  74. package/dist/core/dev/dev.js +1 -1
  75. package/dist/core/dev/lockfile.d.ts +8 -7
  76. package/dist/core/dev/lockfile.js +13 -13
  77. package/dist/core/encryption.d.ts +6 -0
  78. package/dist/core/encryption.js +14 -2
  79. package/dist/core/errors/default-handler.js +4 -5
  80. package/dist/core/fetch/fetch-state.js +1 -0
  81. package/dist/core/fetch/index.js +1 -1
  82. package/dist/core/logger/config.d.ts +1 -1
  83. package/dist/core/logger/load.d.ts +3 -1
  84. package/dist/core/logger/load.js +3 -3
  85. package/dist/core/logger/utils.d.ts +6 -4
  86. package/dist/core/logger/utils.js +13 -5
  87. package/dist/core/logger/vite-plugin.js +1 -1
  88. package/dist/core/messages/runtime.js +1 -1
  89. package/dist/core/middleware/vite-plugin.d.ts +1 -1
  90. package/dist/core/middleware/vite-plugin.js +11 -24
  91. package/dist/core/preview/static-preview-server.d.ts +2 -0
  92. package/dist/core/preview/static-preview-server.js +1 -0
  93. package/dist/core/routing/dev.d.ts +3 -1
  94. package/dist/core/routing/dev.js +10 -2
  95. package/dist/core/routing/handler.js +1 -1
  96. package/dist/core/session/config.d.ts +2 -2
  97. package/dist/core/session/config.js +2 -1
  98. package/dist/core/session/provider-disabled.d.ts +2 -0
  99. package/dist/core/session/provider-disabled.js +7 -0
  100. package/dist/core/session/provider.d.ts +1 -0
  101. package/dist/core/session/provider.js +4 -0
  102. package/dist/core/session/utils.js +3 -0
  103. package/dist/core/session/vite-plugin.d.ts +3 -0
  104. package/dist/core/session/vite-plugin.js +41 -6
  105. package/dist/integrations/hooks.js +3 -1
  106. package/dist/prefetch/index.js +18 -1
  107. package/dist/prefetch/speculation-rules.d.ts +6 -0
  108. package/dist/prefetch/speculation-rules.js +22 -0
  109. package/dist/runtime/prerender/static-paths.js +5 -1
  110. package/dist/runtime/server/render/head.js +10 -0
  111. package/dist/runtime/server/render/util.js +3 -0
  112. package/dist/transitions/swap-functions.js +49 -4
  113. package/dist/types/public/common.d.ts +1 -0
  114. package/dist/types/public/config.d.ts +81 -9
  115. package/dist/types/public/integrations.d.ts +29 -2
  116. package/dist/types/public/internal.d.ts +2 -0
  117. package/dist/types/public/preview.d.ts +2 -0
  118. package/dist/vite-plugin-app/app.d.ts +3 -1
  119. package/dist/vite-plugin-app/app.js +4 -3
  120. package/dist/vite-plugin-config-alias/index.js +1 -0
  121. package/dist/vite-plugin-environment/index.js +4 -1
  122. package/package.json +1 -2
@@ -1,14 +1,25 @@
1
1
  import colors from "piccolore";
2
+ import { pathToFileURL } from "node:url";
3
+ import { checkExistingServer, removeLockFile, writeLockFile } from "../../core/dev/lockfile.js";
4
+ import { resolveRoot } from "../../core/config/config.js";
2
5
  import { printHelp } from "../../core/messages/runtime.js";
3
6
  import previewServer from "../../core/preview/index.js";
4
- import { flagsToAstroInlineConfig } from "../flags.js";
7
+ import { isRunByAgent } from "../agent.js";
8
+ import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
9
+ import { background, logs, previewServerCommand, status, stop } from "../server.js";
5
10
  async function preview({ flags }) {
6
11
  if (flags?.help || flags?.h) {
7
12
  printHelp({
8
13
  commandName: "astro preview",
9
- usage: "[...flags]",
14
+ usage: "[command] [...flags]",
10
15
  tables: {
16
+ Commands: [
17
+ ["stop", "Stop a running background preview server."],
18
+ ["status", "Check if a preview server is running."],
19
+ ["logs [--follow]", "View logs from a background preview server."]
20
+ ],
11
21
  Flags: [
22
+ ["--background", "Start the preview server as a background process."],
12
23
  ["--port", `Specify which port to run on. Defaults to 4321.`],
13
24
  ["--host", `Listen on all addresses, including LAN and public addresses.`],
14
25
  ["--host <custom-address>", `Expose on a network IP address at <custom-address>`],
@@ -26,8 +37,71 @@ async function preview({ flags }) {
26
37
  });
27
38
  return;
28
39
  }
40
+ const agentDetected = !process.env.ASTRO_PREVIEW_BACKGROUND && isRunByAgent();
41
+ if (agentDetected) {
42
+ flags.json = true;
43
+ }
44
+ const logger = createLoggerFromFlags(flags);
45
+ const subcommand = flags._[3]?.toString();
46
+ if (subcommand === "stop") {
47
+ await stop({ flags, logger, config: previewServerCommand });
48
+ return;
49
+ }
50
+ if (subcommand === "status") {
51
+ await status({ flags, logger, config: previewServerCommand });
52
+ return;
53
+ }
54
+ if (subcommand === "logs") {
55
+ await logs({ flags, logger, config: previewServerCommand });
56
+ return;
57
+ }
58
+ if (flags.background || agentDetected) {
59
+ await background({ flags, logger, config: previewServerCommand });
60
+ return;
61
+ }
62
+ if (subcommand) {
63
+ logger.error(
64
+ "SKIP_FORMAT",
65
+ `Unknown command: astro preview ${subcommand}
66
+
67
+ Run \`astro preview --help\` to see available commands.`
68
+ );
69
+ process.exit(1);
70
+ }
71
+ const root = pathToFileURL(resolveRoot(flags.root) + "/");
72
+ const existingServer = checkExistingServer(root, "preview");
73
+ if (existingServer) {
74
+ const message = [
75
+ "Another astro preview server is already running.",
76
+ "",
77
+ ` URL: ${existingServer.url}`,
78
+ ` PID: ${existingServer.pid}`,
79
+ "",
80
+ `Run \`astro preview stop\` to stop it, or use \`astro preview --force\` to replace it.`
81
+ ].join("\n");
82
+ throw new Error(message);
83
+ }
29
84
  const inlineConfig = flagsToAstroInlineConfig(flags);
30
- return await previewServer(inlineConfig);
85
+ const server = await previewServer(inlineConfig);
86
+ const serverUrl = server.urls?.local[0] ? new URL(server.urls.local[0]).origin : `http://${server.host ?? "localhost"}:${server.port}`;
87
+ writeLockFile(
88
+ root,
89
+ {
90
+ pid: process.pid,
91
+ port: server.port,
92
+ url: serverUrl,
93
+ urls: server.urls,
94
+ background: !!process.env.ASTRO_PREVIEW_BACKGROUND,
95
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
96
+ },
97
+ "preview"
98
+ );
99
+ const originalStop = server.stop.bind(server);
100
+ server.stop = async () => {
101
+ removeLockFile(root, "preview");
102
+ await originalStop();
103
+ };
104
+ return server;
31
105
  }
32
106
  export {
33
107
  preview
@@ -0,0 +1,60 @@
1
+ import { type LockFileData, type ServerCommand } from '../core/dev/lockfile.js';
2
+ import type { AstroLogger } from '../core/logger/core.js';
3
+ import type { Flags } from './flags.js';
4
+ export interface BackgroundCommandConfig {
5
+ command: ServerCommand;
6
+ displayName: string;
7
+ envVar: 'ASTRO_DEV_BACKGROUND' | 'ASTRO_PREVIEW_BACKGROUND';
8
+ }
9
+ export declare const devServerCommand: BackgroundCommandConfig;
10
+ export declare const previewServerCommand: BackgroundCommandConfig;
11
+ export interface BackgroundResult {
12
+ pid: number;
13
+ url: string;
14
+ existing?: boolean;
15
+ }
16
+ export interface BackgroundErrorResult {
17
+ error: string;
18
+ message: string;
19
+ }
20
+ export interface StopResult {
21
+ stopped: boolean;
22
+ pid?: number;
23
+ reason?: string;
24
+ }
25
+ export interface StatusResult {
26
+ running: boolean;
27
+ pid?: number;
28
+ url?: string;
29
+ port?: number;
30
+ background?: boolean;
31
+ uptime?: number;
32
+ }
33
+ export declare function formatBackgroundOutput(result: BackgroundResult | BackgroundErrorResult): string;
34
+ export declare function formatStopOutput(result: StopResult): string;
35
+ export declare function formatStatusOutput(result: StatusResult): string;
36
+ export declare function formatServerRunningMessage(data: LockFileData, config: BackgroundCommandConfig, { existing }?: {
37
+ existing?: boolean;
38
+ }): string;
39
+ export declare function buildBackgroundArgs(command: ServerCommand, flags: Flags): string[];
40
+ export declare function stopExistingServer(root: URL, command: ServerCommand, pid: number): Promise<void>;
41
+ export declare function background({ flags, logger, config, }: {
42
+ flags: Flags;
43
+ logger: AstroLogger;
44
+ config: BackgroundCommandConfig;
45
+ }): Promise<void>;
46
+ export declare function stop({ flags, logger, config, }: {
47
+ flags: Flags;
48
+ logger: AstroLogger;
49
+ config: BackgroundCommandConfig;
50
+ }): Promise<void>;
51
+ export declare function status({ flags, logger, config, }: {
52
+ flags: Flags;
53
+ logger: AstroLogger;
54
+ config: BackgroundCommandConfig;
55
+ }): Promise<void>;
56
+ export declare function logs({ flags, logger, config, }: {
57
+ flags: Flags;
58
+ logger: AstroLogger;
59
+ config: BackgroundCommandConfig;
60
+ }): Promise<void>;
@@ -0,0 +1,274 @@
1
+ import { spawn } from "node:child_process";
2
+ import {
3
+ createReadStream,
4
+ existsSync,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync,
8
+ statSync,
9
+ watch
10
+ } from "node:fs";
11
+ import { createRequire } from "node:module";
12
+ import { dirname, resolve } from "node:path";
13
+ import { fileURLToPath, pathToFileURL } from "node:url";
14
+ import { resolveRoot } from "../core/config/config.js";
15
+ import {
16
+ GRACEFUL_SHUTDOWN_TIMEOUT,
17
+ checkExistingServer,
18
+ getLogFileURL,
19
+ isProcessAlive,
20
+ readLockFile,
21
+ removeLockFile
22
+ } from "../core/dev/lockfile.js";
23
+ const require2 = createRequire(import.meta.url);
24
+ const devServerCommand = {
25
+ command: "dev",
26
+ displayName: "Dev server",
27
+ envVar: "ASTRO_DEV_BACKGROUND"
28
+ };
29
+ const previewServerCommand = {
30
+ command: "preview",
31
+ displayName: "Preview server",
32
+ envVar: "ASTRO_PREVIEW_BACKGROUND"
33
+ };
34
+ function formatBackgroundOutput(result) {
35
+ return JSON.stringify(result);
36
+ }
37
+ function formatStopOutput(result) {
38
+ return JSON.stringify(result);
39
+ }
40
+ function formatStatusOutput(result) {
41
+ return JSON.stringify(result);
42
+ }
43
+ function formatServerRunningMessage(data, config, { existing = false } = {}) {
44
+ const command = `astro ${config.command}`;
45
+ const lines = [
46
+ `${config.displayName} ${existing ? "already running" : "running"} at ${data.url} (pid ${data.pid})`
47
+ ];
48
+ if (data.urls && data.urls.network.length > 0) {
49
+ lines.push(" Network:");
50
+ for (const url of data.urls.network) {
51
+ lines.push(` ${url}`);
52
+ }
53
+ }
54
+ lines.push(
55
+ ` Stop: ${command} stop`,
56
+ ` Status: ${command} status`,
57
+ ` Logs: ${command} logs`
58
+ );
59
+ return lines.join("\n");
60
+ }
61
+ function getRootURL(flags) {
62
+ return pathToFileURL(resolveRoot(flags.root) + "/");
63
+ }
64
+ function buildBackgroundArgs(command, flags) {
65
+ const args = [command];
66
+ if (flags.port) args.push("--port", String(flags.port));
67
+ if (flags.host != null) {
68
+ if (typeof flags.host === "string") {
69
+ args.push("--host", flags.host);
70
+ } else {
71
+ args.push("--host");
72
+ }
73
+ }
74
+ if (flags.config) args.push("--config", String(flags.config));
75
+ if (flags.root) args.push("--root", String(flags.root));
76
+ if (flags.allowedHosts) args.push("--allowed-hosts", String(flags.allowedHosts));
77
+ if (flags.json) args.push("--json");
78
+ return args;
79
+ }
80
+ async function stopExistingServer(root, command, pid) {
81
+ try {
82
+ process.kill(pid, "SIGTERM");
83
+ } catch {
84
+ }
85
+ const deadline = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT;
86
+ while (Date.now() < deadline) {
87
+ if (!isProcessAlive(pid)) break;
88
+ await new Promise((r) => setTimeout(r, 100));
89
+ }
90
+ if (isProcessAlive(pid)) {
91
+ try {
92
+ process.kill(pid, "SIGKILL");
93
+ } catch {
94
+ }
95
+ }
96
+ removeLockFile(root, command);
97
+ }
98
+ async function background({
99
+ flags,
100
+ logger,
101
+ config
102
+ }) {
103
+ const root = getRootURL(flags);
104
+ const existing = checkExistingServer(root, config.command);
105
+ if (existing && !flags.force) {
106
+ logger.info("SKIP_FORMAT", formatServerRunningMessage(existing, config, { existing: true }));
107
+ return;
108
+ }
109
+ if (existing && flags.force) {
110
+ await stopExistingServer(root, config.command, existing.pid);
111
+ }
112
+ const args = buildBackgroundArgs(config.command, flags);
113
+ const logFileURL = getLogFileURL(root, config.command);
114
+ const logFilePath = fileURLToPath(logFileURL);
115
+ const dotAstroDir = fileURLToPath(new URL(".astro/", root));
116
+ if (!existsSync(dotAstroDir)) {
117
+ mkdirSync(dotAstroDir, { recursive: true });
118
+ }
119
+ const logFd = openSync(logFilePath, "w");
120
+ const rootPath = fileURLToPath(root);
121
+ const astroBin = resolve(dirname(require2.resolve("astro/package.json")), "bin", "astro.mjs");
122
+ const child = spawn(process.execPath, [astroBin, ...args], {
123
+ detached: true,
124
+ windowsHide: true,
125
+ stdio: ["ignore", logFd, logFd],
126
+ cwd: rootPath,
127
+ env: { ...process.env, [config.envVar]: "1" }
128
+ });
129
+ child.unref();
130
+ const childPid = child.pid;
131
+ if (!childPid) {
132
+ logger.error("SKIP_FORMAT", `Failed to spawn background ${config.command} server process.`);
133
+ process.exit(1);
134
+ }
135
+ const timeout = 3e4;
136
+ const deadline = Date.now() + timeout;
137
+ while (Date.now() < deadline) {
138
+ if (!isProcessAlive(childPid)) {
139
+ logger.error("SKIP_FORMAT", `${config.displayName} process exited before becoming ready.`);
140
+ process.exit(1);
141
+ }
142
+ const lockData = readLockFile(root, config.command);
143
+ if (lockData && lockData.pid === childPid) {
144
+ logger.info("SKIP_FORMAT", formatServerRunningMessage(lockData, config));
145
+ return;
146
+ }
147
+ await new Promise((r) => setTimeout(r, 200));
148
+ }
149
+ try {
150
+ process.kill(childPid, "SIGTERM");
151
+ } catch {
152
+ }
153
+ removeLockFile(root, config.command);
154
+ logger.error("SKIP_FORMAT", `${config.displayName} failed to start within ${timeout / 1e3}s.`);
155
+ process.exit(1);
156
+ }
157
+ async function stop({
158
+ flags,
159
+ logger,
160
+ config
161
+ }) {
162
+ const root = getRootURL(flags);
163
+ const existing = checkExistingServer(root, config.command);
164
+ if (!existing) {
165
+ logger.info("SKIP_FORMAT", `No ${config.command} server is running.`);
166
+ return;
167
+ }
168
+ await stopExistingServer(root, config.command, existing.pid);
169
+ logger.info("SKIP_FORMAT", `Stopped ${config.command} server (pid ${existing.pid}).`);
170
+ }
171
+ async function status({
172
+ flags,
173
+ logger,
174
+ config
175
+ }) {
176
+ const root = getRootURL(flags);
177
+ const existing = checkExistingServer(root, config.command);
178
+ if (!existing) {
179
+ logger.info("SKIP_FORMAT", `No ${config.command} server is running.`);
180
+ return;
181
+ }
182
+ const startedAt = new Date(existing.startedAt).getTime();
183
+ const uptime = Math.floor((Date.now() - startedAt) / 1e3);
184
+ const lines = [
185
+ `${config.displayName} running at ${existing.url} (pid ${existing.pid}, uptime ${uptime}s${existing.background ? ", background" : ""})`
186
+ ];
187
+ if (existing.urls && existing.urls.network.length > 0) {
188
+ lines.push(" Network:");
189
+ for (const url of existing.urls.network) {
190
+ lines.push(` ${url}`);
191
+ }
192
+ }
193
+ logger.info("SKIP_FORMAT", lines.join("\n"));
194
+ }
195
+ async function logs({
196
+ flags,
197
+ logger,
198
+ config
199
+ }) {
200
+ const root = getRootURL(flags);
201
+ const existing = checkExistingServer(root, config.command);
202
+ if (!existing) {
203
+ logger.error("SKIP_FORMAT", `No ${config.command} server is running.`);
204
+ process.exit(1);
205
+ }
206
+ if (!existing.background) {
207
+ logger.error(
208
+ "SKIP_FORMAT",
209
+ `The running ${config.command} server was not started with \`astro ${config.command} --background\`. View logs in the terminal where it was started.`
210
+ );
211
+ process.exit(1);
212
+ }
213
+ const logFileURL = getLogFileURL(root, config.command);
214
+ const logFilePath = fileURLToPath(logFileURL);
215
+ if (!existsSync(logFilePath)) {
216
+ logger.error("SKIP_FORMAT", "No log file found.");
217
+ process.exit(1);
218
+ }
219
+ const follow = flags.follow || flags.f;
220
+ if (!follow) {
221
+ const content = readFileSync(logFilePath, "utf-8");
222
+ process.stdout.write(content);
223
+ return;
224
+ }
225
+ let offset = statSync(logFilePath).size;
226
+ if (offset > 0) {
227
+ const content = readFileSync(logFilePath, "utf-8");
228
+ process.stdout.write(content);
229
+ }
230
+ const watcher = watch(logFilePath, () => {
231
+ const currentSize = statSync(logFilePath).size;
232
+ if (currentSize > offset) {
233
+ const stream = createReadStream(logFilePath, { start: offset, encoding: "utf-8" });
234
+ stream.on("data", (chunk) => process.stdout.write(chunk));
235
+ stream.on("end", () => {
236
+ offset = currentSize;
237
+ });
238
+ }
239
+ });
240
+ const aliveCheck = setInterval(() => {
241
+ if (!isProcessAlive(existing.pid)) {
242
+ const currentSize = statSync(logFilePath).size;
243
+ if (currentSize > offset) {
244
+ const remaining = readFileSync(logFilePath, { encoding: "utf-8" }).slice(offset);
245
+ process.stdout.write(remaining);
246
+ }
247
+ watcher.close();
248
+ clearInterval(aliveCheck);
249
+ }
250
+ }, 1e3);
251
+ const cleanup = () => {
252
+ watcher.close();
253
+ clearInterval(aliveCheck);
254
+ process.exit(0);
255
+ };
256
+ process.on("SIGINT", cleanup);
257
+ process.on("SIGTERM", cleanup);
258
+ await new Promise(() => {
259
+ });
260
+ }
261
+ export {
262
+ background,
263
+ buildBackgroundArgs,
264
+ devServerCommand,
265
+ formatBackgroundOutput,
266
+ formatServerRunningMessage,
267
+ formatStatusOutput,
268
+ formatStopOutput,
269
+ logs,
270
+ previewServerCommand,
271
+ status,
272
+ stop,
273
+ stopExistingServer
274
+ };
@@ -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.1.6") {
199
+ if (previousAstroVersion && previousAstroVersion !== "7.2.1") {
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.1.6") {
208
- this.#store.metaStore().set("astro-version", "7.1.6");
207
+ if ("7.2.1") {
208
+ this.#store.metaStore().set("astro-version", "7.2.1");
209
209
  }
210
210
  if (currentConfigDigest) {
211
211
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -265,6 +265,7 @@ ${contentConfig.error.message}`
265
265
  }
266
266
  })
267
267
  );
268
+ this.#validateReferences(contentConfig.config.collections, logger);
268
269
  await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
269
270
  await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
270
271
  const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
@@ -277,6 +278,65 @@ ${contentConfig.error.message}`
277
278
  await this.regenerateCollectionFileManifest();
278
279
  }
279
280
  }
281
+ /**
282
+ * After all loaders complete, walks every entry's data to find reference objects
283
+ * (`{ id, collection }`) and checks that the referenced entry exists in the store.
284
+ * This replaces the inline Zod validation that was removed in the Zod 4 upgrade.
285
+ */
286
+ #validateReferences(collections, logger) {
287
+ const collectionNames = new Set(Object.keys(collections));
288
+ for (const collectionName of collectionNames) {
289
+ for (const entry of this.#store.values(collectionName)) {
290
+ if (entry?.data) {
291
+ this.#findInvalidReferences(
292
+ entry.data,
293
+ collectionNames,
294
+ collectionName,
295
+ entry.id,
296
+ logger,
297
+ ""
298
+ );
299
+ }
300
+ }
301
+ }
302
+ }
303
+ #findInvalidReferences(value, collectionNames, ownerCollection, ownerId, logger, path) {
304
+ if (value == null || typeof value !== "object") return;
305
+ if (Array.isArray(value)) {
306
+ for (let i = 0; i < value.length; i++) {
307
+ this.#findInvalidReferences(
308
+ value[i],
309
+ collectionNames,
310
+ ownerCollection,
311
+ ownerId,
312
+ logger,
313
+ `${path}[${i}]`
314
+ );
315
+ }
316
+ return;
317
+ }
318
+ const obj = value;
319
+ if (typeof obj.collection === "string" && collectionNames.has(obj.collection)) {
320
+ const refId = typeof obj.id === "string" ? obj.id : typeof obj.slug === "string" ? obj.slug : void 0;
321
+ if (refId !== void 0 && !this.#store.has(obj.collection, refId)) {
322
+ const fieldPath = path ? ` (field: ${path})` : "";
323
+ logger.error(
324
+ `Invalid content reference: entry "${ownerId}" in collection "${ownerCollection}"${fieldPath} references "${refId}" in collection "${obj.collection}", but that entry does not exist.`
325
+ );
326
+ }
327
+ return;
328
+ }
329
+ for (const [key, val] of Object.entries(obj)) {
330
+ this.#findInvalidReferences(
331
+ val,
332
+ collectionNames,
333
+ ownerCollection,
334
+ ownerId,
335
+ logger,
336
+ path ? `${path}.${key}` : key
337
+ );
338
+ }
339
+ }
280
340
  async regenerateCollectionFileManifest() {
281
341
  const collectionsManifest = new URL(COLLECTIONS_MANIFEST_FILE, this.#settings.dotAstroDir);
282
342
  this.#logger.debug("content", "Regenerating collection file manifest");
@@ -1,6 +1,6 @@
1
1
  import { existsSync, promises as fs } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
- import yaml from "js-yaml";
3
+ import * as yaml from "js-yaml";
4
4
  import * as toml from "smol-toml";
5
5
  import { FileGlobNotSupported, FileParserNotFound } from "../../core/errors/errors-data.js";
6
6
  import { AstroError } from "../../core/errors/index.js";
@@ -8,7 +8,7 @@ import { glob as tinyglobby } from "tinyglobby";
8
8
  import { getContentEntryIdAndSlug, posixRelative } from "../utils.js";
9
9
  function generateIdDefault({ entry, base, data }, isLegacy) {
10
10
  if (data.slug) {
11
- return data.slug;
11
+ return String(data.slug);
12
12
  }
13
13
  const entryURL = new URL(encodeURI(entry), base);
14
14
  if (isLegacy) {
@@ -45,7 +45,8 @@ function glob(globOptions) {
45
45
  );
46
46
  }
47
47
  const isLegacy = !!globOptions[secretLegacyFlag];
48
- const generateId = globOptions?.generateId ?? ((opts) => generateIdDefault(opts, isLegacy));
48
+ const userGenerateId = globOptions?.generateId ?? ((opts) => generateIdDefault(opts, isLegacy));
49
+ const generateId = (opts) => String(userGenerateId(opts));
49
50
  const fileToIdMap = /* @__PURE__ */ new Map();
50
51
  return {
51
52
  name: "glob-loader",
@@ -2,7 +2,7 @@ import { existsSync, promises as fs } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import * as devalue from "devalue";
4
4
  import { forEach } from "neotraverse";
5
- import { imageSrcToImportId, importIdToSymbolName } from "../assets/utils/resolveImports.js";
5
+ import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
6
6
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
7
7
  import { DATA_STORE_MANIFEST_FILE, IMAGE_IMPORT_PREFIX } from "./consts.js";
8
8
  import {
@@ -109,8 +109,8 @@ class MutableDataStore extends ImmutableDataStore {
109
109
  const imports = [];
110
110
  const exports = [];
111
111
  const sortedAssetImports = [...this.#assetImports].sort();
112
- sortedAssetImports.forEach((id) => {
113
- const symbol = importIdToSymbolName(id);
112
+ sortedAssetImports.forEach((id, index) => {
113
+ const symbol = `__ASTRO_IMAGE_IMPORT_${index}`;
114
114
  imports.push(`import ${symbol} from ${JSON.stringify(id)};`);
115
115
  exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
116
116
  });
@@ -33,12 +33,14 @@ type ContentEntryResult = {
33
33
  body: string;
34
34
  collection: string;
35
35
  data: Record<string, any>;
36
+ digest?: string | number;
36
37
  render(): Promise<RenderResult>;
37
38
  };
38
39
  type DataEntryResult = {
39
40
  id: string;
40
41
  collection: string;
41
42
  data: Record<string, any>;
43
+ digest?: string | number;
42
44
  };
43
45
  type EntryLookupObject = {
44
46
  collection: string;
@@ -69,6 +71,7 @@ type RenderResult = {
69
71
  remarkPluginFrontmatter: Record<string, any>;
70
72
  };
71
73
  export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>): T;
74
+ export declare function resolveEntryData<T extends Record<string, unknown>>(entry: DataEntry<T>, imageAssetMap?: Map<string, ImageMetadata>): T;
72
75
  export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
73
76
  export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<string, number>>, z.ZodString, z.ZodObject<{
74
77
  id: z.ZodString;
@@ -3,6 +3,7 @@ import { forEach } from "neotraverse";
3
3
  import * as z from "zod/v4";
4
4
  import { createSvgComponent } from "../assets/runtime.js";
5
5
  import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
6
+ import { recordContentEntryRender } from "../core/build/incremental-content-collector.js";
6
7
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
7
8
  import { isRemotePath, prependForwardSlash } from "../core/path.js";
8
9
  import {
@@ -77,7 +78,7 @@ function createGetCollection({
77
78
  const { default: imageAssetMap } = await import("astro:asset-imports");
78
79
  const result = [];
79
80
  for (const rawEntry of await store.values(collection)) {
80
- const data = updateImageReferencesInData(rawEntry.data, rawEntry.filePath, imageAssetMap);
81
+ const data = resolveEntryData(rawEntry, imageAssetMap);
81
82
  let entry = {
82
83
  ...rawEntry,
83
84
  data,
@@ -134,7 +135,7 @@ function createGetEntry({ liveCollections }) {
134
135
  return;
135
136
  }
136
137
  const { default: imageAssetMap } = await import("astro:asset-imports");
137
- const data = updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap);
138
+ const data = resolveEntryData(entry, imageAssetMap);
138
139
  const result = {
139
140
  ...entry,
140
141
  data,
@@ -381,10 +382,14 @@ function updateImageReferencesInData(data, fileName, imageAssetMap) {
381
382
  });
382
383
  return copy;
383
384
  }
385
+ function resolveEntryData(entry, imageAssetMap) {
386
+ return entry.assetImports?.length ? updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap) : structuredClone(entry.data);
387
+ }
384
388
  async function renderEntry(entry) {
385
389
  if (!entry) {
386
390
  throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
387
391
  }
392
+ recordContentEntryRender(entry.filePath);
388
393
  if (entry.deferredRender) {
389
394
  try {
390
395
  const { default: contentModules } = await import("astro:content-module-imports");
@@ -561,5 +566,6 @@ export {
561
566
  defineCollection,
562
567
  defineLiveCollection,
563
568
  renderEntry,
569
+ resolveEntryData,
564
570
  updateImageReferencesInData
565
571
  };
@@ -373,6 +373,7 @@ async function writeContentFiles({
373
373
  data: ${dataType};
374
374
  rendered?: RenderedContent;
375
375
  filePath?: string;
376
+ digest?: string | number;
376
377
  }>;
377
378
  `;
378
379
  if (collectionConfig && (collectionConfig.schema || await getContentLayerSchema(collectionConfig, collectionKey))) {
@@ -1,5 +1,6 @@
1
1
  import { extname } from "node:path";
2
2
  import { getAssetsPrefix } from "../assets/utils/getAssetsPrefix.js";
3
+ import { createContentDataIncrementalMetadata } from "../core/build/incremental-metadata.js";
3
4
  import { fileExtension } from "../core/path.js";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { isRunnableDevEnvironment } from "vite";
@@ -107,7 +108,11 @@ function astroContentAssetPropagationPlugin({
107
108
  const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts: [] };
108
109
  export default defaultMod;
109
110
  `;
110
- return { code, map: { mappings: "" } };
111
+ return {
112
+ code,
113
+ map: { mappings: "" },
114
+ meta: createContentDataIncrementalMetadata()
115
+ };
111
116
  }
112
117
  }
113
118
  }