astro 7.0.4 → 7.0.6

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 (59) hide show
  1. package/components/Picture.astro +19 -2
  2. package/dist/actions/handler.js +7 -0
  3. package/dist/cli/dev/background.js +4 -2
  4. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  5. package/dist/cli/install-package.js +5 -3
  6. package/dist/container/index.js +4 -2
  7. package/dist/content/content-layer.js +3 -3
  8. package/dist/core/app/origin-check.d.ts +32 -0
  9. package/dist/core/app/origin-check.js +52 -0
  10. package/dist/core/base-pipeline.js +1 -1
  11. package/dist/core/build/plugins/index.js +2 -0
  12. package/dist/core/build/plugins/plugin-css-target-lowering.d.ts +14 -0
  13. package/dist/core/build/plugins/plugin-css-target-lowering.js +82 -0
  14. package/dist/core/constants.js +1 -1
  15. package/dist/core/create-vite.js +2 -0
  16. package/dist/core/dev/dev.js +1 -1
  17. package/dist/core/fetch/fetch-state.js +2 -0
  18. package/dist/core/head-propagation/buffer.d.ts +14 -3
  19. package/dist/core/head-propagation/buffer.js +17 -7
  20. package/dist/core/messages/runtime.js +1 -1
  21. package/dist/core/pages/handler.js +9 -1
  22. package/dist/core/routing/generator.js +2 -2
  23. package/dist/core/util.d.ts +8 -0
  24. package/dist/core/util.js +23 -0
  25. package/dist/core/viteUtils.d.ts +3 -0
  26. package/dist/core/viteUtils.js +8 -2
  27. package/dist/prefetch/index.js +10 -0
  28. package/dist/runtime/server/render/astro/instance.js +3 -0
  29. package/dist/runtime/server/render/common.js +7 -1
  30. package/dist/runtime/server/render/dom.js +4 -1
  31. package/dist/runtime/server/render/instruction.d.ts +1 -0
  32. package/dist/runtime/server/render/instruction.js +5 -1
  33. package/dist/runtime/server/render/page.js +7 -2
  34. package/dist/runtime/server/render/server-islands-shared.d.ts +1 -0
  35. package/dist/runtime/server/render/server-islands-shared.js +4 -0
  36. package/dist/runtime/server/render/server-islands.js +9 -8
  37. package/dist/runtime/server/render/slot.d.ts +15 -2
  38. package/dist/runtime/server/render/slot.js +37 -6
  39. package/dist/runtime/server/render/util.d.ts +1 -0
  40. package/dist/runtime/server/render/util.js +1 -0
  41. package/dist/template/4xx.js +1 -1
  42. package/dist/transitions/swap-functions.js +9 -2
  43. package/dist/types/public/internal.d.ts +14 -0
  44. package/dist/vite-plugin-astro/index.js +3 -1
  45. package/dist/vite-plugin-astro-server/plugin.js +3 -0
  46. package/dist/vite-plugin-config-alias/index.d.ts +19 -1
  47. package/dist/vite-plugin-config-alias/index.js +6 -5
  48. package/dist/vite-plugin-css/index.js +1 -1
  49. package/dist/vite-plugin-html/transform/index.js +16 -7
  50. package/dist/vite-plugin-html/transform/slots.d.ts +10 -7
  51. package/dist/vite-plugin-html/transform/slots.js +19 -26
  52. package/dist/vite-plugin-html/transform/utils.d.ts +0 -3
  53. package/dist/vite-plugin-html/transform/utils.js +1 -20
  54. package/package.json +5 -10
  55. package/templates/content/types.d.ts +4 -0
  56. package/dist/core/app/middlewares.d.ts +0 -8
  57. package/dist/core/app/middlewares.js +0 -49
  58. package/dist/vite-plugin-html/transform/escape.d.ts +0 -7
  59. package/dist/vite-plugin-html/transform/escape.js +0 -30
