| /** | ||
| * True when a module id is a propagated-assets boundary. | ||
| * | ||
| * @example | ||
| * `/src/post.mdx?astroPropagatedAssets` stops CSS graph traversal so styles | ||
| * from one content render do not bleed into unrelated pages. | ||
| */ | ||
| export declare function isPropagatedAssetBoundary(id: string): boolean; |
| import { PROPAGATED_ASSET_FLAG } from "../../content/consts.js"; | ||
| function isPropagatedAssetBoundary(id) { | ||
| try { | ||
| return new URL(id, "file://").searchParams.has(PROPAGATED_ASSET_FLAG); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| export { | ||
| isPropagatedAssetBoundary | ||
| }; |
| import type { SSRResult } from '../../types/public/internal.js'; | ||
| export interface HeadPropagator { | ||
| init(result: SSRResult): unknown | Promise<unknown>; | ||
| } | ||
| /** | ||
| * Runs all registered propagators and collects emitted head HTML strings. | ||
| * | ||
| * This iterates the live `Set`, so propagators discovered during iteration | ||
| * are also processed in the same pass. | ||
| * | ||
| * @example | ||
| * If a layout initializes and discovers a nested component that also emits | ||
| * `<link rel="stylesheet">`, both head chunks are collected before flush. | ||
| */ | ||
| export declare function collectPropagatedHeadParts(input: { | ||
| propagators: Set<HeadPropagator>; | ||
| result: SSRResult; | ||
| isHeadAndContent: (value: unknown) => value is { | ||
| head: string; | ||
| }; | ||
| }): Promise<string[]>; |
| async function collectPropagatedHeadParts(input) { | ||
| const collectedHeadParts = []; | ||
| const iterator = input.propagators.values(); | ||
| while (true) { | ||
| const { value, done } = iterator.next(); | ||
| if (done) { | ||
| break; | ||
| } | ||
| const returnValue = await value.init(input.result); | ||
| if (input.isHeadAndContent(returnValue) && returnValue.head) { | ||
| collectedHeadParts.push(returnValue.head); | ||
| } | ||
| } | ||
| return collectedHeadParts; | ||
| } | ||
| export { | ||
| collectPropagatedHeadParts | ||
| }; |
| /** | ||
| * Returns true when source contains the `astro-head-inject` marker comment. | ||
| * | ||
| * @example | ||
| * `//! astro-head-inject` in a helper module marks parent importers as `in-tree`. | ||
| */ | ||
| export declare function hasHeadInjectComment(source: string): boolean; |
| const HEAD_INJECT_COMMENT_EXP = /(?:^\/\/|\/\/!)\s*astro-head-inject/; | ||
| function hasHeadInjectComment(source) { | ||
| return HEAD_INJECT_COMMENT_EXP.test(source); | ||
| } | ||
| export { | ||
| hasHeadInjectComment | ||
| }; |
| export type ModuleId = string; | ||
| export type ImporterGraph = Map<ModuleId, Set<ModuleId>>; | ||
| /** | ||
| * Computes all importer ancestors that should be treated as `in-tree`. | ||
| * | ||
| * @example | ||
| * If `Button.astro` is imported by `PostLayout.astro`, which is imported by | ||
| * `src/pages/blog.astro`, then seeding with `Button.astro` marks all three. | ||
| */ | ||
| export declare function computeInTreeAncestors(input: { | ||
| seeds: Iterable<ModuleId>; | ||
| importerGraph: ImporterGraph; | ||
| stopAt?: (id: ModuleId) => boolean; | ||
| }): Set<ModuleId>; | ||
| export declare function buildImporterGraphFromModuleInfo(moduleIds: Iterable<ModuleId>, getModuleInfo: (id: string) => { | ||
| importers: readonly string[]; | ||
| dynamicImporters: readonly string[]; | ||
| } | null): ImporterGraph; |
| function computeInTreeAncestors(input) { | ||
| const inTree = /* @__PURE__ */ new Set(); | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| function walk(moduleId) { | ||
| if (seen.has(moduleId)) return; | ||
| seen.add(moduleId); | ||
| if (input.stopAt?.(moduleId)) { | ||
| return; | ||
| } | ||
| inTree.add(moduleId); | ||
| for (const importer of input.importerGraph.get(moduleId) ?? []) { | ||
| walk(importer); | ||
| } | ||
| } | ||
| for (const seed of input.seeds) { | ||
| walk(seed); | ||
| } | ||
| return inTree; | ||
| } | ||
| function buildImporterGraphFromModuleInfo(moduleIds, getModuleInfo) { | ||
| const graph = /* @__PURE__ */ new Map(); | ||
| for (const id of moduleIds) { | ||
| const mod = getModuleInfo(id); | ||
| if (!mod) continue; | ||
| graph.set(id, /* @__PURE__ */ new Set([...mod.importers, ...mod.dynamicImporters])); | ||
| } | ||
| return graph; | ||
| } | ||
| export { | ||
| buildImporterGraphFromModuleInfo, | ||
| computeInTreeAncestors | ||
| }; |
| export interface HeadInstructionRenderState { | ||
| hasRenderedHead: boolean; | ||
| headInTree: boolean; | ||
| partial: boolean; | ||
| } | ||
| /** | ||
| * Policy for explicit `head` instructions. | ||
| * | ||
| * @example | ||
| * A page with `<head>{Astro.head}</head>` renders once, then blocks repeats. | ||
| */ | ||
| export declare function shouldRenderHeadInstruction(state: HeadInstructionRenderState): boolean; | ||
| /** | ||
| * Policy for fallback `maybe-head` instructions. | ||
| * | ||
| * @example | ||
| * A layout without `<head>` can inject styles with `maybe-head`, but only when | ||
| * no explicit `<head>` exists in the rendered tree. | ||
| */ | ||
| export declare function shouldRenderMaybeHeadInstruction(state: HeadInstructionRenderState): boolean; | ||
| /** Dispatches to the policy function for `head` or `maybe-head`. */ | ||
| export declare function shouldRenderInstruction(type: 'head' | 'maybe-head', state: HeadInstructionRenderState): boolean; |
| function shouldRenderHeadInstruction(state) { | ||
| return !state.hasRenderedHead && !state.partial; | ||
| } | ||
| function shouldRenderMaybeHeadInstruction(state) { | ||
| return !state.hasRenderedHead && !state.headInTree && !state.partial; | ||
| } | ||
| function shouldRenderInstruction(type, state) { | ||
| return type === "head" ? shouldRenderHeadInstruction(state) : shouldRenderMaybeHeadInstruction(state); | ||
| } | ||
| export { | ||
| shouldRenderHeadInstruction, | ||
| shouldRenderInstruction, | ||
| shouldRenderMaybeHeadInstruction | ||
| }; |
| import type { PropagationHint, SSRResult } from '../../types/public/internal.js'; | ||
| /** | ||
| * Resolves the effective propagation hint for a component. | ||
| * | ||
| * Priority: explicit factory hint -> component metadata -> `none`. | ||
| * | ||
| * @example | ||
| * A runtime-created head entry uses `propagation: 'self'`, so it propagates | ||
| * even when metadata says `none`. | ||
| */ | ||
| export declare function resolvePropagationHint(input: { | ||
| factoryHint: PropagationHint | undefined; | ||
| moduleId: string | undefined; | ||
| metadataLookup: (moduleId: string) => PropagationHint | undefined; | ||
| }): PropagationHint; | ||
| /** Returns true when a hint should register a component as a propagator. */ | ||
| export declare function isPropagatingHint(hint: PropagationHint): boolean; | ||
| /** | ||
| * Reads propagation metadata from an `SSRResult` + component factory. | ||
| * | ||
| * @example | ||
| * A compiled `.astro` module with metadata `in-tree` is treated as propagating | ||
| * when the factory does not set a stronger explicit hint. | ||
| */ | ||
| export declare function getPropagationHint(result: SSRResult, factory: { | ||
| propagation?: PropagationHint; | ||
| moduleId?: string | undefined; | ||
| }): PropagationHint; |
| function resolvePropagationHint(input) { | ||
| const explicitHint = input.factoryHint ?? "none"; | ||
| if (explicitHint !== "none") { | ||
| return explicitHint; | ||
| } | ||
| if (!input.moduleId) { | ||
| return "none"; | ||
| } | ||
| return input.metadataLookup(input.moduleId) ?? "none"; | ||
| } | ||
| function isPropagatingHint(hint) { | ||
| return hint === "self" || hint === "in-tree"; | ||
| } | ||
| function getPropagationHint(result, factory) { | ||
| return resolvePropagationHint({ | ||
| factoryHint: factory.propagation, | ||
| moduleId: factory.moduleId, | ||
| metadataLookup: (moduleId) => result.componentMetadata.get(moduleId)?.propagation | ||
| }); | ||
| } | ||
| export { | ||
| getPropagationHint, | ||
| isPropagatingHint, | ||
| resolvePropagationHint | ||
| }; |
| import { type HeadInstructionRenderState } from '../../../../core/head-propagation/policy.js'; | ||
| import type { SSRResult } from '../../../../types/public/internal.js'; | ||
| import type { AstroComponentFactory } from '../astro/factory.js'; | ||
| /** Facade helper used by runtime adapters to read effective hint resolution. */ | ||
| export declare function getPropagationHint(result: SSRResult, factory: AstroComponentFactory): import("../../../../types/public/internal.js").PropagationHint; | ||
| /** | ||
| * Registers an instance in the propagation set when its hint requires buffering. | ||
| * | ||
| * @example | ||
| * A runtime-created component with `propagation: 'self'` is registered so its | ||
| * styles can be collected before head flush. | ||
| */ | ||
| export declare function registerIfPropagating(result: SSRResult, factory: AstroComponentFactory, instance: { | ||
| init(result: SSRResult): unknown | Promise<unknown>; | ||
| }): void; | ||
| export declare function bufferPropagatedHead(result: SSRResult): Promise<void>; | ||
| /** Facade helper for render instruction gating (`head` vs `maybe-head`). */ | ||
| export declare function shouldRenderInstruction(type: 'head' | 'maybe-head', state: HeadInstructionRenderState): boolean; | ||
| /** Projects `SSRResult` into the minimal state needed by instruction policy. */ | ||
| export declare function getInstructionRenderState(result: SSRResult): HeadInstructionRenderState; |
| import { collectPropagatedHeadParts } from "../../../../core/head-propagation/buffer.js"; | ||
| import { | ||
| getPropagationHint as getHint, | ||
| isPropagatingHint | ||
| } from "../../../../core/head-propagation/resolver.js"; | ||
| import { | ||
| shouldRenderInstruction as shouldRenderInstructionByPolicy | ||
| } from "../../../../core/head-propagation/policy.js"; | ||
| import { isHeadAndContent } from "../astro/head-and-content.js"; | ||
| function getPropagationHint(result, factory) { | ||
| return getHint(result, factory); | ||
| } | ||
| function registerIfPropagating(result, factory, instance) { | ||
| if (factory.propagation === "self" || factory.propagation === "in-tree") { | ||
| result._metadata.propagators.add( | ||
| instance | ||
| ); | ||
| return; | ||
| } | ||
| if (factory.moduleId) { | ||
| const hint = result.componentMetadata.get(factory.moduleId)?.propagation; | ||
| if (isPropagatingHint(hint ?? "none")) { | ||
| result._metadata.propagators.add( | ||
| instance | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| async function bufferPropagatedHead(result) { | ||
| const collected = await collectPropagatedHeadParts({ | ||
| propagators: result._metadata.propagators, | ||
| result, | ||
| isHeadAndContent | ||
| }); | ||
| result._metadata.extraHead.push(...collected); | ||
| } | ||
| function shouldRenderInstruction(type, state) { | ||
| return shouldRenderInstructionByPolicy(type, state); | ||
| } | ||
| function getInstructionRenderState(result) { | ||
| return { | ||
| hasRenderedHead: result._metadata.hasRenderedHead, | ||
| headInTree: result._metadata.headInTree, | ||
| partial: result.partial | ||
| }; | ||
| } | ||
| export { | ||
| bufferPropagatedHead, | ||
| getInstructionRenderState, | ||
| getPropagationHint, | ||
| registerIfPropagating, | ||
| shouldRenderInstruction | ||
| }; |
+30
-40
@@ -33,32 +33,32 @@ /// <reference types="vite/types/import-meta.d.ts" /> | ||
| declare module 'astro:assets' { | ||
| // Exporting things one by one is a bit cumbersome, not sure if there's a better way - erika, 2023-02-03 | ||
| type AstroAssets = { | ||
| // getImage's type here is different from the internal function since the Vite module implicitly pass the service config | ||
| /** | ||
| * Get an optimized image and the necessary attributes to render it. | ||
| * | ||
| * **Example** | ||
| * ```astro | ||
| * --- | ||
| * import { getImage } from 'astro:assets'; | ||
| * import originalImage from '../assets/image.png'; | ||
| * | ||
| * const optimizedImage = await getImage({src: originalImage, width: 1280 }); | ||
| * --- | ||
| * <img src={optimizedImage.src} {...optimizedImage.attributes} /> | ||
| * ``` | ||
| * | ||
| * This is functionally equivalent to using the `<Image />` component, as the component calls this function internally. | ||
| */ | ||
| getImage: ( | ||
| options: import('./dist/assets/types.js').UnresolvedImageTransform, | ||
| ) => Promise<import('./dist/assets/types.js').GetImageResult>; | ||
| imageConfig: import('./dist/types/public/config.js').AstroConfig['image']; | ||
| getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; | ||
| inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize; | ||
| Image: typeof import('./components/Image.astro').default; | ||
| Picture: typeof import('./components/Picture.astro').default; | ||
| Font: typeof import('./components/Font.astro').default; | ||
| fontData: Record<import('astro:assets').CssVariable, Array<import('astro:assets').FontData>>; | ||
| }; | ||
| // getImage's type here is different from the internal function since the Vite module implicitly pass the service config | ||
| /** | ||
| * Get an optimized image and the necessary attributes to render it. | ||
| * | ||
| * **Example** | ||
| * ```astro | ||
| * --- | ||
| * import { getImage } from 'astro:assets'; | ||
| * import originalImage from '../assets/image.png'; | ||
| * | ||
| * const optimizedImage = await getImage({src: originalImage, width: 1280 }); | ||
| * --- | ||
| * <img src={optimizedImage.src} {...optimizedImage.attributes} /> | ||
| * ``` | ||
| * | ||
| * This is functionally equivalent to using the `<Image />` component, as the component calls this function internally. | ||
| */ | ||
| export const getImage: ( | ||
| options: import('./dist/assets/types.js').UnresolvedImageTransform, | ||
| ) => Promise<import('./dist/assets/types.js').GetImageResult>; | ||
| export const imageConfig: import('./dist/types/public/config.js').AstroConfig['image']; | ||
| export const getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; | ||
| export const inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize; | ||
| export const Image: typeof import('./components/Image.astro').default; | ||
| export const Picture: typeof import('./components/Picture.astro').default; | ||
| export const Font: typeof import('./components/Font.astro').default; | ||
| export const fontData: Record< | ||
| import('astro:assets').CssVariable, | ||
| Array<import('astro:assets').FontData> | ||
| >; | ||
@@ -76,12 +76,2 @@ type ImgAttributes = import('./dist/type-utils.js').WithRequired< | ||
| >; | ||
| export const { | ||
| getImage, | ||
| getConfiguredImageService, | ||
| imageConfig, | ||
| Image, | ||
| Picture, | ||
| Font, | ||
| inferRemoteSize, | ||
| fontData, | ||
| }: AstroAssets; | ||
| } | ||
@@ -88,0 +78,0 @@ |
@@ -98,8 +98,7 @@ import fs, { readFileSync } from "node:fs"; | ||
| const JSONData = JSON.parse(readFileSync(cachedMetaFileURL, "utf-8")); | ||
| if (!JSONData.expires) { | ||
| try { | ||
| await fs.promises.unlink(cachedFileURL); | ||
| } catch { | ||
| } | ||
| await fs.promises.unlink(cachedMetaFileURL); | ||
| if (typeof JSONData.expires !== "number") { | ||
| await Promise.allSettled([ | ||
| fs.promises.unlink(cachedFileURL), | ||
| fs.promises.unlink(cachedMetaFileURL) | ||
| ]); | ||
| throw new Error( | ||
@@ -118,5 +117,3 @@ `Malformed cache entry for ${filepath}, cache will be regenerated for this file.` | ||
| await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE); | ||
| return { | ||
| cached: "hit" | ||
| }; | ||
| return { cached: "hit" }; | ||
| } | ||
@@ -129,11 +126,9 @@ if (JSONData.etag || JSONData.lastModified) { | ||
| }); | ||
| if (revalidatedData.data.length) { | ||
| if (revalidatedData.data !== null) { | ||
| originalImage = revalidatedData; | ||
| } else { | ||
| await writeCacheMetaFile(cachedMetaFileURL, revalidatedData, env); | ||
| await fs.promises.copyFile( | ||
| cachedFileURL, | ||
| finalFileURL, | ||
| fs.constants.COPYFILE_FICLONE | ||
| ); | ||
| await Promise.all([ | ||
| writeCacheMetaFile(cachedMetaFileURL, revalidatedData, env), | ||
| fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE) | ||
| ]); | ||
| return { cached: "revalidated" }; | ||
@@ -150,4 +145,6 @@ } | ||
| } | ||
| await fs.promises.unlink(cachedFileURL); | ||
| await fs.promises.unlink(cachedMetaFileURL); | ||
| await Promise.allSettled([ | ||
| fs.promises.unlink(cachedFileURL), | ||
| fs.promises.unlink(cachedMetaFileURL) | ||
| ]); | ||
| } | ||
@@ -226,3 +223,4 @@ } catch (e) { | ||
| lastModified: resultData.lastModified | ||
| }) | ||
| }), | ||
| "utf-8" | ||
| ); | ||
@@ -229,0 +227,0 @@ } catch (e) { |
@@ -7,3 +7,3 @@ export type RemoteCacheEntry = { | ||
| }; | ||
| export declare function loadRemoteImage(src: string): Promise<{ | ||
| export declare function loadRemoteImage(src: string, fetchFn?: typeof fetch): Promise<{ | ||
| data: Buffer<ArrayBuffer>; | ||
@@ -21,3 +21,3 @@ expires: number; | ||
| * @param revalidationData - an object containing the stored Entity-Tag of the cached asset and/or the Last Modified time | ||
| * @returns An ImageData object containing the asset data, a new expiry time, and the asset's etag. The data buffer will be empty if the asset was not modified. | ||
| * @returns An object containing the refreshed expiry time and cache headers. `data` will be a `Buffer` of the new image if the asset was modified (200 OK), or `null` if the cached version is still valid (304 Not Modified). | ||
| */ | ||
@@ -27,4 +27,4 @@ export declare function revalidateRemoteImage(src: string, revalidationData: { | ||
| lastModified?: string; | ||
| }): Promise<{ | ||
| data: Buffer<ArrayBuffer>; | ||
| }, fetchFn?: typeof fetch): Promise<{ | ||
| data: Buffer<ArrayBuffer> | null; | ||
| expires: number; | ||
@@ -31,0 +31,0 @@ etag: string | undefined; |
| import CachePolicy from "http-cache-semantics"; | ||
| async function loadRemoteImage(src) { | ||
| async function loadRemoteImage(src, fetchFn = globalThis.fetch) { | ||
| const req = new Request(src); | ||
| const res = await fetch(req, { redirect: "manual" }); | ||
| const res = await fetchFn(req, { redirect: "manual" }); | ||
| if (res.status >= 300 && res.status < 400) { | ||
@@ -22,3 +22,3 @@ throw new Error(`Failed to load remote image ${src}. The request was redirected.`); | ||
| } | ||
| async function revalidateRemoteImage(src, revalidationData) { | ||
| async function revalidateRemoteImage(src, revalidationData, fetchFn = globalThis.fetch) { | ||
| const headers = { | ||
@@ -29,7 +29,9 @@ ...revalidationData.etag && { "If-None-Match": revalidationData.etag }, | ||
| const req = new Request(src, { headers, cache: "no-cache" }); | ||
| const res = await fetch(req, { redirect: "manual" }); | ||
| if (res.status >= 300 && res.status < 400) { | ||
| throw new Error(`Failed to revalidate cached remote image ${src}. The request was redirected.`); | ||
| } | ||
| const res = await fetchFn(req, { redirect: "manual" }); | ||
| if (!res.ok && res.status !== 304) { | ||
| if (res.status >= 300 && res.status < 400) { | ||
| throw new Error( | ||
| `Failed to revalidate cached remote image ${src}. The request was redirected.` | ||
| ); | ||
| } | ||
| throw new Error( | ||
@@ -41,3 +43,3 @@ `Failed to revalidate cached remote image ${src}. The request did not return a 200 OK / 304 NOT MODIFIED response. (received ${res.status} ${res.statusText})` | ||
| if (res.ok && !data.length) { | ||
| return await loadRemoteImage(src); | ||
| return await loadRemoteImage(src, fetchFn); | ||
| } | ||
@@ -49,7 +51,7 @@ const policy = new CachePolicy( | ||
| ) | ||
| // 304 responses themselves are not cacheable, so just pretend to get the refreshed TTL | ||
| // 304 responses are not cacheable, so just use its headers to get the refreshed TTL | ||
| ); | ||
| const expires = policy.storable() ? policy.timeToLive() : 0; | ||
| return { | ||
| data, | ||
| data: res.ok ? data : null, | ||
| expires: Date.now() + expires, | ||
@@ -56,0 +58,0 @@ // While servers should respond with the same headers as a 200 response, if they don't we should reuse the stored value |
@@ -20,2 +20,5 @@ class DevFontFileIdGenerator { | ||
| } | ||
| #formatStyle(style) { | ||
| return style?.replace(/\s+/g, "-"); | ||
| } | ||
| generate({ | ||
@@ -30,3 +33,3 @@ cssVariable, | ||
| this.#formatWeight(font.weight), | ||
| font.style, | ||
| this.#formatStyle(font.style), | ||
| font.meta?.subset, | ||
@@ -33,0 +36,0 @@ `${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}` |
@@ -54,2 +54,3 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| let fontTypeExtractor = null; | ||
| let built = false; | ||
| const cleanup = () => { | ||
@@ -67,2 +68,5 @@ componentDataByCssVariable = null; | ||
| async buildStart() { | ||
| if (sync) { | ||
| return; | ||
| } | ||
| const { root } = settings.config; | ||
@@ -257,2 +261,5 @@ const hasher = await XxhashHasher.create(); | ||
| async buildEnd() { | ||
| if (built) { | ||
| return; | ||
| } | ||
| if (sync || !settings.config.fonts?.length || !isBuild) { | ||
@@ -288,2 +295,3 @@ cleanup(); | ||
| cleanup(); | ||
| built = true; | ||
| } | ||
@@ -290,0 +298,0 @@ } |
@@ -1,3 +0,3 @@ | ||
| import type { ResizeOptions, SharpOptions } from 'sharp'; | ||
| import { type LocalImageService } from './service.js'; | ||
| import type { AvifOptions, JpegOptions, PngOptions, ResizeOptions, SharpOptions, WebpOptions } from 'sharp'; | ||
| import { type BaseServiceTransform, type LocalImageService } from './service.js'; | ||
| export interface SharpImageServiceConfig { | ||
@@ -12,4 +12,23 @@ /** | ||
| kernel?: ResizeOptions['kernel']; | ||
| /** | ||
| * The default encoder options passed to `sharp().jpeg()`. | ||
| */ | ||
| jpeg?: JpegOptions; | ||
| /** | ||
| * The default encoder options passed to `sharp().png()`. | ||
| */ | ||
| png?: PngOptions; | ||
| /** | ||
| * The default encoder options passed to `sharp().webp()`. | ||
| */ | ||
| webp?: WebpOptions; | ||
| /** | ||
| * The default encoder options passed to `sharp().avif()`. | ||
| */ | ||
| avif?: AvifOptions; | ||
| } | ||
| export declare function resolveSharpEncoderOptions(transform: Pick<BaseServiceTransform, 'format' | 'quality'>, inputFormat: string | undefined, serviceConfig?: SharpImageServiceConfig): JpegOptions | PngOptions | WebpOptions | AvifOptions | { | ||
| quality?: number; | ||
| } | undefined; | ||
| declare const sharpService: LocalImageService<SharpImageServiceConfig>; | ||
| export default sharpService; |
@@ -13,2 +13,43 @@ import { AstroError, AstroErrorData } from "../../core/errors/index.js"; | ||
| }; | ||
| function resolveSharpQuality(quality) { | ||
| if (!quality) return void 0; | ||
| const parsedQuality = parseQuality(quality); | ||
| if (typeof parsedQuality === "number") { | ||
| return parsedQuality; | ||
| } | ||
| return quality in qualityTable ? qualityTable[quality] : void 0; | ||
| } | ||
| function resolveSharpEncoderOptions(transform, inputFormat, serviceConfig = {}) { | ||
| const quality = resolveSharpQuality(transform.quality); | ||
| switch (transform.format) { | ||
| case "jpg": | ||
| case "jpeg": | ||
| return { | ||
| ...serviceConfig.jpeg, | ||
| ...quality === void 0 ? {} : { quality } | ||
| }; | ||
| case "png": | ||
| return { | ||
| ...serviceConfig.png, | ||
| ...quality === void 0 ? {} : { quality } | ||
| }; | ||
| case "webp": { | ||
| const webpOptions = { | ||
| ...serviceConfig.webp, | ||
| ...quality === void 0 ? {} : { quality } | ||
| }; | ||
| if (inputFormat === "gif") { | ||
| webpOptions.loop ??= 0; | ||
| } | ||
| return webpOptions; | ||
| } | ||
| case "avif": | ||
| return { | ||
| ...serviceConfig.avif, | ||
| ...quality === void 0 ? {} : { quality } | ||
| }; | ||
| default: | ||
| return quality === void 0 ? void 0 : { quality }; | ||
| } | ||
| } | ||
| async function loadSharp() { | ||
@@ -79,15 +120,15 @@ let sharpImport; | ||
| if (transform.format) { | ||
| let quality = void 0; | ||
| if (transform.quality) { | ||
| const parsedQuality = parseQuality(transform.quality); | ||
| if (typeof parsedQuality === "number") { | ||
| quality = parsedQuality; | ||
| } else { | ||
| quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0; | ||
| } | ||
| } | ||
| const encoderOptions = resolveSharpEncoderOptions(transform, format, config.service.config); | ||
| if (transform.format === "webp" && format === "gif") { | ||
| result.webp({ quality: typeof quality === "number" ? quality : void 0, loop: 0 }); | ||
| result.webp(encoderOptions); | ||
| } else if (transform.format === "webp") { | ||
| result.webp(encoderOptions); | ||
| } else if (transform.format === "png") { | ||
| result.png(encoderOptions); | ||
| } else if (transform.format === "avif") { | ||
| result.avif(encoderOptions); | ||
| } else if (transform.format === "jpeg" || transform.format === "jpg") { | ||
| result.jpeg(encoderOptions); | ||
| } else { | ||
| result.toFormat(transform.format, { quality }); | ||
| result.toFormat(transform.format, encoderOptions); | ||
| } | ||
@@ -105,3 +146,4 @@ } | ||
| export { | ||
| sharp_default as default | ||
| sharp_default as default, | ||
| resolveSharpEncoderOptions | ||
| }; |
@@ -125,3 +125,6 @@ import { extname } from "node:path"; | ||
| export const getImage = async () => { | ||
| throw new AstroError(AstroErrorData.GetImageNotUsedOnServer); | ||
| throw new AstroError( | ||
| AstroErrorData.GetImageNotUsedOnServer.message, | ||
| AstroErrorData.GetImageNotUsedOnServer.hint, | ||
| ); | ||
| };`; | ||
@@ -128,0 +131,0 @@ return { |
@@ -210,2 +210,8 @@ import fsMod, { existsSync, promises as fs } from "node:fs"; | ||
| }); | ||
| await updatePackageJsonOverrides({ | ||
| configURL, | ||
| flags, | ||
| logger, | ||
| overrides: { vite: "^7" } | ||
| }); | ||
| } | ||
@@ -605,2 +611,50 @@ if (integrations.find((integration) => integration.id === "tailwind")) { | ||
| } | ||
| async function updatePackageJsonOverrides({ | ||
| configURL, | ||
| flags, | ||
| logger, | ||
| overrides | ||
| }) { | ||
| const pkgURL = new URL("./package.json", configURL); | ||
| if (!existsSync(pkgURL)) { | ||
| logger.debug("add", "No package.json found, skipping overrides update"); | ||
| return 0 /* none */; | ||
| } | ||
| const pkgPath = fileURLToPath(pkgURL); | ||
| const input = await fs.readFile(pkgPath, { encoding: "utf-8" }); | ||
| const pkgJson = JSON.parse(input); | ||
| pkgJson.overrides ??= {}; | ||
| let hasChanges = false; | ||
| for (const [name, range] of Object.entries(overrides)) { | ||
| if (!(name in pkgJson.overrides)) { | ||
| pkgJson.overrides[name] = range; | ||
| hasChanges = true; | ||
| } | ||
| } | ||
| if (!hasChanges) { | ||
| return 0 /* none */; | ||
| } | ||
| const output = JSON.stringify(pkgJson, null, 2); | ||
| const diff = getDiffContent(input, output); | ||
| if (!diff) { | ||
| return 0 /* none */; | ||
| } | ||
| logger.info( | ||
| "SKIP_FORMAT", | ||
| ` | ||
| ${magenta("Astro will add the following overrides to your package.json:")}` | ||
| ); | ||
| clack.box(diff, "package.json", { | ||
| rounded: true, | ||
| withGuide: false, | ||
| width: "auto" | ||
| }); | ||
| if (await askToContinue({ flags, logger })) { | ||
| await fs.writeFile(pkgPath, output, { encoding: "utf-8" }); | ||
| logger.debug("add", "Updated package.json overrides"); | ||
| return 1 /* updated */; | ||
| } else { | ||
| return 2 /* cancelled */; | ||
| } | ||
| } | ||
| async function updatePackageJsonScripts({ | ||
@@ -607,0 +661,0 @@ configURL, |
| class BuildTimeAstroVersionProvider { | ||
| // Injected during the build through esbuild define | ||
| version = "6.0.8"; | ||
| version = "6.1.0"; | ||
| } | ||
@@ -5,0 +5,0 @@ export { |
@@ -195,3 +195,3 @@ import { existsSync, promises as fs } from "node:fs"; | ||
| } | ||
| if (previousAstroVersion && previousAstroVersion !== "6.0.8") { | ||
| if (previousAstroVersion && previousAstroVersion !== "6.1.0") { | ||
| logger.info("Astro version changed"); | ||
@@ -204,4 +204,4 @@ shouldClear = true; | ||
| } | ||
| if ("6.0.8") { | ||
| this.#store.metaStore().set("astro-version", "6.0.8"); | ||
| if ("6.1.0") { | ||
| this.#store.metaStore().set("astro-version", "6.1.0"); | ||
| } | ||
@@ -208,0 +208,0 @@ if (currentConfigDigest) { |
| export { attachContentServerListeners } from './server-listeners.js'; | ||
| export { createContentTypesGenerator } from './types-generator.js'; | ||
| export { getContentPaths, hasAssetPropagationFlag } from './utils.js'; | ||
| export { getContentPaths } from './utils.js'; | ||
| export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js'; | ||
| export { astroContentImportPlugin } from './vite-plugin-content-imports.js'; | ||
| export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js'; |
| import { attachContentServerListeners } from "./server-listeners.js"; | ||
| import { createContentTypesGenerator } from "./types-generator.js"; | ||
| import { getContentPaths, hasAssetPropagationFlag } from "./utils.js"; | ||
| import { getContentPaths } from "./utils.js"; | ||
| import { astroContentAssetPropagationPlugin } from "./vite-plugin-content-assets.js"; | ||
@@ -13,4 +13,3 @@ import { astroContentImportPlugin } from "./vite-plugin-content-imports.js"; | ||
| createContentTypesGenerator, | ||
| getContentPaths, | ||
| hasAssetPropagationFlag | ||
| getContentPaths | ||
| }; |
| import type { MarkdownHeading } from '@astrojs/markdown-remark'; | ||
| import * as z from 'zod/v4'; | ||
| import type * as zCore from 'zod/v4/core'; | ||
| import type { ImageMetadata } from '../assets/types.js'; | ||
| import { type AstroComponentFactory } from '../runtime/server/index.js'; | ||
@@ -70,2 +71,3 @@ import type { LiveDataCollectionResult, LiveDataEntryResult } from '../types/public/content.js'; | ||
| }; | ||
| export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>): T; | ||
| export declare function renderEntry(entry: DataEntry): Promise<RenderResult>; | ||
@@ -72,0 +74,0 @@ export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{ |
@@ -557,3 +557,4 @@ import { escape } from "html-escaper"; | ||
| defineLiveCollection, | ||
| renderEntry | ||
| renderEntry, | ||
| updateImageReferencesInData | ||
| }; |
@@ -453,5 +453,9 @@ import * as path from "node:path"; | ||
| if (zodSchemaForJson instanceof z.ZodObject) { | ||
| const existingMeta = z.globalRegistry.get(zodSchemaForJson); | ||
| zodSchemaForJson = zodSchemaForJson.extend({ | ||
| $schema: z.string().optional() | ||
| }); | ||
| if (existingMeta) { | ||
| z.globalRegistry.add(zodSchemaForJson, existingMeta); | ||
| } | ||
| } | ||
@@ -458,0 +462,0 @@ try { |
@@ -190,3 +190,2 @@ import fsMod from 'node:fs'; | ||
| }): Promise<string>; | ||
| export declare function hasAssetPropagationFlag(id: string): boolean; | ||
| /** | ||
@@ -193,0 +192,0 @@ * Unlike `path.posix.relative`, this function will accept a platform path and return a posix path. |
@@ -18,4 +18,3 @@ import fsMod from "node:fs"; | ||
| IMAGE_IMPORT_PREFIX, | ||
| LIVE_CONTENT_TYPE, | ||
| PROPAGATED_ASSET_FLAG | ||
| LIVE_CONTENT_TYPE | ||
| } from "./consts.js"; | ||
@@ -624,9 +623,2 @@ import { glob, secretLegacyFlag } from "./loaders/glob.js"; | ||
| } | ||
| function hasAssetPropagationFlag(id) { | ||
| try { | ||
| return new URL(id, "file://").searchParams.has(PROPAGATED_ASSET_FLAG); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function posixifyPath(filePath) { | ||
@@ -679,3 +671,2 @@ return filePath.split(path.sep).join("/"); | ||
| globalContentConfigObserver, | ||
| hasAssetPropagationFlag, | ||
| hasContentFlag, | ||
@@ -682,0 +673,0 @@ isDeferredModule, |
@@ -13,2 +13,7 @@ import type { RouteData } from '../../../types/public/index.js'; | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change. | ||
| */ | ||
| clearMiddleware(): void; | ||
| /** | ||
| * Updates the routes list when files change during development. | ||
@@ -15,0 +20,0 @@ * Called via HMR when new pages are added/removed. |
@@ -28,2 +28,9 @@ import { MiddlewareNoDataOrNextCalled, MiddlewareNotAResponse } from "../../errors/errors-data.js"; | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change. | ||
| */ | ||
| clearMiddleware() { | ||
| this.pipeline.clearMiddleware(); | ||
| } | ||
| /** | ||
| * Updates the routes list when files change during development. | ||
@@ -30,0 +37,0 @@ * Called via HMR when new pages are added/removed. |
@@ -26,2 +26,6 @@ import { manifest } from "virtual:astro:manifest"; | ||
| }); | ||
| import.meta.hot.on("astro:middleware-updated", () => { | ||
| if (!currentDevApp) return; | ||
| currentDevApp.clearMiddleware(); | ||
| }); | ||
| } | ||
@@ -28,0 +32,0 @@ return currentDevApp; |
@@ -7,3 +7,7 @@ import fs from "node:fs"; | ||
| import { App } from "./app.js"; | ||
| import { validateForwardedHeaders, validateHost } from "./validate-headers.js"; | ||
| import { | ||
| getFirstForwardedValue, | ||
| validateForwardedHeaders, | ||
| validateHost | ||
| } from "./validate-headers.js"; | ||
| function createRequest(req, { | ||
@@ -17,5 +21,2 @@ skipBody = false, | ||
| const isEncrypted = "encrypted" in req.socket && req.socket.encrypted; | ||
| const getFirstForwardedValue = (multiValueHeader) => { | ||
| return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0]; | ||
| }; | ||
| const providedProtocol = isEncrypted ? "https" : "http"; | ||
@@ -22,0 +23,0 @@ const untrustedHostname = req.headers.host ?? req.headers[":authority"]; |
| import { type RemotePattern } from '@astrojs/internal-helpers/remote'; | ||
| /** | ||
| * Parses a potentially comma-separated multi-value header (as produced by | ||
| * proxy chains) and returns the first value, trimmed of whitespace. | ||
| * Returns `undefined` when the header is absent or empty. | ||
| */ | ||
| export declare function getFirstForwardedValue(multiValueHeader: string | string[] | undefined): string | undefined; | ||
| /** | ||
| * Validate a host against allowedDomains. | ||
@@ -4,0 +10,0 @@ * Returns the host only if it matches an allowed pattern; otherwise, undefined. |
| import { matchPattern } from "@astrojs/internal-helpers/remote"; | ||
| function getFirstForwardedValue(multiValueHeader) { | ||
| return multiValueHeader?.toString().split(",").map((e) => e.trim())[0]; | ||
| } | ||
| function sanitizeHost(hostname) { | ||
@@ -78,4 +81,5 @@ if (!hostname) return void 0; | ||
| export { | ||
| getFirstForwardedValue, | ||
| validateForwardedHeaders, | ||
| validateHost | ||
| }; |
@@ -127,2 +127,7 @@ import type { $ZodType } from 'zod/v4/core'; | ||
| getMiddleware(): Promise<MiddlewareHandler>; | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change during development. | ||
| */ | ||
| clearMiddleware(): void; | ||
| getActions(): Promise<SSRActions>; | ||
@@ -129,0 +134,0 @@ getSessionDriver(): Promise<SessionDriverFactory | null>; |
@@ -81,2 +81,9 @@ import { NOOP_ACTIONS_MOD } from "../actions/noop-actions.js"; | ||
| } | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change during development. | ||
| */ | ||
| clearMiddleware() { | ||
| this.resolvedMiddleware = void 0; | ||
| } | ||
| async getActions() { | ||
@@ -83,0 +90,0 @@ if (this.resolvedActions) { |
@@ -0,3 +1,50 @@ | ||
| import type { Logger } from '../logger/core.js'; | ||
| import type { AstroPrerenderer, RouteToHeaders } from '../../types/public/index.js'; | ||
| import type { RouteData } from '../../types/public/internal.js'; | ||
| import { type BuildInternals } from './internal.js'; | ||
| import type { StaticBuildOptions } from './types.js'; | ||
| export declare function generatePages(options: StaticBuildOptions, internals: BuildInternals, prerenderOutputDir: URL): Promise<void>; | ||
| /** | ||
| * The result of rendering a single path, ready to be written to the filesystem. | ||
| * `null` means no file should be written (empty body, redirect skipped, or a file with the | ||
| * same output path already exists in `publicDir`). | ||
| */ | ||
| export interface RenderPathResult { | ||
| body: string | Uint8Array; | ||
| outFile: URL; | ||
| outFolder: URL; | ||
| } | ||
| interface RenderToPathPayload { | ||
| prerenderer: AstroPrerenderer; | ||
| pathname: string; | ||
| route: RouteData; | ||
| options: StaticBuildOptions; | ||
| routeToHeaders?: RouteToHeaders; | ||
| logger: Logger; | ||
| } | ||
| /** | ||
| * Renders a single prerendered path to an in-memory result. | ||
| * | ||
| * This function is intentionally free of filesystem writes — it only calls | ||
| * `prerenderer.render()` and computes output paths. The caller is responsible | ||
| * for persisting the returned `body` to disk (or any other destination). | ||
| * | ||
| * Returning `null` signals that no output file should be created for this path: | ||
| * - the response body was empty | ||
| * - the redirect was suppressed by `config.build.redirects` | ||
| * - a file with the same output path already exists in `publicDir` (public files | ||
| * take priority over generated pages, so the generated page is skipped) | ||
| * | ||
| * @param params | ||
| * @param params.prerenderer - The prerenderer used to obtain a `Response` for the path. | ||
| * @param params.pathname - The URL pathname being rendered (e.g. `/about`). | ||
| * @param params.route - Route data for the page being rendered. | ||
| * @param params.options - Build options; `options.fsMod` is used to check whether a | ||
| * file already exists in `publicDir` at the output path. | ||
| * @param [params.routeToHeaders=new Map()] - Mutable map populated with response headers when | ||
| * the adapter requests static-header tracking. Callers that do | ||
| * not need to inspect the headers after the call can omit this. | ||
| * @param params.logger - Logger instance. | ||
| */ | ||
| export declare function renderPath({ prerenderer, pathname, route, options, routeToHeaders, logger, }: RenderToPathPayload): Promise<RenderPathResult | null>; | ||
| export {}; |
@@ -1,4 +0,3 @@ | ||
| import fs from "node:fs"; | ||
| import nodeFs from "node:fs"; | ||
| import os from "node:os"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import PLimit from "p-limit"; | ||
@@ -208,10 +207,11 @@ import PQueue from "p-queue"; | ||
| const THRESHOLD_SLOW_RENDER_TIME_MS = 500; | ||
| async function generatePathWithPrerenderer(prerenderer, pathname, route, options, routeToHeaders, logger) { | ||
| const timeStart = performance.now(); | ||
| async function renderPath({ | ||
| prerenderer, | ||
| pathname, | ||
| route, | ||
| options, | ||
| routeToHeaders = /* @__PURE__ */ new Map(), | ||
| logger | ||
| }) { | ||
| const { config } = options.settings; | ||
| const filePath = getOutputFilename(config.build.format, pathname, route); | ||
| logger.info(null, ` ${colors.blue("\u251C\u2500")} ${colors.dim(filePath)}`, false); | ||
| if (route.type === "page") { | ||
| addPageName(pathname, options); | ||
| } | ||
| if (route.type === "fallback" && route.pathname !== "/") { | ||
@@ -232,3 +232,3 @@ if (options.routesList.routes.some((routeData) => { | ||
| })) { | ||
| return; | ||
| return null; | ||
| } | ||
@@ -265,4 +265,3 @@ } | ||
| if (routeIsRedirect(route) && !config.build.redirects) { | ||
| logRenderTime(logger, timeStart, false); | ||
| return; | ||
| return null; | ||
| } | ||
@@ -287,4 +286,3 @@ const locationSite = getRedirectLocationOrThrow(responseHeaders); | ||
| if (!response.body) { | ||
| logRenderTime(logger, timeStart, true); | ||
| return; | ||
| return null; | ||
| } | ||
@@ -305,5 +303,27 @@ body = Buffer.from(await response.arrayBuffer()); | ||
| } | ||
| if (checkPublicConflict(outFile, route, options.settings, logger)) return; | ||
| await fs.promises.mkdir(outFolder, { recursive: true }); | ||
| await fs.promises.writeFile(outFile, body); | ||
| if (checkPublicConflict(outFile, route, options.settings, logger)) return null; | ||
| return { body, outFile, outFolder }; | ||
| } | ||
| async function generatePathWithPrerenderer(prerenderer, pathname, route, options, routeToHeaders, logger) { | ||
| const timeStart = performance.now(); | ||
| const { config } = options.settings; | ||
| const filePath = getOutputFilename(config.build.format, pathname, route); | ||
| logger.info(null, ` ${colors.blue("\u251C\u2500")} ${colors.dim(filePath)}`, false); | ||
| if (route.type === "page") { | ||
| addPageName(pathname, options); | ||
| } | ||
| const result = await renderPath({ | ||
| prerenderer, | ||
| pathname, | ||
| route, | ||
| options, | ||
| routeToHeaders, | ||
| logger | ||
| }); | ||
| if (!result) { | ||
| logRenderTime(logger, timeStart, true); | ||
| return; | ||
| } | ||
| await nodeFs.promises.mkdir(result.outFolder, { recursive: true }); | ||
| await nodeFs.promises.writeFile(result.outFile, result.body); | ||
| logRenderTime(logger, timeStart, false); | ||
@@ -355,9 +375,6 @@ } | ||
| function checkPublicConflict(outFile, route, settings, logger) { | ||
| const outFilePath = fileURLToPath(outFile); | ||
| const outRoot = fileURLToPath( | ||
| settings.buildOutput === "static" && !settings.adapter?.adapterFeatures?.preserveBuildClientDir ? settings.config.outDir : settings.config.build.client | ||
| ); | ||
| const relativePath = outFilePath.slice(outRoot.length); | ||
| const publicFilePath = new URL(relativePath, settings.config.publicDir); | ||
| if (fs.existsSync(publicFilePath)) { | ||
| const outRoot = settings.buildOutput === "static" && !settings.adapter?.adapterFeatures?.preserveBuildClientDir ? settings.config.outDir : settings.config.build.client; | ||
| const relativePath = outFile.href.slice(outRoot.href.length); | ||
| const publicFileUrl = new URL(relativePath, settings.config.publicDir); | ||
| if (nodeFs.existsSync(publicFileUrl)) { | ||
| logger.warn( | ||
@@ -372,3 +389,4 @@ "build", | ||
| export { | ||
| generatePages | ||
| generatePages, | ||
| renderPath | ||
| }; |
| import { isCSSRequest } from "vite"; | ||
| import { hasAssetPropagationFlag } from "../../../content/index.js"; | ||
| import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js"; | ||
| import { isPropagatedAssetBoundary } from "../../head-propagation/boundary.js"; | ||
| import { | ||
@@ -98,3 +98,3 @@ getParentExtendedModuleInfos, | ||
| this, | ||
| hasAssetPropagationFlag | ||
| isPropagatedAssetBoundary | ||
| ); | ||
@@ -156,5 +156,9 @@ for (const { info: pageInfo, depth, order } of parentModuleInfos) { | ||
| if (!isCSSRequest(id)) continue; | ||
| const parentModuleInfos = getParentExtendedModuleInfos(id, this, hasAssetPropagationFlag); | ||
| const parentModuleInfos = getParentExtendedModuleInfos( | ||
| id, | ||
| this, | ||
| isPropagatedAssetBoundary | ||
| ); | ||
| for (const { info: pageInfo, depth, order } of parentModuleInfos) { | ||
| if (hasAssetPropagationFlag(pageInfo.id)) { | ||
| if (isPropagatedAssetBoundary(pageInfo.id)) { | ||
| const propagatedCss = moduleIdToPropagatedCss[pageInfo.id] ??= /* @__PURE__ */ new Set(); | ||
@@ -161,0 +165,0 @@ for (const css of meta.importedCss) { |
| import type { OutgoingHttpHeaders } from 'node:http'; | ||
| import type { RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype, ShikiConfig } from '@astrojs/markdown-remark'; | ||
| import type { RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype, ShikiConfig, Smartypants as _Smartypants } from '@astrojs/markdown-remark'; | ||
| import type { Config as SvgoConfig } from 'svgo'; | ||
@@ -18,2 +18,4 @@ import * as z from 'zod/v4'; | ||
| export type RemarkRehype = ComplexifyWithOmit<_RemarkRehype>; | ||
| /** @lintignore */ | ||
| export type Smartypants = ComplexifyWithOmit<_Smartypants>; | ||
| export declare const ASTRO_CONFIG_DEFAULTS: { | ||
@@ -330,3 +332,3 @@ root: string; | ||
| gfm: z.ZodDefault<z.ZodBoolean>; | ||
| smartypants: z.ZodDefault<z.ZodBoolean>; | ||
| smartypants: z.ZodPrefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<Smartypants, unknown, z.core.$ZodTypeInternals<Smartypants, unknown>>]>, z.ZodTransform<false | Smartypants, boolean | Smartypants>>>; | ||
| }, z.core.$strip>>; | ||
@@ -333,0 +335,0 @@ vite: z.ZodDefault<z.ZodCustom<ViteUserConfig, ViteUserConfig>>; |
@@ -73,2 +73,20 @@ import { markdownConfigDefaults, syntaxHighlightDefaults } from "@astrojs/markdown-remark"; | ||
| const highlighterTypesSchema = z.union([z.literal("shiki"), z.literal("prism")]).default(syntaxHighlightDefaults.type); | ||
| const quoteCharacterMapSchema = z.object({ | ||
| double: z.string(), | ||
| single: z.string() | ||
| }); | ||
| const smartypantsOptionsSchema = z.object({ | ||
| backticks: z.union([z.boolean(), z.literal("all")]).default(true), | ||
| closingQuotes: quoteCharacterMapSchema.default({ | ||
| double: "\u201D", | ||
| single: "\u2019" | ||
| }), | ||
| dashes: z.union([z.boolean(), z.literal("inverted"), z.literal("oldschool")]).default(true), | ||
| ellipses: z.union([z.boolean(), z.literal("spaced"), z.literal("unspaced")]).default(true), | ||
| openingQuotes: quoteCharacterMapSchema.default({ | ||
| double: "\u201C", | ||
| single: "\u2018" | ||
| }), | ||
| quotes: z.boolean().default(true) | ||
| }); | ||
| const AstroConfigSchema = z.object({ | ||
@@ -220,3 +238,6 @@ root: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.root).transform((val) => new URL(val)), | ||
| gfm: z.boolean().default(ASTRO_CONFIG_DEFAULTS.markdown.gfm), | ||
| smartypants: z.boolean().default(ASTRO_CONFIG_DEFAULTS.markdown.smartypants) | ||
| smartypants: z.union([z.boolean(), smartypantsOptionsSchema]).transform((val) => { | ||
| if (val === true) return smartypantsOptionsSchema.parse({}); | ||
| return val; | ||
| }).prefault(ASTRO_CONFIG_DEFAULTS.markdown.smartypants) | ||
| }).prefault({}), | ||
@@ -223,0 +244,0 @@ vite: z.custom((data) => data instanceof Object && !Array.isArray(data)).default(ASTRO_CONFIG_DEFAULTS.vite), |
@@ -215,3 +215,3 @@ import type { OutgoingHttpHeaders } from 'node:http'; | ||
| gfm: z.ZodDefault<z.ZodBoolean>; | ||
| smartypants: z.ZodDefault<z.ZodBoolean>; | ||
| smartypants: z.ZodPrefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<import("./base.js").Smartypants, unknown, z.core.$ZodTypeInternals<import("./base.js").Smartypants, unknown>>]>, z.ZodTransform<false | import("./base.js").Smartypants, boolean | import("./base.js").Smartypants>>>; | ||
| }, z.core.$strip>>; | ||
@@ -501,3 +501,3 @@ vite: z.ZodDefault<z.ZodCustom<import("../../../index.js").ViteUserConfig, import("../../../index.js").ViteUserConfig>>; | ||
| gfm: boolean; | ||
| smartypants: boolean; | ||
| smartypants: false | import("./base.js").Smartypants; | ||
| }; | ||
@@ -744,3 +744,3 @@ vite: import("../../../index.js").ViteUserConfig; | ||
| gfm: boolean; | ||
| smartypants: boolean; | ||
| smartypants: false | import("./base.js").Smartypants; | ||
| }; | ||
@@ -747,0 +747,0 @@ vite: import("../../../index.js").ViteUserConfig; |
@@ -1,2 +0,2 @@ | ||
| const ASTRO_VERSION = "6.0.8"; | ||
| const ASTRO_VERSION = "6.1.0"; | ||
| const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`; | ||
@@ -3,0 +3,0 @@ const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute"; |
@@ -106,3 +106,3 @@ import nodeFs from "node:fs"; | ||
| await astroPluginRoutes({ routesList, settings, logger, fsMod: fs, command }), | ||
| astroVirtualManifestPlugin(), | ||
| astroVirtualManifestPlugin({ settings }), | ||
| vitePluginEnvironment({ settings, astroPkgsConfig, command }), | ||
@@ -109,0 +109,0 @@ pluginPage({ routesList }), |
+13
-1
| import fs from "node:fs"; | ||
| import { createRequire } from "node:module"; | ||
| import { performance } from "node:perf_hooks"; | ||
@@ -23,2 +24,12 @@ import colors from "piccolore"; | ||
| import { piccoloreTextStyler } from "../../cli/infra/piccolore-text-styler.js"; | ||
| function warnIfVite8({ root, logger }) { | ||
| try { | ||
| const require2 = createRequire(root); | ||
| const { version } = require2("vite/package.json"); | ||
| if (major(version) >= 8) { | ||
| logger.warn("SKIP_FORMAT", msg.vite8Warning({ viteVersion: version })); | ||
| } | ||
| } catch { | ||
| } | ||
| } | ||
| async function dev(inlineConfig) { | ||
@@ -30,3 +41,3 @@ ensureProcessNodeEnv("development"); | ||
| const logger = restart.container.logger; | ||
| const currentVersion = "6.0.8"; | ||
| const currentVersion = "6.1.0"; | ||
| const isPrerelease = currentVersion.includes("-"); | ||
@@ -105,2 +116,3 @@ if (!isPrerelease) { | ||
| } | ||
| setImmediate(() => warnIfVite8({ root: restart.container.settings.config.root, logger })); | ||
| logger.info(null, colors.green("watching for file changes...")); | ||
@@ -107,0 +119,0 @@ return { |
@@ -44,2 +44,5 @@ import type { ResolvedServerUrls } from 'vite'; | ||
| export declare function fsStrictWarning(): string; | ||
| export declare function vite8Warning({ viteVersion }: { | ||
| viteVersion: string; | ||
| }): string; | ||
| export declare function prerelease({ currentVersion }: { | ||
@@ -46,0 +49,0 @@ currentVersion: string; |
@@ -137,2 +137,9 @@ import colors from "piccolore"; | ||
| } | ||
| function vite8Warning({ viteVersion }) { | ||
| const title = yellow(`\u25B6 Vite ${bold(viteVersion)} detected in your project.`); | ||
| const subtitle = ` Astro requires Vite 7. Add ${bold('"overrides": { "vite": "^7" }')} to your ${bold("package.json")} to avoid issues.`; | ||
| return `${title} | ||
| ${subtitle} | ||
| `; | ||
| } | ||
| function prerelease({ currentVersion }) { | ||
@@ -273,3 +280,3 @@ const tag = currentVersion.split("-").slice(1).join("-").replace(/\..*$/, "") || "unknown"; | ||
| ` ${bgGreen(black(` ${commandName} `))} ${green( | ||
| `v${"6.0.8"}` | ||
| `v${"6.1.0"}` | ||
| )} ${headline}` | ||
@@ -332,3 +339,4 @@ ); | ||
| telemetryReset, | ||
| vite8Warning, | ||
| warnIfCspWithShiki | ||
| }; |
@@ -1,2 +0,2 @@ | ||
| import type { Plugin as VitePlugin } from 'vite'; | ||
| import { type Plugin as VitePlugin } from 'vite'; | ||
| import type { AstroSettings } from '../../types/astro.js'; | ||
@@ -3,0 +3,0 @@ import type { BuildInternals } from '../build/internal.js'; |
@@ -0,1 +1,5 @@ | ||
| import { fileURLToPath } from "node:url"; | ||
| import { | ||
| normalizePath as viteNormalizePath | ||
| } from "vite"; | ||
| import { getServerOutputDirectory } from "../../prerender/utils.js"; | ||
@@ -14,2 +18,3 @@ import { addRollupInput } from "../build/add-rollup-input.js"; | ||
| let userMiddlewareIsPresent = false; | ||
| const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir)); | ||
| return { | ||
@@ -20,2 +25,22 @@ name: "@astro/plugin-middleware", | ||
| }, | ||
| configureServer(server) { | ||
| server.watcher.on("change", (path) => { | ||
| const normalizedPath = viteNormalizePath(path); | ||
| if (!normalizedPath.startsWith(normalizedSrcDir)) return; | ||
| const relativePath = normalizedPath.slice(normalizedSrcDir.length); | ||
| if (!relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}.`)) return; | ||
| for (const name of [ | ||
| ASTRO_VITE_ENVIRONMENT_NAMES.ssr, | ||
| ASTRO_VITE_ENVIRONMENT_NAMES.astro | ||
| ]) { | ||
| const environment = server.environments[name]; | ||
| if (!environment) continue; | ||
| const virtualMod = environment.moduleGraph.getModuleById(MIDDLEWARE_RESOLVED_MODULE_ID); | ||
| if (virtualMod) { | ||
| environment.moduleGraph.invalidateModule(virtualMod); | ||
| } | ||
| environment.hot.send("astro:middleware-updated", {}); | ||
| } | ||
| }); | ||
| }, | ||
| resolveId: { | ||
@@ -22,0 +47,0 @@ filter: { |
@@ -0,4 +1,21 @@ | ||
| import type { Params } from '../../types/public/common.js'; | ||
| import type { RedirectConfig } from '../../types/public/index.js'; | ||
| import type { RouteData } from '../../types/public/internal.js'; | ||
| import type { RenderContext } from '../render-context.js'; | ||
| export declare function redirectIsExternal(redirect: RedirectConfig): boolean; | ||
| /** | ||
| * Computes the HTTP status code for a redirect response. | ||
| * | ||
| * - If the route has a `redirectRoute` and an explicit numeric status, that status is used. | ||
| * - Otherwise: GET → 301, non-GET (e.g. POST) → 308. | ||
| */ | ||
| export declare function computeRedirectStatus(method: string, redirect: RedirectConfig | undefined, redirectRoute: RouteData | undefined): number; | ||
| /** | ||
| * Resolves the final redirect target URL by substituting dynamic params into | ||
| * the redirect string (e.g. `/[slug]/page` → `/hello/page`). | ||
| * | ||
| * When `redirectRoute` is provided its route generator is used; otherwise params | ||
| * are substituted manually into the string redirect target. | ||
| */ | ||
| export declare function resolveRedirectTarget(params: Params, redirect: RedirectConfig | undefined, redirectRoute: RouteData | undefined, trailingSlash: 'always' | 'never' | 'ignore'): string; | ||
| export declare function renderRedirect(renderContext: RenderContext): Promise<Response>; |
@@ -12,27 +12,8 @@ import { getRouteGenerator } from "../routing/generator.js"; | ||
| } | ||
| async function renderRedirect(renderContext) { | ||
| const { | ||
| request: { method }, | ||
| routeData | ||
| } = renderContext; | ||
| const { redirect, redirectRoute } = routeData; | ||
| const status = redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308; | ||
| const headers = { location: encodeURI(redirectRouteGenerate(renderContext)) }; | ||
| if (redirect && redirectIsExternal(redirect)) { | ||
| if (typeof redirect === "string") { | ||
| return Response.redirect(redirect, status); | ||
| } else { | ||
| return Response.redirect(redirect.destination, status); | ||
| } | ||
| } | ||
| return new Response(null, { status, headers }); | ||
| function computeRedirectStatus(method, redirect, redirectRoute) { | ||
| return redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308; | ||
| } | ||
| function redirectRouteGenerate(renderContext) { | ||
| const { | ||
| params, | ||
| routeData: { redirect, redirectRoute }, | ||
| pipeline | ||
| } = renderContext; | ||
| function resolveRedirectTarget(params, redirect, redirectRoute, trailingSlash) { | ||
| if (typeof redirectRoute !== "undefined") { | ||
| const generate = getRouteGenerator(redirectRoute.segments, pipeline.manifest.trailingSlash); | ||
| const generate = getRouteGenerator(redirectRoute.segments, trailingSlash); | ||
| return generate(params) || redirectRoute?.pathname || "/"; | ||
@@ -55,5 +36,33 @@ } else if (typeof redirect === "string") { | ||
| } | ||
| async function renderRedirect(renderContext) { | ||
| const { | ||
| request: { method }, | ||
| routeData | ||
| } = renderContext; | ||
| const { redirect, redirectRoute } = routeData; | ||
| const status = computeRedirectStatus(method, redirect, redirectRoute); | ||
| const headers = { | ||
| location: encodeURI( | ||
| resolveRedirectTarget( | ||
| renderContext.params, | ||
| redirect, | ||
| redirectRoute, | ||
| renderContext.pipeline.manifest.trailingSlash | ||
| ) | ||
| ) | ||
| }; | ||
| if (redirect && redirectIsExternal(redirect)) { | ||
| if (typeof redirect === "string") { | ||
| return Response.redirect(redirect, status); | ||
| } else { | ||
| return Response.redirect(redirect.destination, status); | ||
| } | ||
| } | ||
| return new Response(null, { status, headers }); | ||
| } | ||
| export { | ||
| computeRedirectStatus, | ||
| redirectIsExternal, | ||
| renderRedirect | ||
| renderRedirect, | ||
| resolveRedirectTarget | ||
| }; |
| import nodeFs from 'node:fs'; | ||
| import type { AstroSettings, RoutesList } from '../../types/astro.js'; | ||
| import type { AstroConfig } from '../../types/public/config.js'; | ||
| import type { RouteData } from '../../types/public/internal.js'; | ||
@@ -26,2 +27,16 @@ import type { Logger } from '../logger/core.js'; | ||
| /** | ||
| * Generates i18n fallback routes and attaches them to their source routes. | ||
| * | ||
| * For each locale that has a fallback configured (e.g. `{ es: 'en' }`), this | ||
| * function inspects the existing route list and creates `type: 'fallback'` | ||
| * entries for any paths that the source locale does not already have. The | ||
| * fallback routes are pushed onto `route.fallbackRoutes` of their source route | ||
| * so that the build pipeline can serve the fallback content. | ||
| * | ||
| * @param routes The full route list — mutated in-place. | ||
| * @param i18n The resolved `config.i18n` object. | ||
| * @param config The resolved Astro config (needs `base` and `trailingSlash`). | ||
| */ | ||
| export declare function createI18nFallbackRoutes(routes: RouteData[], i18n: NonNullable<AstroConfig['i18n']>, config: Pick<AstroConfig, 'base' | 'trailingSlash'>): void; | ||
| /** | ||
| * Resolve a route entrypoint to an absolute component path. | ||
@@ -28,0 +43,0 @@ */ |
@@ -66,2 +66,3 @@ import nodeFs from "node:fs"; | ||
| const rootPath = fileURLToPath(config.root); | ||
| const pagesPath = fileURLToPath(pages); | ||
| if (!localFs.existsSync(pages)) { | ||
@@ -181,3 +182,3 @@ if (settings.injectedRoutes.length === 0) { | ||
| } | ||
| walk(localFs, fileURLToPath(pages), [], []); | ||
| walk(localFs, pagesPath, [], []); | ||
| return routes; | ||
@@ -472,12 +473,8 @@ } | ||
| const localFs = params.fsMod ?? nodeFs; | ||
| const content = await localFs.promises.readFile( | ||
| fileURLToPath( | ||
| new URL( | ||
| // The destination redirect might be a prerendered | ||
| route.type === "redirect" && route.redirectRoute ? route.redirectRoute.component : route.component, | ||
| settings.config.root | ||
| ) | ||
| ), | ||
| "utf-8" | ||
| const componentUrl = new URL( | ||
| // The destination redirect might be a prerendered | ||
| route.type === "redirect" && route.redirectRoute ? route.redirectRoute.component : route.component, | ||
| settings.config.root | ||
| ); | ||
| const content = await localFs.promises.readFile(componentUrl, "utf-8"); | ||
| await getRoutePrerenderOption(content, route, settings, logger); | ||
@@ -510,121 +507,137 @@ }) | ||
| } | ||
| const routesByLocale = /* @__PURE__ */ new Map(); | ||
| const setRoutes = new Set(routes.filter((route) => route.type === "page")); | ||
| const filteredLocales = i18n.locales.filter((loc) => { | ||
| if (typeof loc === "string") { | ||
| return loc !== i18n.defaultLocale; | ||
| } | ||
| return loc.path !== i18n.defaultLocale; | ||
| }).map((locale) => { | ||
| if (typeof locale === "string") { | ||
| return locale; | ||
| } | ||
| return locale.path; | ||
| }); | ||
| for (const locale of filteredLocales) { | ||
| for (const route of setRoutes) { | ||
| const hasLocaleInRoute = route.route.split("/").includes(locale); | ||
| if (!hasLocaleInRoute) { | ||
| continue; | ||
| } | ||
| const currentRoutes = routesByLocale.get(locale); | ||
| if (currentRoutes) { | ||
| currentRoutes.push(route); | ||
| routesByLocale.set(locale, currentRoutes); | ||
| } else { | ||
| routesByLocale.set(locale, [route]); | ||
| } | ||
| setRoutes.delete(route); | ||
| } | ||
| createI18nFallbackRoutes(routes, i18n, config); | ||
| } | ||
| if (dev) { | ||
| ensure404Route({ routes }); | ||
| } | ||
| if (dev || settings.buildOutput === "server") { | ||
| injectImageEndpoint(settings, { routes }, dev ? "dev" : "build"); | ||
| } | ||
| if (dev || settings.config.adapter) { | ||
| injectServerIslandRoute(settings.config, { routes }); | ||
| } | ||
| await runHookRoutesResolved({ routes, settings, logger }); | ||
| return { | ||
| routes | ||
| }; | ||
| } | ||
| function createI18nFallbackRoutes(routes, i18n, config) { | ||
| const strategy = toRoutingStrategy(i18n.routing, i18n.domains); | ||
| const routesByLocale = /* @__PURE__ */ new Map(); | ||
| const setRoutes = new Set(routes.filter((route) => route.type === "page")); | ||
| const filteredLocales = i18n.locales.filter((loc) => { | ||
| if (typeof loc === "string") { | ||
| return loc !== i18n.defaultLocale; | ||
| } | ||
| return loc.path !== i18n.defaultLocale; | ||
| }).map((locale) => { | ||
| if (typeof locale === "string") { | ||
| return locale; | ||
| } | ||
| return locale.path; | ||
| }); | ||
| for (const locale of filteredLocales) { | ||
| for (const route of setRoutes) { | ||
| const currentRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| const hasLocaleInRoute = route.route.split("/").includes(locale); | ||
| if (!hasLocaleInRoute) { | ||
| continue; | ||
| } | ||
| const currentRoutes = routesByLocale.get(locale); | ||
| if (currentRoutes) { | ||
| currentRoutes.push(route); | ||
| routesByLocale.set(i18n.defaultLocale, currentRoutes); | ||
| routesByLocale.set(locale, currentRoutes); | ||
| } else { | ||
| routesByLocale.set(i18n.defaultLocale, [route]); | ||
| routesByLocale.set(locale, [route]); | ||
| } | ||
| setRoutes.delete(route); | ||
| } | ||
| if (strategy === "pathname-prefix-always") { | ||
| const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| if (defaultLocaleRoutes) { | ||
| const indexDefaultRoute = defaultLocaleRoutes.find(({ route }) => route === "/") ?? defaultLocaleRoutes.find(({ route }) => route === `/${i18n.defaultLocale}`); | ||
| if (indexDefaultRoute) { | ||
| const pathname = "/"; | ||
| const route = "/"; | ||
| const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => { | ||
| validateSegment(s); | ||
| return getParts(s, route); | ||
| }); | ||
| routes.push({ | ||
| ...indexDefaultRoute, | ||
| pathname, | ||
| route, | ||
| segments, | ||
| pattern: getPattern(segments, config.base, config.trailingSlash), | ||
| type: "fallback" | ||
| }); | ||
| } | ||
| } | ||
| for (const route of setRoutes) { | ||
| const currentRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| if (currentRoutes) { | ||
| currentRoutes.push(route); | ||
| routesByLocale.set(i18n.defaultLocale, currentRoutes); | ||
| } else { | ||
| routesByLocale.set(i18n.defaultLocale, [route]); | ||
| } | ||
| setRoutes.delete(route); | ||
| } | ||
| if (strategy === "pathname-prefix-always") { | ||
| const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| if (defaultLocaleRoutes) { | ||
| const indexDefaultRoute = defaultLocaleRoutes.find(({ route }) => route === "/") ?? defaultLocaleRoutes.find(({ route }) => route === `/${i18n.defaultLocale}`); | ||
| if (indexDefaultRoute) { | ||
| const pathname = "/"; | ||
| const route = "/"; | ||
| const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => { | ||
| validateSegment(s); | ||
| return getParts(s, route); | ||
| }); | ||
| routes.push({ | ||
| ...indexDefaultRoute, | ||
| pathname, | ||
| route, | ||
| segments, | ||
| pattern: getPattern(segments, config.base, config.trailingSlash), | ||
| type: "fallback" | ||
| }); | ||
| } | ||
| } | ||
| if (i18n.fallback) { | ||
| let fallback = Object.entries(i18n.fallback); | ||
| if (fallback.length > 0) { | ||
| for (const [fallbackFromLocale, fallbackToLocale] of fallback) { | ||
| let fallbackToRoutes; | ||
| if (fallbackToLocale === i18n.defaultLocale) { | ||
| fallbackToRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| } else { | ||
| fallbackToRoutes = routesByLocale.get(fallbackToLocale); | ||
| } | ||
| const fallbackFromRoutes = routesByLocale.get(fallbackFromLocale); | ||
| if (!fallbackToRoutes) { | ||
| continue; | ||
| } | ||
| for (const fallbackToRoute of fallbackToRoutes) { | ||
| const hasRoute = fallbackFromRoutes && // we check if the fallback from locale (the origin) has already this route | ||
| fallbackFromRoutes.some((route) => { | ||
| if (fallbackToLocale === i18n.defaultLocale) { | ||
| return route.route === `/${fallbackFromLocale}${fallbackToRoute.route}` || route.route.replace(`/${fallbackFromLocale}`, "") === fallbackToRoute.route; | ||
| } else { | ||
| const expectedRoute = replaceOrKeep( | ||
| fallbackToRoute.route, | ||
| fallbackToLocale, | ||
| fallbackFromLocale | ||
| ); | ||
| return route.route === expectedRoute; | ||
| } | ||
| if (i18n.fallback) { | ||
| const fallback = Object.entries(i18n.fallback); | ||
| if (fallback.length > 0) { | ||
| for (const [fallbackFromLocale, fallbackToLocale] of fallback) { | ||
| let fallbackToRoutes; | ||
| if (fallbackToLocale === i18n.defaultLocale) { | ||
| fallbackToRoutes = routesByLocale.get(i18n.defaultLocale); | ||
| } else { | ||
| fallbackToRoutes = routesByLocale.get(fallbackToLocale); | ||
| } | ||
| const fallbackFromRoutes = routesByLocale.get(fallbackFromLocale); | ||
| if (!fallbackToRoutes) { | ||
| continue; | ||
| } | ||
| for (const fallbackToRoute of fallbackToRoutes) { | ||
| const hasRoute = fallbackFromRoutes && fallbackFromRoutes.some((route) => { | ||
| if (fallbackToLocale === i18n.defaultLocale) { | ||
| return route.route === `/${fallbackFromLocale}${fallbackToRoute.route}` || route.route.replace(`/${fallbackFromLocale}`, "") === fallbackToRoute.route; | ||
| } else { | ||
| const expectedRoute = replaceOrKeep( | ||
| fallbackToRoute.route, | ||
| fallbackToLocale, | ||
| fallbackFromLocale | ||
| ); | ||
| return route.route === expectedRoute; | ||
| } | ||
| }); | ||
| if (!hasRoute) { | ||
| let pathname; | ||
| let route; | ||
| if (fallbackToLocale === i18n.defaultLocale && strategy === "pathname-prefix-other-locales") { | ||
| if (fallbackToRoute.pathname) { | ||
| pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`; | ||
| } | ||
| route = `/${fallbackFromLocale}${fallbackToRoute.route}`; | ||
| } else { | ||
| pathname = fallbackToRoute.pathname ? replaceOrKeep(fallbackToRoute.pathname, fallbackToLocale, fallbackFromLocale) : void 0; | ||
| route = replaceOrKeep(fallbackToRoute.route, fallbackToLocale, fallbackFromLocale); | ||
| } | ||
| const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => { | ||
| validateSegment(s); | ||
| return getParts(s, route); | ||
| }); | ||
| if (!hasRoute) { | ||
| let pathname; | ||
| let route; | ||
| if (fallbackToLocale === i18n.defaultLocale && strategy === "pathname-prefix-other-locales") { | ||
| if (fallbackToRoute.pathname) { | ||
| pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`; | ||
| } | ||
| route = `/${fallbackFromLocale}${fallbackToRoute.route}`; | ||
| } else { | ||
| pathname = fallbackToRoute.pathname ? replaceOrKeep(fallbackToRoute.pathname, fallbackToLocale, fallbackFromLocale) : void 0; | ||
| route = replaceOrKeep(fallbackToRoute.route, fallbackToLocale, fallbackFromLocale); | ||
| } | ||
| const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => { | ||
| validateSegment(s); | ||
| return getParts(s, route); | ||
| }); | ||
| const index = routes.findIndex((r) => r === fallbackToRoute); | ||
| if (index >= 0) { | ||
| const fallbackRoute = { | ||
| ...fallbackToRoute, | ||
| pathname, | ||
| route, | ||
| segments, | ||
| pattern: getPattern(segments, config.base, config.trailingSlash), | ||
| type: "fallback", | ||
| fallbackRoutes: [] | ||
| }; | ||
| const routeData = routes[index]; | ||
| routeData.fallbackRoutes.push(fallbackRoute); | ||
| } | ||
| const index = routes.findIndex((r) => r === fallbackToRoute); | ||
| if (index >= 0) { | ||
| const fallbackRoute = { | ||
| ...fallbackToRoute, | ||
| pathname, | ||
| route, | ||
| segments, | ||
| pattern: getPattern(segments, config.base, config.trailingSlash), | ||
| type: "fallback", | ||
| fallbackRoutes: [] | ||
| }; | ||
| const routeData = routes[index]; | ||
| routeData.fallbackRoutes.push(fallbackRoute); | ||
| } | ||
@@ -636,15 +649,2 @@ } | ||
| } | ||
| if (dev) { | ||
| ensure404Route({ routes }); | ||
| } | ||
| if (dev || settings.buildOutput === "server") { | ||
| injectImageEndpoint(settings, { routes }, dev ? "dev" : "build"); | ||
| } | ||
| if (dev || settings.config.adapter) { | ||
| injectServerIslandRoute(settings.config, { routes }); | ||
| } | ||
| await runHookRoutesResolved({ routes, settings, logger }); | ||
| return { | ||
| routes | ||
| }; | ||
| } | ||
@@ -674,2 +674,3 @@ function resolveInjectedRoute(entrypoint, root, cwd) { | ||
| export { | ||
| createI18nFallbackRoutes, | ||
| createRoutesFromEntries, | ||
@@ -676,0 +677,0 @@ createRoutesList, |
| import type { AstroSettings } from '../../types/astro.js'; | ||
| import type { RouteData } from '../../types/public/internal.js'; | ||
| import type { Logger } from '../logger/core.js'; | ||
| /** | ||
| * Parses the `export const prerender = true|false` declaration from a route's | ||
| * source content. Returns `true`, `false`, or `undefined` if not present. | ||
| */ | ||
| export declare function parsePrerenderExport(content: string): boolean | undefined; | ||
| export declare function getRoutePrerenderOption(content: string, route: RouteData, settings: AstroSettings, logger: Logger): Promise<void>; |
| import { runHookRouteSetup } from "../../integrations/hooks.js"; | ||
| import { getPrerenderDefault } from "../../prerender/utils.js"; | ||
| const PRERENDER_REGEX = /^\s*export\s+const\s+prerender\s*=\s*(true|false);?/m; | ||
| function parsePrerenderExport(content) { | ||
| const match = PRERENDER_REGEX.exec(content); | ||
| if (!match) return void 0; | ||
| return match[1] === "true"; | ||
| } | ||
| async function getRoutePrerenderOption(content, route, settings, logger) { | ||
@@ -19,3 +24,4 @@ const match = PRERENDER_REGEX.exec(content); | ||
| export { | ||
| getRoutePrerenderOption | ||
| getRoutePrerenderOption, | ||
| parsePrerenderExport | ||
| }; |
@@ -15,3 +15,3 @@ import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js"; | ||
| let command = "serve"; | ||
| let ssrEnvironment = null; | ||
| let serverEnvironments = []; | ||
| function ensureServerIslandReferenceIds(ctx) { | ||
@@ -42,3 +42,13 @@ for (const [resolvedPath, island] of serverIslandsState.getDiscoveredIslandEntries()) { | ||
| configureServer(server) { | ||
| ssrEnvironment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; | ||
| serverEnvironments = []; | ||
| for (const name of [ | ||
| ASTRO_VITE_ENVIRONMENT_NAMES.ssr, | ||
| ASTRO_VITE_ENVIRONMENT_NAMES.prerender, | ||
| ASTRO_VITE_ENVIRONMENT_NAMES.astro | ||
| ]) { | ||
| const env = server.environments[name]; | ||
| if (env) { | ||
| serverEnvironments.push(env); | ||
| } | ||
| } | ||
| }, | ||
@@ -97,6 +107,8 @@ resolveId: { | ||
| } | ||
| if (serverIslandsState.hasIslands() && ssrEnvironment) { | ||
| const mod = ssrEnvironment.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST); | ||
| if (mod) { | ||
| ssrEnvironment.moduleGraph.invalidateModule(mod); | ||
| if (serverIslandsState.hasIslands()) { | ||
| for (const env of serverEnvironments) { | ||
| const mod = env.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST); | ||
| if (mod) { | ||
| env.moduleGraph.invalidateModule(mod); | ||
| } | ||
| } | ||
@@ -103,0 +115,0 @@ } |
@@ -525,3 +525,6 @@ import fsMod from "node:fs"; | ||
| redirect: route.redirect, | ||
| redirectRoute: route.redirectRoute ? toIntegrationResolvedRoute(route.redirectRoute, trailingSlash) : void 0 | ||
| redirectRoute: route.redirectRoute ? toIntegrationResolvedRoute(route.redirectRoute, trailingSlash) : void 0, | ||
| fallbackRoutes: route.fallbackRoutes.map( | ||
| (fallbackRoute) => toIntegrationResolvedRoute(fallbackRoute, trailingSlash) | ||
| ) | ||
| }; | ||
@@ -528,0 +531,0 @@ } |
@@ -185,3 +185,3 @@ import { visit } from "unist-util-visit"; | ||
| } | ||
| if (!attributeNames.includes("client:component-hydpathation")) { | ||
| if (!attributeNames.includes("client:component-path")) { | ||
| node.attributes.push({ | ||
@@ -188,0 +188,0 @@ type: "mdxJsxAttribute", |
@@ -61,2 +61,7 @@ import { fileURLToPath } from "node:url"; | ||
| }, | ||
| // Restrict to server environments only since the generated code imports | ||
| // server-only virtual modules (virtual:astro:routes, virtual:astro:pages) | ||
| applyToEnvironment(environment) { | ||
| return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender; | ||
| }, | ||
| resolveId: { | ||
@@ -63,0 +68,0 @@ filter: { |
| import type { Plugin } from 'vite'; | ||
| export default function virtualModulePlugin(): Plugin; | ||
| import type { AstroSettings } from '../types/astro.js'; | ||
| export default function virtualModulePlugin({ settings }: { | ||
| settings: AstroSettings; | ||
| }): Plugin; |
| import { AstroError, AstroErrorData } from "../core/errors/index.js"; | ||
| import { SERIALIZED_MANIFEST_ID } from "./serialized.js"; | ||
| import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js"; | ||
| import { fromRoutingStrategy, toFallbackType, toRoutingStrategy } from "../core/app/common.js"; | ||
| const VIRTUAL_SERVER_ID = "astro:config/server"; | ||
@@ -8,3 +9,37 @@ const RESOLVED_VIRTUAL_SERVER_ID = "\0" + VIRTUAL_SERVER_ID; | ||
| const RESOLVED_VIRTUAL_CLIENT_ID = "\0" + VIRTUAL_CLIENT_ID; | ||
| function virtualModulePlugin() { | ||
| function virtualModulePlugin({ settings }) { | ||
| const config = settings.config; | ||
| let i18nCode = "const i18n = undefined;"; | ||
| if (config.i18n) { | ||
| const strategy = toRoutingStrategy(config.i18n.routing, config.i18n.domains); | ||
| const fallbackType = toFallbackType(config.i18n.routing); | ||
| const routing = fromRoutingStrategy(strategy, fallbackType); | ||
| i18nCode = `const i18n = { | ||
| defaultLocale: ${JSON.stringify(config.i18n.defaultLocale)}, | ||
| locales: ${JSON.stringify(config.i18n.locales)}, | ||
| routing: ${JSON.stringify(routing)}, | ||
| fallback: ${JSON.stringify(config.i18n.fallback)} | ||
| };`; | ||
| } | ||
| let imageCode = "const image = undefined;"; | ||
| if (config.image) { | ||
| imageCode = `const image = { | ||
| objectFit: ${JSON.stringify(config.image.objectFit)}, | ||
| objectPosition: ${JSON.stringify(config.image.objectPosition)}, | ||
| layout: ${JSON.stringify(config.image.layout)}, | ||
| };`; | ||
| } | ||
| const clientConfigCode = ` | ||
| ${i18nCode} | ||
| ${imageCode} | ||
| const base = ${JSON.stringify(config.base)}; | ||
| const trailingSlash = ${JSON.stringify(config.trailingSlash)}; | ||
| const site = ${JSON.stringify(config.site)}; | ||
| const compressHTML = ${JSON.stringify(config.compressHTML)}; | ||
| const build = { | ||
| format: ${JSON.stringify(config.build.format)}, | ||
| }; | ||
| export { base, i18n, trailingSlash, site, compressHTML, build, image }; | ||
| `; | ||
| return { | ||
@@ -31,36 +66,3 @@ name: "astro-manifest-plugin", | ||
| if (id === RESOLVED_VIRTUAL_CLIENT_ID) { | ||
| const code = ` | ||
| import { manifest } from '${SERIALIZED_MANIFEST_ID}' | ||
| import { fromRoutingStrategy } from 'astro/app'; | ||
| let i18n = undefined; | ||
| if (manifest.i18n) { | ||
| i18n = { | ||
| defaultLocale: manifest.i18n.defaultLocale, | ||
| locales: manifest.i18n.locales, | ||
| routing: fromRoutingStrategy(manifest.i18n.strategy, manifest.i18n.fallbackType), | ||
| fallback: manifest.i18n.fallback | ||
| }; | ||
| } | ||
| let image = undefined; | ||
| if (manifest.image) { | ||
| image = { | ||
| objectFit: manifest.image.objectFit, | ||
| objectPosition: manifest.image.objectPosition, | ||
| layout: manifest.image.layout, | ||
| }; | ||
| } | ||
| const base = manifest.base; | ||
| const trailingSlash = manifest.trailingSlash; | ||
| const site = manifest.site; | ||
| const compressHTML = manifest.compressHTML; | ||
| const build = { | ||
| format: manifest.buildFormat, | ||
| }; | ||
| export { base, i18n, trailingSlash, site, compressHTML, build, image }; | ||
| `; | ||
| return { code }; | ||
| return { code: clientConfigCode }; | ||
| } | ||
@@ -67,0 +69,0 @@ if (id === RESOLVED_VIRTUAL_SERVER_ID) { |
@@ -76,2 +76,3 @@ /** | ||
| "heading", | ||
| "image", | ||
| "img", | ||
@@ -84,3 +85,2 @@ "list", | ||
| "math", | ||
| "menuitemradio", | ||
| "navigation", | ||
@@ -138,3 +138,3 @@ "none", | ||
| ["hr", "separator"], | ||
| ["img", "img"], | ||
| ["img", "image"], | ||
| ["li", "listitem"], | ||
@@ -187,3 +187,3 @@ ["link", "link"], | ||
| const ariaRoles = new Set( | ||
| "alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox command complementary composite contentinfo definition deletion dialog directory document emphasis feed figure form generic grid gridcell group heading img input insertion landmark link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status strong structure subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem widget window".split( | ||
| "alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox command complementary composite contentinfo definition deletion dialog directory document emphasis feed figure form generic grid gridcell group heading image img input insertion landmark link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status strong structure subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem widget window".split( | ||
| " " | ||
@@ -190,0 +190,0 @@ ) |
@@ -0,1 +1,5 @@ | ||
| import { | ||
| getPropagationHint as getHint, | ||
| isPropagatingHint | ||
| } from "../../../../core/head-propagation/resolver.js"; | ||
| function isAstroComponentFactory(obj) { | ||
@@ -5,11 +9,6 @@ return obj == null ? false : obj.isAstroComponentFactory === true; | ||
| function isAPropagatingComponent(result, factory) { | ||
| const hint = getPropagationHint(result, factory); | ||
| return hint === "in-tree" || hint === "self"; | ||
| return isPropagatingHint(getPropagationHint(result, factory)); | ||
| } | ||
| function getPropagationHint(result, factory) { | ||
| let hint = factory.propagation || "none"; | ||
| if (factory.moduleId && result.componentMetadata.has(factory.moduleId) && hint === "none") { | ||
| hint = result.componentMetadata.get(factory.moduleId).propagation; | ||
| } | ||
| return hint; | ||
| return getHint(result, factory); | ||
| } | ||
@@ -16,0 +15,0 @@ export { |
| import { isPromise } from "../../util.js"; | ||
| import { renderChild } from "../any.js"; | ||
| import { isAPropagatingComponent } from "./factory.js"; | ||
| import { registerIfPropagating } from "../head-propagation/runtime.js"; | ||
| import { isHeadAndContent } from "./head-and-content.js"; | ||
@@ -73,5 +73,3 @@ const astroComponentInstanceSym = /* @__PURE__ */ Symbol.for("astro.componentInstance"); | ||
| const instance = new AstroComponentInstance(result, props, slots, factory); | ||
| if (isAPropagatingComponent(result, factory)) { | ||
| result._metadata.propagators.add(instance); | ||
| } | ||
| registerIfPropagating(result, factory, instance); | ||
| return instance; | ||
@@ -78,0 +76,0 @@ } |
@@ -10,2 +10,3 @@ import { AstroError, AstroErrorData } from "../../../../core/errors/index.js"; | ||
| import { promiseWithResolvers } from "../util.js"; | ||
| import { bufferPropagatedHead } from "../head-propagation/runtime.js"; | ||
| import { isHeadAndContent } from "./head-and-content.js"; | ||
@@ -126,13 +127,3 @@ import { isRenderTemplateResult } from "./render-template.js"; | ||
| async function bufferHeadContent(result) { | ||
| const iterator = result._metadata.propagators.values(); | ||
| while (true) { | ||
| const { value, done } = iterator.next(); | ||
| if (done) { | ||
| break; | ||
| } | ||
| const returnValue = await value.init(result); | ||
| if (isHeadAndContent(returnValue) && returnValue.head) { | ||
| result._metadata.extraHead.push(returnValue.head); | ||
| } | ||
| } | ||
| await bufferPropagatedHead(result); | ||
| } | ||
@@ -139,0 +130,0 @@ async function renderToAsyncIterable(result, componentFactory, props, children, isPage = false, route) { |
@@ -7,2 +7,3 @@ import { markHTMLString } from "../escape.js"; | ||
| } from "../scripts.js"; | ||
| import { getInstructionRenderState, shouldRenderInstruction } from "./head-propagation/runtime.js"; | ||
| import { renderAllHeadContent } from "./head.js"; | ||
@@ -35,3 +36,3 @@ import { isRenderInstruction } from "./instruction.js"; | ||
| case "head": { | ||
| if (result._metadata.hasRenderedHead || result.partial) { | ||
| if (!shouldRenderInstruction("head", getInstructionRenderState(result))) { | ||
| return ""; | ||
@@ -42,3 +43,3 @@ } | ||
| case "maybe-head": { | ||
| if (result._metadata.hasRenderedHead || result._metadata.headInTree || result.partial) { | ||
| if (!shouldRenderInstruction("maybe-head", getInstructionRenderState(result))) { | ||
| return ""; | ||
@@ -45,0 +46,0 @@ } |
| import { renderToAsyncIterable, renderToReadableStream, renderToString } from "./astro/render.js"; | ||
| import { encoder } from "./common.js"; | ||
| import { renderComponentToString } from "./component.js"; | ||
| import { markHTMLString } from "../escape.js"; | ||
| import { renderCspContent } from "./csp.js"; | ||
@@ -16,3 +17,6 @@ import { isDeno, isNode } from "./util.js"; | ||
| if (result._experimentalQueuedRendering && result._experimentalQueuedRendering.enabled) { | ||
| const vnode = await componentFactory(pageProps); | ||
| let vnode = await componentFactory(pageProps); | ||
| if (componentFactory["astro:html"] && typeof vnode === "string") { | ||
| vnode = markHTMLString(vnode); | ||
| } | ||
| const queue = await buildRenderQueue( | ||
@@ -19,0 +23,0 @@ vnode, |
| import type { SSRResult } from '../../types/public/internal.js'; | ||
| import type { TransitionAnimationPair, TransitionAnimationValue } from '../../types/public/view-transitions.js'; | ||
| import type { TransitionAnimation, TransitionAnimationPair, TransitionAnimationValue } from '../../types/public/view-transitions.js'; | ||
| export declare function createTransitionScope(result: SSRResult, hash: string): string; | ||
| export declare function reEncode(s: string): string; | ||
| export declare function renderTransition(result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string): string; | ||
@@ -10,1 +11,18 @@ /** @deprecated This will be removed in Astro 7 */ | ||
| }; | ||
| export declare class ViewTransitionStyleSheet { | ||
| private scope; | ||
| private name; | ||
| private modern; | ||
| private fallback; | ||
| constructor(scope: string, name: string); | ||
| toString(): string; | ||
| private layer; | ||
| private addRule; | ||
| addAnimationRaw(image: 'old' | 'new' | 'group', animation: string): void; | ||
| addModern(image: 'old' | 'new' | 'group', animation: string): void; | ||
| addFallback(image: 'old' | 'new' | 'group', animation: string): void; | ||
| addAnimationPair(direction: 'forwards' | 'backwards' | string, image: 'old' | 'new', rules: TransitionAnimation | TransitionAnimation[]): void; | ||
| } | ||
| export declare function stringifyAnimation(anim: TransitionAnimation | TransitionAnimation[]): string; | ||
| export declare function stringifyAnimations(anims: TransitionAnimation[]): string; | ||
| export declare function toTimeValue(num: number | string): string; |
@@ -177,5 +177,10 @@ import cssesc from "../../transitions/cssesc.js"; | ||
| export { | ||
| ViewTransitionStyleSheet, | ||
| createAnimationScope, | ||
| createTransitionScope, | ||
| renderTransition | ||
| reEncode, | ||
| renderTransition, | ||
| stringifyAnimation, | ||
| stringifyAnimations, | ||
| toTimeValue | ||
| }; |
@@ -12,3 +12,3 @@ import type { Direction, NavigationTypeString } from './types.js'; | ||
| export declare const TRANSITION_PAGE_LOAD = "astro:page-load"; | ||
| type Events = typeof TRANSITION_AFTER_PREPARATION | typeof TRANSITION_AFTER_SWAP | typeof TRANSITION_PAGE_LOAD; | ||
| type Events = 'astro:after-preparation' | 'astro:after-swap' | 'astro:page-load'; | ||
| export declare const triggerEvent: (name: Events) => boolean; | ||
@@ -15,0 +15,0 @@ export declare const onPageLoad: () => boolean; |
@@ -8,3 +8,3 @@ import { swap } from "./swap-functions.js"; | ||
| const triggerEvent = (name) => document.dispatchEvent(new Event(name)); | ||
| const onPageLoad = () => triggerEvent(TRANSITION_PAGE_LOAD); | ||
| const onPageLoad = () => triggerEvent("astro:page-load"); | ||
| class BeforeEvent extends Event { | ||
@@ -47,3 +47,3 @@ from; | ||
| super( | ||
| TRANSITION_BEFORE_PREPARATION, | ||
| "astro:before-preparation", | ||
| { cancelable: true }, | ||
@@ -67,3 +67,3 @@ from, | ||
| } | ||
| const isTransitionBeforeSwapEvent = (value) => value.type === TRANSITION_BEFORE_SWAP; | ||
| const isTransitionBeforeSwapEvent = (value) => value.type === "astro:before-swap"; | ||
| class TransitionBeforeSwapEvent extends BeforeEvent { | ||
@@ -75,3 +75,3 @@ direction; | ||
| super( | ||
| TRANSITION_BEFORE_SWAP, | ||
| "astro:before-swap", | ||
| void 0, | ||
@@ -113,3 +113,3 @@ afterPreparation.from, | ||
| if (!event.defaultPrevented) { | ||
| triggerEvent(TRANSITION_AFTER_PREPARATION); | ||
| triggerEvent("astro:after-preparation"); | ||
| if (event.navigationType !== "traverse") { | ||
@@ -116,0 +116,0 @@ updateScrollPosition({ scrollX, scrollY }); |
| import { internalFetchHeaders } from "virtual:astro:adapter-config/client"; | ||
| import { | ||
| doPreparation, | ||
| doSwap, | ||
| TRANSITION_AFTER_SWAP, | ||
| onPageLoad, | ||
| triggerEvent, | ||
| updateScrollPosition | ||
| } from "./events.js"; | ||
| import { doPreparation, doSwap, onPageLoad, triggerEvent, updateScrollPosition } from "./events.js"; | ||
| import { detectScriptExecuted } from "./swap-functions.js"; | ||
@@ -215,9 +208,7 @@ const inBrowser = import.meta.env.SSR === false; | ||
| moveToLocation(swapEvent.to, swapEvent.from, options, pageTitleForBrowserHistory, historyState); | ||
| triggerEvent(TRANSITION_AFTER_SWAP); | ||
| if (fallback === "animate") { | ||
| if (!currentTransition.transitionSkipped && !swapEvent.signal.aborted) { | ||
| animate("new").finally(() => currentTransition.viewTransitionFinished()); | ||
| } else { | ||
| currentTransition.viewTransitionFinished(); | ||
| } | ||
| triggerEvent("astro:after-swap"); | ||
| if (fallback === "animate" && !currentTransition.transitionSkipped && !swapEvent.signal.aborted) { | ||
| animate("new").finally(() => currentTransition.viewTransitionFinished()); | ||
| } else { | ||
| currentTransition.viewTransitionFinished?.(); | ||
| } | ||
@@ -231,3 +222,3 @@ } | ||
| } | ||
| async function transition(direction, from, to, options, historyState) { | ||
| async function transition(direction, from, to, options, historyState, hasUAVisualTransition = false) { | ||
| const currentNavigation = abortAndRecreateMostRecentNavigation(); | ||
@@ -328,3 +319,3 @@ if (!transitionEnabledOnThisPage() || location.origin !== to.origin) { | ||
| document.documentElement.setAttribute(DIRECTION_ATTR, prepEvent.direction); | ||
| if (supportsViewTransitions) { | ||
| if (supportsViewTransitions && !hasUAVisualTransition) { | ||
| currentTransition.viewTransition = document.startViewTransition( | ||
@@ -336,3 +327,9 @@ async () => await updateDOM(prepEvent, options, currentTransition, historyState) | ||
| await Promise.resolve(); | ||
| await updateDOM(prepEvent, options, currentTransition, historyState, getFallback()); | ||
| await updateDOM( | ||
| prepEvent, | ||
| options, | ||
| currentTransition, | ||
| historyState, | ||
| hasUAVisualTransition ? "swap" : getFallback() | ||
| ); | ||
| return void 0; | ||
@@ -403,3 +400,10 @@ })(); | ||
| currentHistoryIndex = nextIndex; | ||
| transition(direction, originalLocation, new URL(location.href), {}, state); | ||
| transition( | ||
| direction, | ||
| originalLocation, | ||
| new URL(location.href), | ||
| {}, | ||
| state, | ||
| ev.hasUAVisualTransition | ||
| ); | ||
| } | ||
@@ -406,0 +410,0 @@ const onScrollEnd = () => { |
@@ -207,4 +207,7 @@ import type { AddressInfo } from 'node:net'; | ||
| * Renders a single page. Called by Astro for each path returned by getStaticPaths. | ||
| * @param request - The request to render | ||
| * @param options - Render options including routeData | ||
| * @param request - The request to render. The URL reflects the build format | ||
| * (e.g. trailing slash for `directory` format). To get the canonical pathname, | ||
| * use the `pathname` from the `PathWithRoute` entry returned by `getStaticPaths`. | ||
| * @param options - Render options | ||
| * @param options.routeData - The matched route for this path | ||
| */ | ||
@@ -384,2 +387,6 @@ render: (request: Request, options: { | ||
| /** | ||
| * {@link RouteData.fallbackRoutes} | ||
| */ | ||
| fallbackRoutes: IntegrationResolvedRoute[]; | ||
| /** | ||
| * @param {any} data The optional parameters of the route | ||
@@ -386,0 +393,0 @@ * |
@@ -29,2 +29,7 @@ import type http from 'node:http'; | ||
| clearRouteCache(): void; | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change. | ||
| */ | ||
| clearMiddleware(): void; | ||
| devMatch(pathname: string): Promise<DevMatch | undefined>; | ||
@@ -31,0 +36,0 @@ static create(manifest: SSRManifest, routesList: RoutesList, logger: Logger, loader: ModuleLoader, settings: AstroSettings, getDebugInfo: () => Promise<string>): Promise<AstroServerApp>; |
| import { removeTrailingForwardSlash } from "@astrojs/internal-helpers/path"; | ||
| import { BaseApp } from "../core/app/entrypoints/index.js"; | ||
| import { getFirstForwardedValue, validateForwardedHeaders } from "../core/app/validate-headers.js"; | ||
| import { shouldAppendForwardSlash } from "../core/build/util.js"; | ||
@@ -51,2 +52,9 @@ import { clientLocalsSymbol } from "../core/constants.js"; | ||
| } | ||
| /** | ||
| * Clears the cached middleware so it is re-resolved on the next request. | ||
| * Called via HMR when middleware files change. | ||
| */ | ||
| clearMiddleware() { | ||
| this.pipeline.clearMiddleware(); | ||
| } | ||
| async devMatch(pathname) { | ||
@@ -90,3 +98,11 @@ const matchedRoute = await matchRoute( | ||
| }) { | ||
| const origin = `${isHttps ? "https" : "http"}://${incomingRequest.headers[":authority"] ?? incomingRequest.headers.host}`; | ||
| const validated = validateForwardedHeaders( | ||
| getFirstForwardedValue(incomingRequest.headers["x-forwarded-proto"]), | ||
| getFirstForwardedValue(incomingRequest.headers["x-forwarded-host"]), | ||
| getFirstForwardedValue(incomingRequest.headers["x-forwarded-port"]), | ||
| this.manifest.allowedDomains | ||
| ); | ||
| const protocol = validated.protocol ?? (isHttps ? "https" : "http"); | ||
| const host = validated.host ?? incomingRequest.headers[":authority"] ?? incomingRequest.headers.host; | ||
| const origin = `${protocol}://${host}`; | ||
| const url = new URL(origin + incomingRequest.url); | ||
@@ -93,0 +109,0 @@ let pathname; |
@@ -61,2 +61,6 @@ import { manifest } from "virtual:astro:manifest"; | ||
| }); | ||
| import.meta.hot.on("astro:middleware-updated", () => { | ||
| app.clearMiddleware(); | ||
| actualLogger.debug("router", "Middleware cache cleared due to file change"); | ||
| }); | ||
| } | ||
@@ -63,0 +67,0 @@ return { |
@@ -214,3 +214,4 @@ import { AsyncLocalStorage } from "node:async_hooks"; | ||
| i18n: i18nManifest, | ||
| checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false, | ||
| checkOrigin: settings.config.security?.checkOrigin ?? false, | ||
| allowedDomains: settings.config.security?.allowedDomains, | ||
| actionBodySizeLimit: settings.config.security?.actionBodySizeLimit ? settings.config.security.actionBodySizeLimit : 1024 * 1024, | ||
@@ -217,0 +218,0 @@ // 1mb default |
| import npath from "node:path"; | ||
| import { isCSSRequest } from "vite"; | ||
| import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "../core/constants.js"; | ||
| import { isPropagatedAssetBoundary } from "../core/head-propagation/boundary.js"; | ||
| import { unwrapId } from "../core/util.js"; | ||
| import { hasSpecialQueries } from "../vite-plugin-utils/index.js"; | ||
| import { PROPAGATED_ASSET_QUERY_PARAM } from "../content/consts.js"; | ||
| const fileExtensionsToSSR = /* @__PURE__ */ new Set([".astro", ".mdoc", ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]); | ||
@@ -38,3 +38,3 @@ const STRIP_QUERY_PARAMS_REGEX = /\?.*$/; | ||
| const isFileTypeNeedingSSR = fileExtensionsToSSR.has(npath.extname(importedModulePathname)); | ||
| const isPropagationStoppingPoint = importedModule.id.includes(PROPAGATED_ASSET_QUERY_PARAM); | ||
| const isPropagationStoppingPoint = isPropagatedAssetBoundary(importedModule.id); | ||
| if (isFileTypeNeedingSSR && // Should not SSR a module with ?astroPropagatedAssets | ||
@@ -41,0 +41,0 @@ !isPropagationStoppingPoint) { |
@@ -1,21 +0,47 @@ | ||
| import { getParentModuleInfos, getTopLevelPageModuleInfos } from "../core/build/graph.js"; | ||
| import { hasHeadInjectComment } from "../core/head-propagation/comment.js"; | ||
| import { | ||
| buildImporterGraphFromModuleInfo, | ||
| computeInTreeAncestors | ||
| } from "../core/head-propagation/graph.js"; | ||
| import { getTopLevelPageModuleInfos } from "../core/build/graph.js"; | ||
| import { getAstroMetadata } from "../vite-plugin-astro/index.js"; | ||
| import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js"; | ||
| const injectExp = /(?:^\/\/|\/\/!)\s*astro-head-inject/; | ||
| function configHeadVitePlugin() { | ||
| let environment; | ||
| function propagateMetadata(id, prop, value, seen = /* @__PURE__ */ new Set()) { | ||
| if (seen.has(id)) return; | ||
| seen.add(id); | ||
| const mod = environment.moduleGraph.getModuleById(id); | ||
| const info = this.getModuleInfo(id); | ||
| if (info?.meta.astro) { | ||
| const astroMetadata = getAstroMetadata(info); | ||
| if (astroMetadata) { | ||
| Reflect.set(astroMetadata, prop, value); | ||
| function buildImporterGraphFromEnvironment(seed) { | ||
| const queue = [seed]; | ||
| const collected = /* @__PURE__ */ new Set(); | ||
| while (queue.length > 0) { | ||
| const current = queue.pop(); | ||
| if (collected.has(current)) continue; | ||
| collected.add(current); | ||
| const mod = environment.moduleGraph.getModuleById(current); | ||
| for (const importer of mod?.importers ?? []) { | ||
| if (importer.id) { | ||
| queue.push(importer.id); | ||
| } | ||
| } | ||
| } | ||
| for (const parent of mod?.importers || []) { | ||
| if (parent.id) { | ||
| propagateMetadata.call(this, parent.id, prop, value, seen); | ||
| return buildImporterGraphFromModuleInfo(collected, (id) => { | ||
| const mod = environment.moduleGraph.getModuleById(id); | ||
| if (!mod) return null; | ||
| return { | ||
| importers: Array.from(mod.importers).map((importer) => importer.id).filter((moduleId) => !!moduleId), | ||
| dynamicImporters: [] | ||
| }; | ||
| }); | ||
| } | ||
| function propagateMetadata(seed, prop, value) { | ||
| const importerGraph = buildImporterGraphFromEnvironment(seed); | ||
| const allAncestors = computeInTreeAncestors({ | ||
| seeds: [seed], | ||
| importerGraph | ||
| }); | ||
| for (const id of allAncestors) { | ||
| const info = this.getModuleInfo(id); | ||
| if (info?.meta.astro) { | ||
| const astroMetadata = getAstroMetadata(info); | ||
| if (astroMetadata) { | ||
| Reflect.set(astroMetadata, prop, value); | ||
| } | ||
| } | ||
@@ -55,3 +81,3 @@ } | ||
| } | ||
| if (injectExp.test(source)) { | ||
| if (hasHeadInjectComment(source)) { | ||
| propagateMetadata.call(this, id, "propagation", "in-tree"); | ||
@@ -70,2 +96,5 @@ } | ||
| const map = internals.componentMetadata; | ||
| const moduleIds = /* @__PURE__ */ new Set(); | ||
| const selfPropagationSeeds = /* @__PURE__ */ new Set(); | ||
| const commentPropagationSeeds = /* @__PURE__ */ new Set(); | ||
| function getOrCreateMetadata(id) { | ||
@@ -83,2 +112,3 @@ if (map.has(id)) return map.get(id); | ||
| for (const [id, mod] of Object.entries(output.modules)) { | ||
| moduleIds.add(id); | ||
| const modinfo = this.getModuleInfo(id); | ||
@@ -94,17 +124,25 @@ if (modinfo) { | ||
| if (meta?.propagation === "self") { | ||
| for (const info of getParentModuleInfos(id, this)) { | ||
| let metadata = getOrCreateMetadata(info.id); | ||
| if (metadata.propagation !== "self") { | ||
| metadata.propagation = "in-tree"; | ||
| } | ||
| } | ||
| selfPropagationSeeds.add(id); | ||
| } | ||
| } | ||
| if (mod.code && injectExp.test(mod.code)) { | ||
| for (const info of getParentModuleInfos(id, this)) { | ||
| getOrCreateMetadata(info.id).propagation = "in-tree"; | ||
| } | ||
| if (mod.code && hasHeadInjectComment(mod.code)) { | ||
| commentPropagationSeeds.add(id); | ||
| } | ||
| } | ||
| } | ||
| const importerGraph = buildImporterGraphFromModuleInfo( | ||
| moduleIds, | ||
| (id) => this.getModuleInfo(id) | ||
| ); | ||
| const allPropagationSeeds = /* @__PURE__ */ new Set([...selfPropagationSeeds, ...commentPropagationSeeds]); | ||
| const allAncestors = computeInTreeAncestors({ | ||
| seeds: allPropagationSeeds, | ||
| importerGraph | ||
| }); | ||
| for (const id of allAncestors) { | ||
| const metadata = getOrCreateMetadata(id); | ||
| if (metadata.propagation !== "self") { | ||
| metadata.propagation = "in-tree"; | ||
| } | ||
| } | ||
| } | ||
@@ -111,0 +149,0 @@ }; |
@@ -7,4 +7,8 @@ import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js"; | ||
| function astroScriptsPlugin({ settings }) { | ||
| let command; | ||
| return { | ||
| name: "astro:scripts", | ||
| config(_, env) { | ||
| command = env.command; | ||
| }, | ||
| resolveId: { | ||
@@ -41,2 +45,3 @@ filter: { | ||
| buildStart() { | ||
| if (command === "serve") return; | ||
| const hasHydrationScripts = settings.scripts.some((s) => s.stage === "before-hydration"); | ||
@@ -43,0 +48,0 @@ if (hasHydrationScripts && (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender || this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr)) { |
+11
-11
| { | ||
| "name": "astro", | ||
| "version": "6.0.8", | ||
| "version": "6.1.0", | ||
| "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", | ||
@@ -104,5 +104,5 @@ "type": "module", | ||
| "dependencies": { | ||
| "@astrojs/compiler": "^3.0.0", | ||
| "@astrojs/compiler": "^3.0.1", | ||
| "@capsizecss/unpack": "^4.0.0", | ||
| "@clack/prompts": "^1.0.1", | ||
| "@clack/prompts": "^1.1.0", | ||
| "@oslojs/encoding": "^1.1.0", | ||
@@ -140,7 +140,7 @@ "@rollup/pluginutils": "^5.3.0", | ||
| "semver": "^7.7.4", | ||
| "shiki": "^4.0.0", | ||
| "shiki": "^4.0.2", | ||
| "smol-toml": "^1.6.0", | ||
| "svgo": "^4.0.0", | ||
| "tinyclip": "^0.1.6", | ||
| "tinyexec": "^1.0.2", | ||
| "svgo": "^4.0.1", | ||
| "tinyclip": "^0.1.12", | ||
| "tinyexec": "^1.0.4", | ||
| "tinyglobby": "^0.2.15", | ||
@@ -159,3 +159,3 @@ "tsconfck": "^3.1.6", | ||
| "@astrojs/internal-helpers": "0.8.0", | ||
| "@astrojs/markdown-remark": "7.0.1", | ||
| "@astrojs/markdown-remark": "7.1.0", | ||
| "@astrojs/telemetry": "3.3.0" | ||
@@ -167,3 +167,3 @@ }, | ||
| "devDependencies": { | ||
| "@astrojs/compiler-rs": "^0.1.4", | ||
| "@astrojs/compiler-rs": "^0.1.6", | ||
| "@playwright/test": "1.58.2", | ||
@@ -192,7 +192,7 @@ "@types/aria-query": "^5.0.4", | ||
| "rollup": "^4.58.0", | ||
| "sass": "^1.97.3", | ||
| "sass": "^1.98.0", | ||
| "typescript": "^5.9.3", | ||
| "undici": "^7.22.0", | ||
| "unified": "^11.0.5", | ||
| "vitest": "^3.2.4", | ||
| "vitest": "^4.1.0", | ||
| "@astrojs/check": "0.9.8", | ||
@@ -199,0 +199,0 @@ "astro-scripts": "0.0.14" |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 7 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 7 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2644765
1.21%1195
1.19%69816
1.18%149
0.68%57
3.64%+ Added
- Removed
Updated
Updated
Updated
Updated
Updated
Updated