astro 7.0.6 → 7.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import {
9
9
  readLockFile,
10
10
  removeLockFile,
11
11
  isProcessAlive,
12
- GRACEFUL_SHUTDOWN_TIMEOUT
12
+ killDevServer
13
13
  } from "../../core/dev/lockfile.js";
14
14
  import { resolveRoot } from "../../core/config/config.js";
15
15
  const require2 = createRequire(import.meta.url);
@@ -40,22 +40,7 @@ async function background({
40
40
  return;
41
41
  }
42
42
  if (existing && flags.force) {
43
- try {
44
- process.kill(existing.pid, "SIGTERM");
45
- } catch {
46
- }
47
- const deadline2 = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT;
48
- while (Date.now() < deadline2) {
49
- if (!isProcessAlive(existing.pid)) break;
50
- await new Promise((r) => setTimeout(r, 100));
51
- }
52
- if (isProcessAlive(existing.pid)) {
53
- try {
54
- process.kill(existing.pid, "SIGKILL");
55
- } catch {
56
- }
57
- }
58
- removeLockFile(root);
43
+ await killDevServer(root, existing);
59
44
  }
60
45
  const args = ["dev"];
61
46
  if (flags.port) args.push("--port", String(flags.port));
@@ -2,7 +2,12 @@ import { detectAgenticEnvironment } from "am-i-vibing";
2
2
  import colors from "piccolore";
3
3
  import devServer from "../../core/dev/index.js";
4
4
  import { pathToFileURL } from "node:url";
5
- import { checkExistingServer, removeLockFile, writeLockFile } from "../../core/dev/lockfile.js";
5
+ import {
6
+ checkExistingServer,
7
+ killDevServer,
8
+ removeLockFile,
9
+ writeLockFile
10
+ } from "../../core/dev/lockfile.js";
6
11
  import { resolveRoot } from "../../core/config/config.js";
7
12
  import { printHelp } from "../../core/messages/runtime.js";
8
13
  import { createLoggerFromFlags, flagsToAstroInlineConfig } from "../flags.js";
@@ -83,15 +88,19 @@ Run \`astro dev --help\` to see available commands.`
83
88
  const root = pathToFileURL(resolveRoot(flags.root) + "/");
84
89
  const existingServer = checkExistingServer(root);
85
90
  if (existingServer) {
86
- const message = [
87
- "Another astro dev server is already running.",
88
- "",
89
- ` URL: ${existingServer.url}`,
90
- ` PID: ${existingServer.pid}`,
91
- "",
92
- `Run \`astro dev stop\` to stop it, or use \`astro dev --force\` to replace it.`
93
- ].join("\n");
94
- throw new Error(message);
91
+ if (flags.force) {
92
+ await killDevServer(root, existingServer);
93
+ } else {
94
+ const message = [
95
+ "Another astro dev server is already running.",
96
+ "",
97
+ ` URL: ${existingServer.url}`,
98
+ ` PID: ${existingServer.pid}`,
99
+ "",
100
+ `Run \`astro dev stop\` to stop it, or use \`astro dev --force\` to replace it.`
101
+ ].join("\n");
102
+ throw new Error(message);
103
+ }
95
104
  }
96
105
  const inlineConfig = flagsToAstroInlineConfig(flags);
97
106
  const server = await devServer(inlineConfig);
@@ -1,10 +1,5 @@
1
1
  import { pathToFileURL } from "node:url";
2
- import {
3
- checkExistingServer,
4
- removeLockFile,
5
- isProcessAlive,
6
- GRACEFUL_SHUTDOWN_TIMEOUT
7
- } from "../../core/dev/lockfile.js";
2
+ import { checkExistingServer, killDevServer } from "../../core/dev/lockfile.js";
8
3
  import { resolveRoot } from "../../core/config/config.js";
9
4
  function formatStopOutput(result) {
10
5
  return JSON.stringify(result);
@@ -19,22 +14,7 @@ async function stop({
19
14
  logger.info("SKIP_FORMAT", "No dev server is running.");
20
15
  return;
21
16
  }
22
- try {
23
- process.kill(existing.pid, "SIGTERM");
24
- } catch {
25
- }
26
- const deadline = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT;
27
- while (Date.now() < deadline) {
28
- if (!isProcessAlive(existing.pid)) break;
29
- await new Promise((r) => setTimeout(r, 100));
30
- }
31
- if (isProcessAlive(existing.pid)) {
32
- try {
33
- process.kill(existing.pid, "SIGKILL");
34
- } catch {
35
- }
36
- }
37
- removeLockFile(root);
17
+ await killDevServer(root, existing);
38
18
  logger.info("SKIP_FORMAT", `Stopped dev server (pid ${existing.pid}).`);
39
19
  }
40
20
  export {
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "7.0.6";
3
+ version = "7.0.8";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -197,7 +197,7 @@ ${contentConfig.error.message}`
197
197
  logger.info("Content config changed");
198
198
  shouldClear = true;
199
199
  }
200
- if (previousAstroVersion && previousAstroVersion !== "7.0.6") {
200
+ if (previousAstroVersion && previousAstroVersion !== "7.0.8") {
201
201
  logger.info("Astro version changed");
202
202
  shouldClear = true;
203
203
  }
@@ -205,8 +205,8 @@ ${contentConfig.error.message}`
205
205
  logger.info("Clearing content store");
206
206
  this.#store.clearAll();
207
207
  }
208
- if ("7.0.6") {
209
- this.#store.metaStore().set("astro-version", "7.0.6");
208
+ if ("7.0.8") {
209
+ this.#store.metaStore().set("astro-version", "7.0.8");
210
210
  }
211
211
  if (currentConfigDigest) {
212
212
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -3,8 +3,12 @@ import { type Plugin } from 'vite';
3
3
  import type { BuildInternals } from '../core/build/internal.js';
4
4
  import type { ExtractedChunk } from '../core/build/static-build.js';
5
5
  import type { AstroSettings } from '../types/astro.js';
6
- export declare function astroContentAssetPropagationPlugin({ settings, }: {
6
+ export declare function astroContentAssetPropagationPlugin({ settings, cssContentCache, }: {
7
7
  settings: AstroSettings;
8
+ /** Shared cache of CSS content populated by the dev-css plugin's transform hook.
9
+ * Used to retrieve already-processed CSS for CSS modules without re-importing
10
+ * via `?inline`, which would produce different scoped-name hashes with Lightning CSS. */
11
+ cssContentCache?: Map<string, string>;
8
12
  }): Plugin;
9
13
  /**
10
14
  * Post-build hook that injects propagated styles into content collection chunks.
@@ -19,7 +19,8 @@ import { joinPaths, prependForwardSlash, slash } from "@astrojs/internal-helpers
19
19
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
20
20
  import { isAstroServerEnvironment } from "../environments.js";
21
21
  function astroContentAssetPropagationPlugin({
22
- settings
22
+ settings,
23
+ cssContentCache
23
24
  }) {
24
25
  let environment = void 0;
25
26
  return {
@@ -84,7 +85,7 @@ function astroContentAssetPropagationPlugin({
84
85
  styles,
85
86
  urls,
86
87
  crawledFiles: styleCrawledFiles
87
- } = await getStylesForURL(basePath, environment);
88
+ } = await getStylesForURL(basePath, environment, cssContentCache);
88
89
  for (const file of styleCrawledFiles) {
89
90
  if (!file.includes("node_modules")) {
90
91
  this.addWatchFile(file);
@@ -113,7 +114,7 @@ function astroContentAssetPropagationPlugin({
113
114
  };
114
115
  }
115
116
  const INLINE_QUERY_REGEX = /(?:\?|&)inline(?:$|&)/;
116
- async function getStylesForURL(filePath, environment) {
117
+ async function getStylesForURL(filePath, environment, cssContentCache) {
117
118
  const importedCssUrls = /* @__PURE__ */ new Set();
118
119
  const importedStylesMap = /* @__PURE__ */ new Map();
119
120
  const crawledFiles = /* @__PURE__ */ new Set();
@@ -125,6 +126,8 @@ async function getStylesForURL(filePath, environment) {
125
126
  let css = "";
126
127
  if (typeof importedModule.ssrModule?.default === "string") {
127
128
  css = importedModule.ssrModule.default;
129
+ } else if (importedModule.id && cssContentCache?.has(importedModule.id)) {
130
+ css = cssContentCache.get(importedModule.id);
128
131
  } else {
129
132
  let modId = importedModule.url;
130
133
  if (!INLINE_QUERY_REGEX.test(importedModule.url)) {
@@ -1,6 +1,16 @@
1
- import type { Plugin as VitePlugin } from 'vite';
1
+ import type { BuildOptions, Plugin as VitePlugin, Rollup } from 'vite';
2
2
  import type { BuildInternals } from '../internal.js';
3
+ type GetModuleInfo = (moduleId: string) => Rollup.ModuleInfo | null;
4
+ export type ScriptChunkInfo = Pick<Rollup.OutputChunk, 'code' | 'facadeModuleId' | 'fileName' | 'imports' | 'dynamicImports' | 'moduleIds'>;
5
+ export declare function chunkHasDynamicImports(output: Pick<ScriptChunkInfo, 'dynamicImports' | 'moduleIds'>, getModuleInfo: GetModuleInfo): boolean;
6
+ export declare function shouldInlineScriptChunk(output: ScriptChunkInfo, { discoveredScripts, importedIds, assetInlineLimit, getModuleInfo, }: {
7
+ discoveredScripts: Set<string>;
8
+ importedIds: Set<string>;
9
+ assetInlineLimit: NonNullable<BuildOptions['assetsInlineLimit']>;
10
+ getModuleInfo: GetModuleInfo;
11
+ }): boolean;
3
12
  /**
4
13
  * Inline scripts from Astro files directly into the HTML.
5
14
  */
6
15
  export declare function pluginScripts(internals: BuildInternals): VitePlugin;
16
+ export {};
@@ -1,5 +1,18 @@
1
1
  import { shouldInlineAsset } from "./util.js";
2
2
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
3
+ function chunkHasDynamicImports(output, getModuleInfo) {
4
+ return output.dynamicImports.length > 0 || output.moduleIds.some((id) => (getModuleInfo(id)?.dynamicallyImportedIds.length ?? 0) > 0);
5
+ }
6
+ function shouldInlineScriptChunk(output, {
7
+ discoveredScripts,
8
+ importedIds,
9
+ assetInlineLimit,
10
+ getModuleInfo
11
+ }) {
12
+ const facadeModuleId = output.facadeModuleId;
13
+ if (facadeModuleId === null) return false;
14
+ return discoveredScripts.has(facadeModuleId) && !importedIds.has(output.fileName) && output.imports.length === 0 && !chunkHasDynamicImports(output, getModuleInfo) && shouldInlineAsset(output.code, output.fileName, assetInlineLimit);
15
+ }
3
16
  function pluginScripts(internals) {
4
17
  let assetInlineLimit;
5
18
  return {
@@ -20,8 +33,14 @@ function pluginScripts(internals) {
20
33
  }
21
34
  }
22
35
  }
36
+ const getModuleInfo = this.getModuleInfo.bind(this);
23
37
  for (const output of outputs) {
24
- if (output.type === "chunk" && output.facadeModuleId && internals.discoveredScripts.has(output.facadeModuleId) && !importedIds.has(output.fileName) && output.imports.length === 0 && output.dynamicImports.length === 0 && shouldInlineAsset(output.code, output.fileName, assetInlineLimit)) {
38
+ if (output.type === "chunk" && shouldInlineScriptChunk(output, {
39
+ discoveredScripts: internals.discoveredScripts,
40
+ importedIds,
41
+ assetInlineLimit,
42
+ getModuleInfo
43
+ })) {
25
44
  internals.inlinedScripts.set(output.facadeModuleId, output.code.trim());
26
45
  delete bundle[output.fileName];
27
46
  }
@@ -30,5 +49,7 @@ function pluginScripts(internals) {
30
49
  };
31
50
  }
32
51
  export {
33
- pluginScripts
52
+ chunkHasDynamicImports,
53
+ pluginScripts,
54
+ shouldInlineScriptChunk
34
55
  };
@@ -21,6 +21,11 @@ export declare function cssOrder(a: OrderInfo, b: OrderInfo): 1 | -1;
21
21
  /**
22
22
  * Merges inline CSS into as few stylesheets as possible,
23
23
  * preserving ordering when there are non-inlined in between.
24
+ *
25
+ * CSS chunks containing `@import` are never merged with other chunks.
26
+ * Per CSS spec, `@import` rules must appear at the top of a stylesheet
27
+ * or browsers silently ignore them. Keeping these chunks as separate
28
+ * `<style>` tags ensures the `@import` stays at the top of its own stylesheet.
24
29
  */
25
30
  export declare function mergeInlineCss(acc: Array<StylesheetAsset>, current: StylesheetAsset): Array<StylesheetAsset>;
26
31
  export {};
@@ -31,9 +31,13 @@ function mergeInlineCss(acc, current) {
31
31
  const lastWasInline = lastAdded?.type === "inline";
32
32
  const currentIsInline = current?.type === "inline";
33
33
  if (lastWasInline && currentIsInline) {
34
- const merged = { type: "inline", content: lastAdded.content + current.content };
35
- acc[acc.length - 1] = merged;
36
- return acc;
34
+ const currentHasImport = current.content.includes("@import");
35
+ const lastHasImport = lastAdded.content.includes("@import");
36
+ if (!currentHasImport && !lastHasImport) {
37
+ const merged = { type: "inline", content: lastAdded.content + current.content };
38
+ acc[acc.length - 1] = merged;
39
+ return acc;
40
+ }
37
41
  }
38
42
  acc.push(current);
39
43
  return acc;
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "7.0.6";
1
+ const ASTRO_VERSION = "7.0.8";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const ASTRO_ERROR_HEADER = "X-Astro-Error";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404.astro";
@@ -108,6 +108,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
108
108
  config: settings.config
109
109
  });
110
110
  const serverIslandsState = new ServerIslandsState();
111
+ const cssContentCache = /* @__PURE__ */ new Map();
111
112
  validateEnvPrefixAgainstSchema(settings.config);
112
113
  const commonConfig = {
113
114
  // Tell Vite not to combine config from vite.config.js with our provided inline config
@@ -156,7 +157,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
156
157
  vitePluginFetchable({ settings }),
157
158
  command === "dev" && vitePluginAstroServer({ settings, logger }),
158
159
  command === "dev" && vitePluginAstroServerClient(),
159
- astroDevCssPlugin({ routesList, command }),
160
+ astroDevCssPlugin({ routesList, command, cssContentCache }),
160
161
  importMetaEnv({ envLoader }),
161
162
  astroEnv({ settings, sync, envLoader }),
162
163
  vitePluginAdapterConfig(settings),
@@ -167,7 +168,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
167
168
  astroHeadPlugin(),
168
169
  astroContentVirtualModPlugin({ fs, settings }),
169
170
  astroContentImportPlugin({ fs, settings, logger }),
170
- astroContentAssetPropagationPlugin({ settings }),
171
+ astroContentAssetPropagationPlugin({ settings, cssContentCache }),
171
172
  vitePluginMiddleware({ settings }),
172
173
  astroAssetsPlugin({ fs, settings, sync, logger }),
173
174
  astroPrefetch({ settings }),
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
26
26
  await telemetry.record([]);
27
27
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
28
28
  const logger = restart.container.logger;
29
- const currentVersion = "7.0.6";
29
+ const currentVersion = "7.0.8";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -48,6 +48,14 @@ export declare function removeLockFile(root: URL): void;
48
48
  * This is the pure decision logic, separated from I/O for testability.
49
49
  */
50
50
  export declare function evaluateExistingServer(data: LockFileData | null, alive: boolean): ExistingServer | null;
51
+ /**
52
+ * Kill the dev server identified by `data` and clean up its lock file.
53
+ *
54
+ * Sends SIGTERM and waits up to {@link GRACEFUL_SHUTDOWN_TIMEOUT} for the
55
+ * process to exit, escalating to SIGKILL if it is still alive. The lock file
56
+ * is always removed afterwards so a new server can start.
57
+ */
58
+ export declare function killDevServer(root: URL, data: LockFileData): Promise<void>;
51
59
  /**
52
60
  * Check for an existing dev server by reading the lock file and checking process liveness.
53
61
  * Automatically cleans up stale lock files.
@@ -80,6 +80,24 @@ function evaluateExistingServer(data, alive) {
80
80
  }
81
81
  return { data, stale: !alive };
82
82
  }
83
+ async function killDevServer(root, data) {
84
+ try {
85
+ process.kill(data.pid, "SIGTERM");
86
+ } catch {
87
+ }
88
+ const deadline = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT;
89
+ while (Date.now() < deadline) {
90
+ if (!isProcessAlive(data.pid)) break;
91
+ await new Promise((r) => setTimeout(r, 100));
92
+ }
93
+ if (isProcessAlive(data.pid)) {
94
+ try {
95
+ process.kill(data.pid, "SIGKILL");
96
+ } catch {
97
+ }
98
+ }
99
+ removeLockFile(root);
100
+ }
83
101
  function checkExistingServer(root) {
84
102
  const data = readLockFile(root);
85
103
  const result = evaluateExistingServer(data, data !== null && isProcessAlive(data.pid));
@@ -98,6 +116,7 @@ export {
98
116
  evaluateExistingServer,
99
117
  getLogFileURL,
100
118
  isProcessAlive,
119
+ killDevServer,
101
120
  parseLockFile,
102
121
  readLockFile,
103
122
  removeLockFile,
@@ -10,14 +10,13 @@ export interface HeadPropagator {
10
10
  * its children, and one of those children may be a `self` component that emits
11
11
  * styles. Slots add a second way to find them — a slot whose markup contains an
12
12
  * `await` only reaches the components after that `await` once it resolves, so
13
- * we also wait for those pending slot pre-renders. We keep initializing
14
- * propagators and waiting on slots until no new ones appear.
13
+ * the pending slot pre-renders are fully drained before moving on to the next
14
+ * propagator.
15
15
  *
16
- * Propagators are tracked in a `seen` set rather than read through a single
17
- * live `Set` iterator. A propagator can be registered after we have already
18
- * iterated to the end of the set (e.g. once a slot's `await` resolves), and a
19
- * `Set` iterator that has reported `done` would never report those late
20
- * additions.
16
+ * A single pass over the live `Set` reaches every late registration: a `Set`
17
+ * iterator visits entries added during iteration (in insertion order), and
18
+ * because slots are drained before the iterator advances, nothing can register
19
+ * a propagator after the iterator has reported `done`.
21
20
  *
22
21
  * @example
23
22
  * If a layout initializes and discovers a nested component that also emits
@@ -1,25 +1,19 @@
1
1
  async function collectPropagatedHeadParts(input) {
2
2
  const collectedHeadParts = [];
3
- const seen = /* @__PURE__ */ new Set();
4
3
  const pendingSlotEvaluations = input.result._metadata?.pendingSlotEvaluations ?? [];
5
- while (true) {
6
- if (pendingSlotEvaluations.length > 0) {
4
+ const drainPendingSlots = async () => {
5
+ while (pendingSlotEvaluations.length > 0) {
7
6
  const batch = pendingSlotEvaluations.splice(0, pendingSlotEvaluations.length);
8
7
  await Promise.all(batch);
9
- continue;
10
8
  }
11
- let progressed = false;
12
- for (const propagator of input.propagators) {
13
- if (seen.has(propagator)) continue;
14
- seen.add(propagator);
15
- progressed = true;
16
- const returnValue = await propagator.init(input.result);
17
- if (input.isHeadAndContent(returnValue) && returnValue.head) {
18
- collectedHeadParts.push(returnValue.head);
19
- }
20
- break;
9
+ };
10
+ await drainPendingSlots();
11
+ for (const propagator of input.propagators) {
12
+ const returnValue = await propagator.init(input.result);
13
+ if (input.isHeadAndContent(returnValue) && returnValue.head) {
14
+ collectedHeadParts.push(returnValue.head);
21
15
  }
22
- if (!progressed) break;
16
+ await drainPendingSlots();
23
17
  }
24
18
  return collectedHeadParts;
25
19
  }
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"7.0.6"}`
272
+ `v${"7.0.8"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -69,6 +69,7 @@ async function preview(inlineConfig) {
69
69
  logger: new AstroIntegrationLogger(logger.options, settings.adapter.name),
70
70
  headers: settings.config.server.headers,
71
71
  allowedHosts: settings.config.server.allowedHosts,
72
+ open: settings.config.server.open,
72
73
  root: settings.config.root
73
74
  });
74
75
  return server;
@@ -44,9 +44,14 @@ async function getProps(opts) {
44
44
  }
45
45
  function getParams(route, pathname) {
46
46
  if (!route.params.length) return {};
47
- const path = pathname.endsWith(".html") && route.type === "page" && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
47
+ const hasHtmlSuffix = pathname.endsWith(".html") && !routeHasHtmlExtension(route);
48
+ const path = hasHtmlSuffix && route.type === "page" ? pathname.slice(0, -".html".length) : pathname;
48
49
  const allPatterns = [route, ...route.fallbackRoutes].map((r) => r.pattern);
49
- const paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
50
+ let paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
51
+ if (!paramsMatch && hasHtmlSuffix && route.type !== "page") {
52
+ const strippedPath = pathname.endsWith("/index.html") ? pathname.slice(0, -"/index.html".length) || "/" : pathname.slice(0, -".html".length);
53
+ paramsMatch = allPatterns.map((pattern) => pattern.exec(strippedPath)).find((x) => x);
54
+ }
50
55
  if (!paramsMatch) return {};
51
56
  const params = {};
52
57
  route.params.forEach((key, i) => {
@@ -1,7 +1,11 @@
1
+ import { hasFileExtension } from "@astrojs/internal-helpers/path";
1
2
  import { trimSlashes } from "../path.js";
2
3
  import { getRouteGenerator } from "./generator.js";
3
4
  import { validateGetStaticPathsParameter } from "./internal/validation.js";
4
5
  function stringifyParams(params, route, trailingSlash) {
6
+ if (route.type === "endpoint" && hasFileExtension(route.route)) {
7
+ trailingSlash = "never";
8
+ }
5
9
  const validatedParams = {};
6
10
  for (const [key, value] of Object.entries(params)) {
7
11
  validateGetStaticPathsParameter([key, value], route.component);
@@ -120,6 +120,25 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
120
120
  render(slotName: string, args?: any[]): Promise<string>;
121
121
  };
122
122
  }
123
+ /**
124
+ * A type containing functions for logging messages.
125
+ *
126
+ * [Astro reference](https://docs.astro.build/en/reference/api-reference/#logger)
127
+ */
128
+ export interface AstroRuntimeLogger {
129
+ /**
130
+ * Logs a message with `info` level.
131
+ */
132
+ info: (msg: string) => void;
133
+ /**
134
+ * Logs a message with `warn` level.
135
+ */
136
+ warn: (msg: string) => void;
137
+ /**
138
+ * Logs a message with `error` level.
139
+ */
140
+ error: (msg: string) => void;
141
+ }
123
142
  /**
124
143
  * The `APIContext` is the object made available to endpoints and middleware.
125
144
  * It is a subset of the `Astro` global object available in pages.
@@ -533,20 +552,7 @@ export interface APIContext<Props extends Record<string, any> = Record<string, a
533
552
  /**
534
553
  * It exposes utilities for logging messages.
535
554
  */
536
- logger: {
537
- /**
538
- * Logs a message with `info` level.
539
- */
540
- info: (msg: string) => void;
541
- /**
542
- * Logs a message with `warn` level.
543
- */
544
- warn: (msg: string) => void;
545
- /**
546
- * Logs a message with `error` level.
547
- */
548
- error: (msg: string) => void;
549
- };
555
+ logger: AstroRuntimeLogger;
550
556
  /**
551
557
  * The route currently rendered. It's stripped of the `srcDir` and the `pages` folder, and it doesn't contain the extension.
552
558
  *
@@ -21,6 +21,11 @@ export interface PreviewServerParams {
21
21
  * If the `Host` header doesn't match one of the allowed hosts, the server will return a 403 response.
22
22
  */
23
23
  allowedHosts?: string[] | true;
24
+ /**
25
+ * Controls whether the preview server should open in the browser on startup.
26
+ * Pass a full URL string (e.g. "http://example.com") or a pathname (e.g. "/about") to specify the URL to open.
27
+ */
28
+ open?: string | boolean;
24
29
  root: URL;
25
30
  }
26
31
  export type CreatePreviewServer = (params: PreviewServerParams) => PreviewServer | Promise<PreviewServer>;
@@ -167,27 +167,31 @@ class AstroServerApp extends BaseApp {
167
167
  } else {
168
168
  socket.on("close", onSocketClose);
169
169
  }
170
- const request = createRequest({
171
- url,
172
- headers: incomingRequest.headers,
173
- method: incomingRequest.method,
174
- body,
175
- logger: self.logger,
176
- isPrerendered: matchedRoute.routeData.prerender,
177
- routePattern: matchedRoute.routeData.component,
178
- init: { signal: abortController.signal }
179
- });
180
- const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
181
- for (const [name, value] of Object.entries(self.settings.config.server.headers ?? {})) {
182
- if (value) incomingResponse.setHeader(name, value);
170
+ try {
171
+ const request = createRequest({
172
+ url,
173
+ headers: incomingRequest.headers,
174
+ method: incomingRequest.method,
175
+ body,
176
+ logger: self.logger,
177
+ isPrerendered: matchedRoute.routeData.prerender,
178
+ routePattern: matchedRoute.routeData.component,
179
+ init: { signal: abortController.signal }
180
+ });
181
+ const locals = Reflect.get(incomingRequest, clientLocalsSymbol);
182
+ for (const [name, value] of Object.entries(self.settings.config.server.headers ?? {})) {
183
+ if (value) incomingResponse.setHeader(name, value);
184
+ }
185
+ const clientAddress = incomingRequest.socket.remoteAddress;
186
+ const response = await self.render(request, {
187
+ locals,
188
+ routeData: matchedRoute.routeData,
189
+ clientAddress
190
+ });
191
+ await writeSSRResult(request, response, incomingResponse);
192
+ } finally {
193
+ socket.off("close", onSocketClose);
183
194
  }
184
- const clientAddress = incomingRequest.socket.remoteAddress;
185
- const response = await self.render(request, {
186
- locals,
187
- routeData: matchedRoute.routeData,
188
- clientAddress
189
- });
190
- await writeSSRResult(request, response, incomingResponse);
191
195
  },
192
196
  onError(_err) {
193
197
  const error = createSafeError(_err);
@@ -3,6 +3,11 @@ import type { RoutesList } from '../types/astro.js';
3
3
  interface AstroVitePluginOptions {
4
4
  routesList: RoutesList;
5
5
  command: 'dev' | 'build';
6
+ /** Shared cache of CSS content by module ID. Populated by the transform hook and
7
+ * consumed by the content asset propagation plugin to avoid re-processing CSS
8
+ * modules with `?inline` (which can produce different scoped-name hashes with
9
+ * Lightning CSS). */
10
+ cssContentCache: Map<string, string>;
6
11
  }
7
12
  /**
8
13
  * This plugin tracks the CSS that should be applied by route.
@@ -14,5 +19,5 @@ interface AstroVitePluginOptions {
14
19
  *
15
20
  * @param routesList
16
21
  */
17
- export declare function astroDevCssPlugin({ routesList, command }: AstroVitePluginOptions): Plugin[];
22
+ export declare function astroDevCssPlugin({ routesList, command, cssContentCache, }: AstroVitePluginOptions): Plugin[];
18
23
  export {};
@@ -60,9 +60,12 @@ function* collectCSSWithOrder(id, mod, seen = /* @__PURE__ */ new Set()) {
60
60
  }
61
61
  }
62
62
  }
63
- function astroDevCssPlugin({ routesList, command }) {
63
+ function astroDevCssPlugin({
64
+ routesList,
65
+ command,
66
+ cssContentCache
67
+ }) {
64
68
  let server;
65
- const cssContentCache = /* @__PURE__ */ new Map();
66
69
  function getCurrentEnvironment(pluginEnv) {
67
70
  return pluginEnv ?? server?.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
68
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "7.0.6",
3
+ "version": "7.0.8",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -96,7 +96,7 @@
96
96
  "README.md"
97
97
  ],
98
98
  "dependencies": {
99
- "@astrojs/compiler-rs": "^0.3.0",
99
+ "@astrojs/compiler-rs": "^0.3.1",
100
100
  "@capsizecss/unpack": "^4.0.0",
101
101
  "@clack/prompts": "^1.1.0",
102
102
  "@oslojs/encoding": "^1.1.0",
@@ -147,8 +147,8 @@
147
147
  "yargs-parser": "^22.0.0",
148
148
  "zod": "^4.3.6",
149
149
  "@astrojs/internal-helpers": "0.10.1",
150
- "@astrojs/markdown-satteri": "0.3.3",
151
- "@astrojs/telemetry": "3.3.2"
150
+ "@astrojs/markdown-satteri": "0.3.4",
151
+ "@astrojs/telemetry": "3.3.3"
152
152
  },
153
153
  "optionalDependencies": {
154
154
  "sharp": "^0.34.0 || ^0.35.0"