@@ -1,7 +1,13 @@
1
1
  ---
2
- import { getImage, imageConfig, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
2
+ import {
3
+ getImage,
4
+ imageConfig,
5
+ inferRemoteSize,
6
+ type LocalImageProps,
7
+ type RemoteImageProps,
8
+ } from 'astro:assets';
3
9
  import * as mime from 'mrmime';
4
- import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind.js';
10
+ import { isESMImportedImage, isRemoteImage, resolveSrc } from '../dist/assets/utils/imageKind.js';
5
11
  import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
6
12
  import type {
7
13
  GetImageResult,
@@ -62,6 +68,17 @@ for (const key in props) {
62
68
  }
63
69
 
64
70
  const originalSrc = await resolveSrc(props.src);
71
+
72
+ // When inferSize is set for a remote image, resolve dimensions once and thread them through
73
+ // to all getImage() calls. This avoids multiple redundant HTTP requests to the same URL,
74
+ // which can trigger rate-limiting (HTTP 429) on servers like Wikimedia.
75
+ if (props.inferSize && isRemoteImage(originalSrc)) {
76
+ const remoteSize = await inferRemoteSize(originalSrc);
77
+ delete props.inferSize;
78
+ props.width ??= remoteSize.width;
79
+ props.height ??= remoteSize.height;
80
+ }
81
+
65
82
  const optimizedImages: GetImageResult[] = await Promise.all(
66
83
  formats.map(
67
84
  async (format) =>
@@ -1,3 +1,7 @@
1
+ import {
2
+ createCrossOriginForbiddenResponse,
3
+ isForbiddenCrossOriginRequest
4
+ } from "../core/app/origin-check.js";
1
5
  import { PipelineFeatures } from "../core/base-pipeline.js";
2
6
  import { getActionContext, serializeActionResult } from "./runtime/server.js";
3
7
  class ActionHandler {
@@ -18,6 +22,9 @@ class ActionHandler {
18
22
  if (!action) {
19
23
  return void 0;
20
24
  }
25
+ if (state.pipeline.manifest.checkOrigin && isForbiddenCrossOriginRequest(apiContext.request, apiContext.url, apiContext.isPrerendered)) {
26
+ return Promise.resolve(createCrossOriginForbiddenResponse(apiContext.request));
27
+ }
21
28
  return this.#executeAction(action, setActionResult);
22
29
  }
23
30
  async #executeAction(action, setActionResult) {
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, openSync } from "node:fs";
3
- import { resolve } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, resolve } from "node:path";
4
5
  import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import {
6
7
  checkExistingServer,
@@ -11,6 +12,7 @@ import {
11
12
  GRACEFUL_SHUTDOWN_TIMEOUT
12
13
  } from "../../core/dev/lockfile.js";
13
14
  import { resolveRoot } from "../../core/config/config.js";
15
+ const require2 = createRequire(import.meta.url);
14
16
  function formatBackgroundOutput(result) {
15
17
  return JSON.stringify(result);
16
18
  }
@@ -76,7 +78,7 @@ async function background({
76
78
  }
77
79
  const logFd = openSync(logFilePath, "w");
78
80
  const rootPath = fileURLToPath(root);
79
- const astroBin = resolve(rootPath, "node_modules", "astro", "bin", "astro.mjs");
81
+ const astroBin = resolve(dirname(require2.resolve("astro/package.json")), "bin", "astro.mjs");
80
82
  const child = spawn(process.execPath, [astroBin, ...args], {
81
83
  detached: true,
82
84
  stdio: ["ignore", logFd, logFd],
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "7.0.4";
3
+ version = "7.0.6";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
+ import { pathToFileURL } from "node:url";
2
3
  import * as clack from "@clack/prompts";
3
4
  import ci from "ci-info";
4
5
  import { detect, resolveCommand } from "package-manager-detector";
@@ -8,8 +9,8 @@ const require2 = createRequire(import.meta.url);
8
9
  const { bold, cyan, dim, magenta } = colors;
9
10
  async function getPackage(packageName, logger, options, otherDeps = []) {
10
11
  try {
11
- require2.resolve(packageName, { paths: [options.cwd ?? process.cwd()] });
12
- const packageImport = await import(packageName);
12
+ const resolved = require2.resolve(packageName, { paths: [options.cwd ?? process.cwd()] });
13
+ const packageImport = await import(pathToFileURL(resolved).href);
13
14
  return packageImport;
14
15
  } catch {
15
16
  if (options.optional) return void 0;
@@ -25,7 +26,8 @@ async function getPackage(packageName, logger, options, otherDeps = []) {
25
26
  }
26
27
  const result = await installPackage([packageName, ...otherDeps], options, logger);
27
28
  if (result) {
28
- const packageImport = await import(packageName);
29
+ const resolved = require2.resolve(packageName, { paths: [options.cwd ?? process.cwd()] });
30
+ const packageImport = await import(pathToFileURL(resolved).href);
29
31
  return packageImport;
30
32
  } else {
31
33
  return void 0;
@@ -14,6 +14,8 @@ import { validateSegment } from "../core/routing/segment.js";
14
14
  import { SlotString } from "../runtime/server/render/slot.js";
15
15
  import { ContainerPipeline } from "./pipeline.js";
16
16
  import { createConsoleLogger } from "../core/logger/impls/console.js";
17
+ const { gfm: _, smartypants: __, ...containerMarkdownDefaults } = ASTRO_CONFIG_DEFAULTS.markdown;
18
+ const CONTAINER_CONFIG_DEFAULTS = { ...ASTRO_CONFIG_DEFAULTS, markdown: containerMarkdownDefaults };
17
19
  function createManifest(manifest, renderers, middleware) {
18
20
  function middlewareInstance() {
19
21
  return {
@@ -113,7 +115,7 @@ class experimental_AstroContainer {
113
115
  */
114
116
  static async create(containerOptions = {}) {
115
117
  const { streaming = false, manifest, renderers = [], resolve } = containerOptions;
116
- const astroConfig = await validateConfig(ASTRO_CONFIG_DEFAULTS, process.cwd(), "container");
118
+ const astroConfig = await validateConfig(CONTAINER_CONFIG_DEFAULTS, process.cwd(), "container");
117
119
  return new experimental_AstroContainer({
118
120
  streaming,
119
121
  manifest,
@@ -206,7 +208,7 @@ class experimental_AstroContainer {
206
208
  // NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
207
209
  // @ts-expect-error @ematipico: I plan to use it for a possible integration that could help people
208
210
  static async createFromManifest(manifest) {
209
- const astroConfig = await validateConfig(ASTRO_CONFIG_DEFAULTS, process.cwd(), "container");
211
+ const astroConfig = await validateConfig(CONTAINER_CONFIG_DEFAULTS, process.cwd(), "container");
210
212
  const container = new experimental_AstroContainer({
211
213
  manifest,
212
214
  astroConfig
@@ -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.4") {
200
+ if (previousAstroVersion && previousAstroVersion !== "7.0.6") {
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.4") {
209
- this.#store.metaStore().set("astro-version", "7.0.4");
208
+ if ("7.0.6") {
209
+ this.#store.metaStore().set("astro-version", "7.0.6");
210
210
  }
211
211
  if (currentConfigDigest) {
212
212
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Home of the `security.checkOrigin` logic. Exposes the shared predicate and
3
+ * response used to reject cross-site submissions, plus the middleware factory
4
+ * that installs the check in the request pipeline. Consumers (the pipeline
5
+ * middleware and the Astro Actions dispatch) import from here so the check
6
+ * stays consistent regardless of where it runs.
7
+ */
8
+ import type { MiddlewareHandler } from '../../types/public/common.js';
9
+ /**
10
+ * Determines whether a request should be rejected because it is a cross-site
11
+ * submission for a route rendered on demand.
12
+ *
13
+ * This encapsulates the shared logic used by both the origin-check middleware
14
+ * and the Astro Actions dispatch, so the check is applied consistently
15
+ * regardless of where the request is handled.
16
+ *
17
+ * @private
18
+ */
19
+ export declare function isForbiddenCrossOriginRequest(request: Request, url: URL, isPrerendered: boolean): boolean;
20
+ /**
21
+ * Builds the 403 response returned when a cross-site submission is rejected.
22
+ *
23
+ * @private
24
+ */
25
+ export declare function createCrossOriginForbiddenResponse(request: Request): Response;
26
+ /**
27
+ * Returns a middleware function in charge to check the `origin` header.
28
+ *
29
+ * @private
30
+ */
31
+ export declare function createOriginCheckMiddleware(): MiddlewareHandler;
32
+ export declare function hasFormLikeHeader(contentType: string | null): boolean;
@@ -0,0 +1,52 @@
1
+ import { defineMiddleware } from "../middleware/defineMiddleware.js";
2
+ const FORM_CONTENT_TYPES = [
3
+ "application/x-www-form-urlencoded",
4
+ "multipart/form-data",
5
+ "text/plain"
6
+ ];
7
+ const SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
8
+ function isForbiddenCrossOriginRequest(request, url, isPrerendered) {
9
+ if (isPrerendered) {
10
+ return false;
11
+ }
12
+ if (SAFE_METHODS.includes(request.method)) {
13
+ return false;
14
+ }
15
+ const isSameOrigin = request.headers.get("origin") === url.origin;
16
+ const hasContentType = request.headers.has("content-type");
17
+ if (hasContentType) {
18
+ const formLikeHeader = hasFormLikeHeader(request.headers.get("content-type"));
19
+ return formLikeHeader && !isSameOrigin;
20
+ }
21
+ return !isSameOrigin;
22
+ }
23
+ function createCrossOriginForbiddenResponse(request) {
24
+ return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
25
+ status: 403
26
+ });
27
+ }
28
+ function createOriginCheckMiddleware() {
29
+ return defineMiddleware((context, next) => {
30
+ const { request, url, isPrerendered } = context;
31
+ if (isForbiddenCrossOriginRequest(request, url, isPrerendered)) {
32
+ return createCrossOriginForbiddenResponse(request);
33
+ }
34
+ return next();
35
+ });
36
+ }
37
+ function hasFormLikeHeader(contentType) {
38
+ if (contentType) {
39
+ for (const FORM_CONTENT_TYPE of FORM_CONTENT_TYPES) {
40
+ if (contentType.toLowerCase().includes(FORM_CONTENT_TYPE)) {
41
+ return true;
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ export {
48
+ createCrossOriginForbiddenResponse,
49
+ createOriginCheckMiddleware,
50
+ hasFormLikeHeader,
51
+ isForbiddenCrossOriginRequest
52
+ };
@@ -1,5 +1,5 @@
1
1
  import { NOOP_ACTIONS_MOD } from "../actions/noop-actions.js";
2
- import { createOriginCheckMiddleware } from "./app/middlewares.js";
2
+ import { createOriginCheckMiddleware } from "./app/origin-check.js";
3
3
  import { ActionNotFoundError } from "./errors/errors-data.js";
4
4
  import { AstroError } from "./errors/index.js";
5
5
  import { NOOP_MIDDLEWARE_FN } from "./middleware/noop-middleware.js";
@@ -3,6 +3,7 @@ import { astroHeadBuildPlugin } from "../../../vite-plugin-head/index.js";
3
3
  import { pluginAnalyzer } from "./plugin-analyzer.js";
4
4
  import { pluginComponentEntry } from "./plugin-component-entry.js";
5
5
  import { pluginCSS } from "./plugin-css.js";
6
+ import { pluginCssTargetLowering } from "./plugin-css-target-lowering.js";
6
7
  import { pluginInternals } from "./plugin-internals.js";
7
8
  import { pluginMiddleware } from "./plugin-middleware.js";
8
9
  import { pluginPrerender } from "./plugin-prerender.js";
@@ -18,6 +19,7 @@ function getAllBuildPlugins(internals, options) {
18
19
  pluginInternals(options, internals),
19
20
  pluginMiddleware(options, internals),
20
21
  vitePluginActionsBuild(options, internals),
22
+ pluginCssTargetLowering(),
21
23
  ...pluginCSS(options, internals),
22
24
  astroHeadBuildPlugin(internals),
23
25
  pluginPrerender(options, internals),
@@ -0,0 +1,14 @@
1
+ import type { Plugin as VitePlugin } from 'vite';
2
+ /**
3
+ * Vite plugin that applies CSS target lowering without minification.
4
+ *
5
+ * Vite's `finalizeCss` only applies CSS target lowering (via lightningcss) when
6
+ * `config.build.cssMinify` is truthy, because target lowering and minification
7
+ * are coupled in `minifyCSS`. When users set `minify: false`, Astro forces
8
+ * `cssMinify: false`, which disables target lowering for all CSS.
9
+ *
10
+ * This plugin fills that gap: when `cssMinify` is falsy and a CSS target is
11
+ * configured, it runs lightningcss with `minify: false` on all CSS assets in
12
+ * `generateBundle`, applying target lowering without minification.
13
+ */
14
+ export declare function pluginCssTargetLowering(): VitePlugin;
@@ -0,0 +1,82 @@
1
+ import { createRequire } from "node:module";
2
+ const BROWSER_MAP = {
3
+ chrome: "chrome",
4
+ edge: "edge",
5
+ firefox: "firefox",
6
+ hermes: false,
7
+ ie: "ie",
8
+ ios: "ios_saf",
9
+ node: false,
10
+ opera: "opera",
11
+ rhino: false,
12
+ safari: "safari"
13
+ };
14
+ const VERSION_RE = /\d/;
15
+ function convertTargets(esbuildTarget) {
16
+ if (!esbuildTarget) return {};
17
+ const targets = {};
18
+ const entries = Array.isArray(esbuildTarget) ? esbuildTarget : [esbuildTarget];
19
+ for (const entry of entries) {
20
+ if (entry === "esnext") continue;
21
+ const index = entry.search(VERSION_RE);
22
+ if (index >= 0) {
23
+ const browserName = entry.slice(0, index);
24
+ const browser = BROWSER_MAP[browserName];
25
+ if (browser === false) continue;
26
+ if (browser) {
27
+ const [major, minor = 0] = entry.slice(index).split(".").map((v) => Number.parseInt(v, 10));
28
+ if (!isNaN(major) && !isNaN(minor)) {
29
+ const version = major << 16 | minor << 8;
30
+ if (!targets[browser] || version < targets[browser]) {
31
+ targets[browser] = version;
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ return targets;
38
+ }
39
+ function pluginCssTargetLowering() {
40
+ let resolvedConfig;
41
+ return {
42
+ name: "astro:css-target-lowering",
43
+ enforce: "post",
44
+ configResolved(config) {
45
+ resolvedConfig = config;
46
+ },
47
+ async generateBundle(_outputOptions, bundle) {
48
+ if (resolvedConfig.build.cssMinify) return;
49
+ const cssTarget = resolvedConfig.build.cssTarget;
50
+ if (!cssTarget) return;
51
+ const targets = convertTargets(cssTarget);
52
+ if (Object.keys(targets).length === 0) return;
53
+ let lcssTransform;
54
+ try {
55
+ const requireFromVite = createRequire(import.meta.resolve("vite"));
56
+ lcssTransform = requireFromVite("lightningcss").transform;
57
+ } catch {
58
+ return;
59
+ }
60
+ for (const [, asset] of Object.entries(bundle)) {
61
+ if (asset.type !== "asset") continue;
62
+ if (!asset.fileName.endsWith(".css")) continue;
63
+ if (typeof asset.source !== "string") continue;
64
+ try {
65
+ const result = lcssTransform({
66
+ ...resolvedConfig.css?.lightningcss,
67
+ targets,
68
+ cssModules: void 0,
69
+ filename: asset.fileName,
70
+ code: Buffer.from(asset.source),
71
+ minify: false
72
+ });
73
+ asset.source = new TextDecoder().decode(result.code);
74
+ } catch {
75
+ }
76
+ }
77
+ }
78
+ };
79
+ }
80
+ export {
81
+ pluginCssTargetLowering
82
+ };
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "7.0.4";
1
+ const ASTRO_VERSION = "7.0.6";
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";
@@ -201,6 +201,8 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
201
201
  }
202
202
  },
203
203
  resolve: {
204
+ // Vite's native tsconfig path resolution; see configAliasVitePlugin for the deprecated fallback.
205
+ tsconfigPaths: true,
204
206
  alias: [
205
207
  {
206
208
  // This is needed for Deno compatibility, as the non-browser version
@@ -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.4";
29
+ const currentVersion = "7.0.6";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -297,6 +297,8 @@ class FetchState {
297
297
  extraStyleHashes,
298
298
  extraScriptHashes,
299
299
  propagators: /* @__PURE__ */ new Set(),
300
+ routeHasPropagation: false,
301
+ pendingSlotEvaluations: [],
300
302
  templateDepth: 0
301
303
  },
302
304
  cspDestination: manifest.csp?.cspDestination ?? (routeData.prerender ? "meta" : "header"),
@@ -3,10 +3,21 @@ export interface HeadPropagator {
3
3
  init(result: SSRResult): unknown | Promise<unknown>;
4
4
  }
5
5
  /**
6
- * Runs all registered propagators and collects emitted head HTML strings.
6
+ * Runs all registered propagators and collects the head HTML they emit.
7
7
  *
8
- * This iterates the live `Set`, so propagators discovered during iteration
9
- * are also processed in the same pass.
8
+ * Components with head content are discovered as we go. Initializing one
9
+ * propagator can register more of them: a component marked `in-tree` renders
10
+ * its children, and one of those children may be a `self` component that emits
11
+ * styles. Slots add a second way to find them — a slot whose markup contains an
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.
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.
10
21
  *
11
22
  * @example
12
23
  * If a layout initializes and discovers a nested component that also emits
@@ -1,15 +1,25 @@
1
1
  async function collectPropagatedHeadParts(input) {
2
2
  const collectedHeadParts = [];
3
- const iterator = input.propagators.values();
3
+ const seen = /* @__PURE__ */ new Set();
4
+ const pendingSlotEvaluations = input.result._metadata?.pendingSlotEvaluations ?? [];
4
5
  while (true) {
5
- const { value, done } = iterator.next();
6
- if (done) {
7
- break;
6
+ if (pendingSlotEvaluations.length > 0) {
7
+ const batch = pendingSlotEvaluations.splice(0, pendingSlotEvaluations.length);
8
+ await Promise.all(batch);
9
+ continue;
8
10
  }
9
- const returnValue = await value.init(input.result);
10
- if (input.isHeadAndContent(returnValue) && returnValue.head) {
11
- collectedHeadParts.push(returnValue.head);
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;
12
21
  }
22
+ if (!progressed) break;
13
23
  }
14
24
  return collectedHeadParts;
15
25
  }
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"7.0.4"}`
272
+ `v${"7.0.6"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -1,6 +1,10 @@
1
1
  import { renderEndpoint } from "../../runtime/server/endpoint.js";
2
2
  import { renderPage } from "../../runtime/server/index.js";
3
3
  import { ASTRO_ERROR_HEADER } from "../constants.js";
4
+ import {
5
+ createCrossOriginForbiddenResponse,
6
+ isForbiddenCrossOriginRequest
7
+ } from "../app/origin-check.js";
4
8
  import { getCookiesFromResponse } from "../cookies/response.js";
5
9
  const EMPTY_SLOTS = Object.freeze({});
6
10
  class PagesHandler {
@@ -78,8 +82,12 @@ class PagesHandler {
78
82
  if (!state.routeData) {
79
83
  return new Response(null, { status: 404, headers: { [ASTRO_ERROR_HEADER]: "true" } });
80
84
  }
85
+ const ctx = state.getAPIContext();
86
+ if (this.#pipeline.manifest.checkOrigin && isForbiddenCrossOriginRequest(ctx.request, ctx.url, ctx.isPrerendered)) {
87
+ return createCrossOriginForbiddenResponse(ctx.request);
88
+ }
81
89
  try {
82
- return await this.handle(state, state.getAPIContext());
90
+ return await this.handle(state, ctx);
83
91
  } catch (err) {
84
92
  app.logger.error(null, err.stack || err.message || String(err));
85
93
  return app.renderError(state.request, {
@@ -11,10 +11,10 @@ function sanitizeParams(params) {
11
11
  }
12
12
  function getParameter(part, params) {
13
13
  if (part.spread) {
14
- return params[part.content.slice(3)] || "";
14
+ return params[part.content.slice(3)] ?? "";
15
15
  }
16
16
  if (part.dynamic) {
17
- if (!params[part.content]) {
17
+ if (params[part.content] === void 0) {
18
18
  throw new TypeError(`Missing parameter: ${part.content}`);
19
19
  }
20
20
  return params[part.content];
@@ -24,6 +24,14 @@ export declare function resolvePages(config: AstroConfig): URL;
24
24
  export declare function isPage(file: URL, settings: AstroSettings): boolean;
25
25
  export declare function isEndpoint(file: URL, settings: AstroSettings): boolean;
26
26
  export declare function resolveJsToTs(filePath: string): string;
27
+ /**
28
+ * Resolve a path that doesn't name a file on disk (e.g. produced by an
29
+ * extensionless import like `import { Counter } from './Counter'`) to the file
30
+ * Vite would load, by probing Vite's default extension order and directory
31
+ * `index` files. Returns the path unchanged when it already exists as a file
32
+ * or when no candidate is found.
33
+ */
34
+ export declare function resolveExtensionlessPath(filePath: string): string;
27
35
  /**
28
36
  * Set a default NODE_ENV so Vite doesn't set an incorrect default when loading the Astro config
29
37
  */
package/dist/core/util.js CHANGED
@@ -94,6 +94,28 @@ function resolveJsToTs(filePath) {
94
94
  }
95
95
  return filePath;
96
96
  }
97
+ const VITE_DEFAULT_RESOLVE_EXTENSIONS = [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json"];
98
+ function resolveExtensionlessPath(filePath) {
99
+ const stat = fs.statSync(filePath, { throwIfNoEntry: false });
100
+ if (stat?.isFile()) {
101
+ return filePath;
102
+ }
103
+ for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) {
104
+ const tryPath = filePath + ext;
105
+ if (fs.existsSync(tryPath)) {
106
+ return tryPath;
107
+ }
108
+ }
109
+ if (stat?.isDirectory()) {
110
+ for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) {
111
+ const tryPath = `${filePath}/index${ext}`;
112
+ if (fs.existsSync(tryPath)) {
113
+ return tryPath;
114
+ }
115
+ }
116
+ }
117
+ return filePath;
118
+ }
97
119
  function ensureProcessNodeEnv(defaultNodeEnv) {
98
120
  if (!process.env.NODE_ENV) {
99
121
  process.env.NODE_ENV = defaultNodeEnv;
@@ -106,6 +128,7 @@ export {
106
128
  isMarkdownFile,
107
129
  isPage,
108
130
  parseNpmName,
131
+ resolveExtensionlessPath,
109
132
  resolveJsToTs,
110
133
  resolvePages,
111
134
  unwrapId,
@@ -9,6 +9,9 @@ export declare function normalizePath(id: string): string;
9
9
  * Examples:
10
10
  * - `./components/Button.jsx` from `/app/src/pages/index.astro`
11
11
  * -> `/app/src/pages/components/Button.tsx` (when `.tsx` exists)
12
+ * - `../components/Counter` from `/app/src/pages/index.astro`
13
+ * -> `/app/src/components/Counter.tsx` (extensionless imports probe Vite's
14
+ * default extension order, then directory `index` files)
12
15
  * - `#components/react/Counter.tsx`
13
16
  * -> `/app/src/components/react/Counter.tsx` via package `imports`
14
17
  */
@@ -2,7 +2,13 @@ import { createRequire } from "node:module";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { prependForwardSlash, slash } from "../core/path.js";
5
- import { resolveJsToTs, unwrapId, VALID_ID_PREFIX, viteID } from "./util.js";
5
+ import {
6
+ resolveExtensionlessPath,
7
+ resolveJsToTs,
8
+ unwrapId,
9
+ VALID_ID_PREFIX,
10
+ viteID
11
+ } from "./util.js";
6
12
  const isWindows = typeof process !== "undefined" && process.platform === "win32";
7
13
  function normalizePath(id) {
8
14
  return path.posix.normalize(isWindows ? slash(id) : id);
@@ -10,7 +16,7 @@ function normalizePath(id) {
10
16
  function resolvePath(specifier, importer) {
11
17
  if (specifier.startsWith(".")) {
12
18
  const absoluteSpecifier = path.resolve(path.dirname(importer), specifier);
13
- return resolveJsToTs(normalizePath(absoluteSpecifier));
19
+ return resolveExtensionlessPath(resolveJsToTs(normalizePath(absoluteSpecifier)));
14
20
  } else if (specifier.startsWith("#")) {
15
21
  try {
16
22
  const resolved = createRequire(pathToFileURL(importer)).resolve(specifier);
@@ -191,6 +191,16 @@ function onPageLoad(cb) {
191
191
  }
192
192
  cb();
193
193
  });
194
+ new MutationObserver((mutations) => {
195
+ for (const mutation of mutations) {
196
+ for (const node of mutation.addedNodes) {
197
+ if (node instanceof Element && (node.tagName === "A" || node.querySelector?.("a"))) {
198
+ cb();
199
+ return;
200
+ }
201
+ }
202
+ }
203
+ }).observe(document.body, { childList: true, subtree: true });
194
204
  }
195
205
  function appendSpeculationRules(url, eagerness) {
196
206
  const script = document.createElement("script");
@@ -18,6 +18,9 @@ class AstroComponentInstance {
18
18
  for (const name in slots) {
19
19
  let didRender = false;
20
20
  let value = slots[name](result);
21
+ if (result._metadata.routeHasPropagation && isPromise(value)) {
22
+ result._metadata.pendingSlotEvaluations.push(value);
23
+ }
21
24
  this.slotValues[name] = () => {
22
25
  if (!didRender) {
23
26
  didRender = true;
@@ -103,7 +103,13 @@ function stringifyChunk(result, chunk) {
103
103
  out += stringifyChunk(result, instr);
104
104
  }
105
105
  }
106
- out += chunk.toString();
106
+ if (c.chunks.length) {
107
+ for (const part of c.chunks) {
108
+ out += typeof part === "string" ? part : stringifyChunk(result, part);
109
+ }
110
+ } else {
111
+ out += chunk.toString();
112
+ }
107
113
  return out;
108
114
  }
109
115
  return chunk.toString();