astro 7.0.0-alpha.0 → 7.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/components/Code.astro +1 -1
  2. package/dist/actions/handler.d.ts +32 -0
  3. package/dist/actions/handler.js +45 -0
  4. package/dist/actions/runtime/server.js +1 -1
  5. package/dist/assets/build/generate.js +1 -1
  6. package/dist/assets/build/remote.d.ts +3 -2
  7. package/dist/assets/build/remote.js +16 -9
  8. package/dist/assets/endpoint/dev.js +1 -1
  9. package/dist/assets/endpoint/generic.js +8 -20
  10. package/dist/assets/endpoint/loadImage.d.ts +11 -0
  11. package/dist/assets/endpoint/loadImage.js +19 -0
  12. package/dist/assets/endpoint/shared.js +7 -2
  13. package/dist/assets/fonts/config.d.ts +4 -4
  14. package/dist/assets/fonts/core/optimize-fallbacks.js +38 -13
  15. package/dist/assets/fonts/definitions.d.ts +2 -2
  16. package/dist/assets/fonts/infra/system-fallbacks-provider.d.ts +2 -2
  17. package/dist/assets/fonts/infra/system-fallbacks-provider.js +46 -9
  18. package/dist/assets/fonts/types.d.ts +1 -0
  19. package/dist/assets/index.d.ts +1 -0
  20. package/dist/assets/index.js +2 -0
  21. package/dist/assets/internal.js +22 -3
  22. package/dist/assets/services/service.d.ts +1 -1
  23. package/dist/assets/services/service.js +9 -9
  24. package/dist/assets/services/sharp.js +53 -18
  25. package/dist/assets/utils/generateImageStylesCSS.js +26 -6
  26. package/dist/assets/utils/index.d.ts +1 -0
  27. package/dist/assets/utils/index.js +2 -0
  28. package/dist/assets/utils/inferSourceFormat.d.ts +8 -3
  29. package/dist/assets/utils/inferSourceFormat.js +15 -4
  30. package/dist/assets/utils/metadata.js +1 -1
  31. package/dist/assets/utils/redirectValidation.d.ts +48 -0
  32. package/dist/assets/utils/redirectValidation.js +48 -0
  33. package/dist/assets/utils/remoteProbe.js +25 -2
  34. package/dist/assets/utils/vendor/image-size/types/svg.js +1 -1
  35. package/dist/cli/add/index.js +7 -58
  36. package/dist/cli/dev/background.d.ts +16 -0
  37. package/dist/cli/dev/background.js +116 -0
  38. package/dist/cli/dev/index.js +82 -3
  39. package/dist/cli/dev/logs.d.ts +6 -0
  40. package/dist/cli/dev/logs.js +72 -0
  41. package/dist/cli/dev/status.d.ts +15 -0
  42. package/dist/cli/dev/status.js +27 -0
  43. package/dist/cli/dev/stop.d.ts +12 -0
  44. package/dist/cli/dev/stop.js +43 -0
  45. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  46. package/dist/container/index.js +18 -14
  47. package/dist/content/content-layer.js +16 -11
  48. package/dist/content/data-store.d.ts +1 -1
  49. package/dist/content/loaders/types.d.ts +1 -1
  50. package/dist/content/runtime-assets.d.ts +2 -2
  51. package/dist/content/runtime.d.ts +1 -1
  52. package/dist/content/runtime.js +9 -4
  53. package/dist/content/server-listeners.js +0 -4
  54. package/dist/content/types-generator.js +5 -1
  55. package/dist/content/utils.d.ts +1 -1
  56. package/dist/content/utils.js +1 -1
  57. package/dist/content/vite-plugin-content-assets.js +1 -0
  58. package/dist/content/vite-plugin-content-virtual-mod.js +9 -1
  59. package/dist/core/app/base.d.ts +42 -15
  60. package/dist/core/app/base.js +151 -375
  61. package/dist/core/app/dev/app.d.ts +3 -2
  62. package/dist/core/app/dev/app.js +4 -60
  63. package/dist/core/app/entrypoints/node.d.ts +1 -1
  64. package/dist/core/app/entrypoints/node.js +2 -0
  65. package/dist/core/app/entrypoints/virtual/dev.js +2 -0
  66. package/dist/core/app/entrypoints/virtual/prod.js +4 -1
  67. package/dist/core/app/node.d.ts +16 -0
  68. package/dist/core/app/node.js +59 -13
  69. package/dist/core/app/prepare-response.d.ts +11 -0
  70. package/dist/core/app/prepare-response.js +18 -0
  71. package/dist/core/app/render-options.d.ts +11 -0
  72. package/dist/core/app/render-options.js +11 -0
  73. package/dist/core/base-pipeline.d.ts +48 -1
  74. package/dist/core/base-pipeline.js +63 -8
  75. package/dist/core/build/app.d.ts +3 -4
  76. package/dist/core/build/app.js +3 -17
  77. package/dist/core/build/generate.js +8 -1
  78. package/dist/core/build/index.d.ts +0 -11
  79. package/dist/core/build/index.js +0 -3
  80. package/dist/core/build/internal.d.ts +7 -0
  81. package/dist/core/build/plugins/plugin-chunk-imports.d.ts +6 -0
  82. package/dist/core/build/plugins/plugin-chunk-imports.js +29 -17
  83. package/dist/core/build/plugins/plugin-css.js +40 -1
  84. package/dist/core/build/plugins/plugin-internals.js +1 -1
  85. package/dist/core/build/plugins/plugin-manifest.js +11 -1
  86. package/dist/core/build/static-build.js +22 -141
  87. package/dist/core/build/types.d.ts +0 -1
  88. package/dist/core/build/util.js +8 -1
  89. package/dist/core/build/vite-build-config.d.ts +28 -0
  90. package/dist/core/build/vite-build-config.js +165 -0
  91. package/dist/core/cache/handler.d.ts +29 -0
  92. package/dist/core/cache/handler.js +81 -0
  93. package/dist/core/compile/style.js +18 -1
  94. package/dist/core/config/index.d.ts +1 -1
  95. package/dist/core/config/index.js +4 -1
  96. package/dist/core/config/merge.js +4 -0
  97. package/dist/core/config/schemas/base.d.ts +25 -13
  98. package/dist/core/config/schemas/base.js +39 -10
  99. package/dist/core/config/schemas/relative.d.ts +76 -49
  100. package/dist/core/config/schemas/relative.js +2 -3
  101. package/dist/core/config/settings.js +2 -4
  102. package/dist/core/config/tsconfig.d.ts +24 -9
  103. package/dist/core/config/tsconfig.js +54 -45
  104. package/dist/core/config/validate.js +59 -0
  105. package/dist/core/constants.d.ts +27 -1
  106. package/dist/core/constants.js +14 -1
  107. package/dist/core/cookies/cookies.d.ts +7 -2
  108. package/dist/core/cookies/cookies.js +11 -4
  109. package/dist/core/cookies/response.d.ts +1 -1
  110. package/dist/core/cookies/response.js +1 -2
  111. package/dist/core/create-vite.js +18 -1
  112. package/dist/core/csp/config.js +17 -5
  113. package/dist/core/csp/runtime.js +6 -4
  114. package/dist/core/dev/dev.d.ts +1 -0
  115. package/dist/core/dev/dev.js +4 -13
  116. package/dist/core/dev/lockfile.d.ts +54 -0
  117. package/dist/core/dev/lockfile.js +93 -0
  118. package/dist/core/errors/build-handler.d.ts +17 -0
  119. package/dist/core/errors/build-handler.js +22 -0
  120. package/dist/core/errors/default-handler.d.ts +14 -0
  121. package/dist/core/errors/default-handler.js +144 -0
  122. package/dist/core/errors/dev-handler.d.ts +21 -0
  123. package/dist/core/errors/dev-handler.js +82 -0
  124. package/dist/core/errors/errors-data.d.ts +43 -38
  125. package/dist/core/errors/errors-data.js +79 -73
  126. package/dist/core/errors/handler.d.ts +9 -0
  127. package/dist/core/errors/handler.js +0 -0
  128. package/dist/core/errors/zod-error-map.js +30 -1
  129. package/dist/core/fetch/default-handler.d.ts +17 -0
  130. package/dist/core/fetch/default-handler.js +45 -0
  131. package/dist/core/fetch/fetch-state.d.ts +230 -0
  132. package/dist/core/fetch/fetch-state.js +896 -0
  133. package/dist/core/fetch/index.d.ts +61 -0
  134. package/dist/core/fetch/index.js +121 -0
  135. package/dist/core/fetch/types.d.ts +25 -0
  136. package/dist/core/fetch/types.js +0 -0
  137. package/dist/core/fetch/vite-plugin.d.ts +5 -0
  138. package/dist/core/fetch/vite-plugin.js +76 -0
  139. package/dist/core/hono/index.d.ts +21 -0
  140. package/dist/core/hono/index.js +101 -0
  141. package/dist/core/i18n/domain.d.ts +12 -0
  142. package/dist/core/i18n/domain.js +66 -0
  143. package/dist/core/i18n/handler.d.ts +18 -0
  144. package/dist/core/i18n/handler.js +122 -0
  145. package/dist/core/logger/core.d.ts +9 -1
  146. package/dist/core/logger/core.js +17 -1
  147. package/dist/core/messages/runtime.d.ts +0 -3
  148. package/dist/core/messages/runtime.js +1 -9
  149. package/dist/core/middleware/astro-middleware.d.ts +27 -0
  150. package/dist/core/middleware/astro-middleware.js +51 -0
  151. package/dist/core/module-loader/vite.js +1 -2
  152. package/dist/core/pages/handler.d.ts +20 -0
  153. package/dist/core/pages/handler.js +75 -0
  154. package/dist/core/preview/index.js +6 -5
  155. package/dist/core/preview/static-preview-server.js +5 -2
  156. package/dist/core/redirects/render.d.ts +2 -2
  157. package/dist/core/redirects/render.js +7 -8
  158. package/dist/core/render/params-and-props.js +2 -2
  159. package/dist/core/render/route-cache.d.ts +1 -0
  160. package/dist/core/render/route-cache.js +4 -4
  161. package/dist/core/render/slots.js +9 -2
  162. package/dist/core/rewrites/handler.d.ts +37 -0
  163. package/dist/core/rewrites/handler.js +67 -0
  164. package/dist/core/routing/3xx.js +8 -4
  165. package/dist/core/routing/create-manifest.js +11 -1
  166. package/dist/core/routing/handler.d.ts +17 -0
  167. package/dist/core/routing/handler.js +171 -0
  168. package/dist/core/routing/match.d.ts +0 -7
  169. package/dist/core/routing/match.js +0 -5
  170. package/dist/core/routing/parse-route.js +1 -1
  171. package/dist/core/routing/pattern.js +1 -1
  172. package/dist/core/routing/rewrite.js +2 -5
  173. package/dist/core/routing/router.d.ts +8 -0
  174. package/dist/core/routing/router.js +28 -0
  175. package/dist/core/routing/trailing-slash-handler.d.ts +18 -0
  176. package/dist/core/routing/trailing-slash-handler.js +67 -0
  177. package/dist/core/routing/validation.js +1 -1
  178. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +6 -1
  179. package/dist/core/server-islands/vite-plugin-server-islands.js +13 -3
  180. package/dist/core/session/config.d.ts +1 -1
  181. package/dist/core/session/drivers.d.ts +1 -1
  182. package/dist/core/session/handler.d.ts +11 -0
  183. package/dist/core/session/handler.js +33 -0
  184. package/dist/core/session/runtime.js +7 -2
  185. package/dist/core/util/normalized-url.d.ts +10 -0
  186. package/dist/core/util/normalized-url.js +24 -0
  187. package/dist/core/util/pathname.d.ts +10 -1
  188. package/dist/core/util/pathname.js +13 -4
  189. package/dist/environments.js +1 -1
  190. package/dist/events/session.d.ts +8 -0
  191. package/dist/events/session.js +11 -0
  192. package/dist/i18n/middleware.d.ts +10 -0
  193. package/dist/i18n/middleware.js +4 -88
  194. package/dist/i18n/utils.js +2 -2
  195. package/dist/jsx/rehype.d.ts +1 -1
  196. package/dist/manifest/virtual-module.js +3 -1
  197. package/dist/markdown/index.d.ts +4 -0
  198. package/dist/markdown/index.js +14 -0
  199. package/dist/prefetch/index.js +12 -7
  200. package/dist/prerender/utils.js +5 -1
  201. package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +9 -0
  202. package/dist/runtime/server/astro-island.js +57 -20
  203. package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
  204. package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
  205. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  206. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  207. package/dist/runtime/server/jsx.js +1 -1
  208. package/dist/runtime/server/render/common.js +10 -4
  209. package/dist/runtime/server/render/component.js +8 -6
  210. package/dist/runtime/server/render/head.js +1 -5
  211. package/dist/runtime/server/render/server-islands.js +2 -1
  212. package/dist/runtime/server/render/util.js +2 -2
  213. package/dist/runtime/server/scripts.js +6 -0
  214. package/dist/runtime/server/transition.d.ts +1 -6
  215. package/dist/runtime/server/transition.js +0 -8
  216. package/dist/transitions/events.d.ts +0 -14
  217. package/dist/transitions/events.js +0 -14
  218. package/dist/transitions/index.d.ts +0 -1
  219. package/dist/transitions/index.js +0 -2
  220. package/dist/transitions/vite-plugin-transitions.js +2 -4
  221. package/dist/types/public/config.d.ts +96 -14
  222. package/dist/types/public/content.d.ts +5 -5
  223. package/dist/types/public/index.d.ts +2 -1
  224. package/dist/types/public/integrations.d.ts +11 -3
  225. package/dist/types/public/internal.d.ts +1 -1
  226. package/dist/types/public/manifest.d.ts +1 -1
  227. package/dist/virtual-modules/i18n.d.ts +2 -2
  228. package/dist/virtual-modules/i18n.js +1 -1
  229. package/dist/vite-plugin-app/app.d.ts +13 -6
  230. package/dist/vite-plugin-app/app.js +51 -89
  231. package/dist/vite-plugin-app/createAstroServerApp.d.ts +3 -1
  232. package/dist/vite-plugin-app/createAstroServerApp.js +4 -3
  233. package/dist/vite-plugin-astro-server/plugin.js +11 -5
  234. package/dist/vite-plugin-astro-server/route-guard.d.ts +33 -0
  235. package/dist/vite-plugin-astro-server/route-guard.js +42 -23
  236. package/dist/vite-plugin-dev-status/index.d.ts +2 -0
  237. package/dist/vite-plugin-dev-status/index.js +15 -0
  238. package/dist/vite-plugin-head/index.js +36 -19
  239. package/dist/vite-plugin-hmr-reload/index.d.ts +1 -1
  240. package/dist/vite-plugin-hmr-reload/index.js +23 -1
  241. package/dist/vite-plugin-integrations-container/index.js +15 -6
  242. package/dist/vite-plugin-markdown/content-entry-type.js +7 -4
  243. package/dist/vite-plugin-markdown/images.js +9 -11
  244. package/dist/vite-plugin-markdown/index.js +12 -11
  245. package/dist/vite-plugin-utils/index.d.ts +1 -0
  246. package/dist/vite-plugin-utils/index.js +10 -1
  247. package/package.json +23 -16
  248. package/templates/content/types.d.ts +1 -0
  249. package/types/transitions.d.ts +0 -7
  250. package/dist/core/render-context.d.ts +0 -77
  251. package/dist/core/render-context.js +0 -826
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { createShikiHighlighter, type ThemePresets } from '@astrojs/markdown-remark/shiki';
2
+ import { createShikiHighlighter, type ThemePresets } from '@astrojs/internal-helpers/shiki';
3
3
  import type { ShikiTransformer, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
4
4
  import { bundledLanguages } from 'shiki/langs';
5
5
  import type { CodeLanguage } from '../dist/types/public/common.js';
@@ -0,0 +1,32 @@
1
+ import type { APIContext } from '../types/public/context.js';
2
+ import type { FetchState } from '../core/fetch/fetch-state.js';
3
+ /**
4
+ * Handles Astro Action requests in both modes:
5
+ *
6
+ * - **RPC**: POST requests to `/_actions/<name>` (originating from the
7
+ * generated JS client). Runs the action and returns the serialized result
8
+ * as the response, so the caller can short-circuit rendering.
9
+ * - **Form**: POST requests with `?_action=<name>` targeting a page route
10
+ * (originating from an HTML `<form action={actions.foo}>`). Runs the
11
+ * action, stashes the result into `locals._actionPayload`, and returns
12
+ * `undefined` so the caller continues to render the page.
13
+ *
14
+ * Non-action requests are a no-op (`undefined`).
15
+ *
16
+ * This handler is invoked at the bottom of the middleware chain, before
17
+ * page dispatch. That placement preserves the existing behavior where
18
+ * user middleware sees action requests and response finalization (cookies,
19
+ * sessions, etc.) runs around the action response.
20
+ */
21
+ export declare class ActionHandler {
22
+ #private;
23
+ /**
24
+ * Run action handling for the current request. Expects the APIContext
25
+ * that is already being used by the render pipeline.
26
+ *
27
+ * Returns a `Response` when the action fully handles the request (RPC),
28
+ * or `undefined` when the caller should continue processing the
29
+ * request (form actions or non-action requests).
30
+ */
31
+ handle(apiContext: APIContext, state: FetchState): Promise<Response | undefined> | undefined;
32
+ }
@@ -0,0 +1,45 @@
1
+ import { PipelineFeatures } from "../core/base-pipeline.js";
2
+ import { getActionContext, serializeActionResult } from "./runtime/server.js";
3
+ class ActionHandler {
4
+ /**
5
+ * Run action handling for the current request. Expects the APIContext
6
+ * that is already being used by the render pipeline.
7
+ *
8
+ * Returns a `Response` when the action fully handles the request (RPC),
9
+ * or `undefined` when the caller should continue processing the
10
+ * request (form actions or non-action requests).
11
+ */
12
+ handle(apiContext, state) {
13
+ state.pipeline.usedFeatures |= PipelineFeatures.actions;
14
+ if (apiContext.isPrerendered) {
15
+ return void 0;
16
+ }
17
+ const { action, setActionResult } = getActionContext(apiContext);
18
+ if (!action) {
19
+ return void 0;
20
+ }
21
+ return this.#executeAction(action, setActionResult);
22
+ }
23
+ async #executeAction(action, setActionResult) {
24
+ const actionResult = await action.handler();
25
+ const serialized = serializeActionResult(actionResult);
26
+ if (action.calledFrom === "rpc") {
27
+ if (serialized.type === "empty") {
28
+ return new Response(null, {
29
+ status: serialized.status
30
+ });
31
+ }
32
+ return new Response(serialized.body, {
33
+ status: serialized.status,
34
+ headers: {
35
+ "Content-Type": serialized.contentType
36
+ }
37
+ });
38
+ }
39
+ setActionResult(action.name, serialized);
40
+ return void 0;
41
+ }
42
+ }
43
+ export {
44
+ ActionHandler
45
+ };
@@ -266,7 +266,7 @@ function handleFormDataGetAll(key, formData, validator) {
266
266
  if (elementValidator instanceof z.$ZodNumber) {
267
267
  return entries.map(Number);
268
268
  } else if (elementValidator instanceof z.$ZodBoolean) {
269
- return entries.map(Boolean);
269
+ return entries.map((v) => v === "true" ? true : v === "false" ? false : Boolean(v));
270
270
  }
271
271
  return entries;
272
272
  }
@@ -234,7 +234,7 @@ function getStaticImageList() {
234
234
  }
235
235
  async function loadImage(path, env) {
236
236
  if (isRemotePath(path)) {
237
- return await loadRemoteImage(path);
237
+ return await loadRemoteImage(path, void 0, env.imageConfig);
238
238
  }
239
239
  return {
240
240
  data: await fs.promises.readFile(getFullImagePath(path, env)),
@@ -1,10 +1,11 @@
1
+ import { type RemoteImageConfig } from '../utils/redirectValidation.js';
1
2
  export type RemoteCacheEntry = {
2
3
  data?: string;
3
4
  expires: number;
4
5
  etag?: string;
5
6
  lastModified?: string;
6
7
  };
7
- export declare function loadRemoteImage(src: string, fetchFn?: typeof fetch): Promise<{
8
+ export declare function loadRemoteImage(src: string, fetchFn?: typeof fetch, imageConfig?: RemoteImageConfig): Promise<{
8
9
  data: Buffer<ArrayBuffer>;
9
10
  expires: number;
10
11
  etag: string | undefined;
@@ -22,7 +23,7 @@ export declare function loadRemoteImage(src: string, fetchFn?: typeof fetch): Pr
22
23
  export declare function revalidateRemoteImage(src: string, revalidationData: {
23
24
  etag?: string;
24
25
  lastModified?: string;
25
- }, fetchFn?: typeof fetch): Promise<{
26
+ }, fetchFn?: typeof fetch, imageConfig?: RemoteImageConfig): Promise<{
26
27
  data: Buffer<ArrayBuffer> | null;
27
28
  expires: number;
28
29
  etag: string | undefined;
@@ -1,15 +1,17 @@
1
1
  import CachePolicy from "http-cache-semantics";
2
- async function loadRemoteImage(src, fetchFn = globalThis.fetch) {
3
- const req = new Request(src);
4
- const res = await fetchFn(req, { redirect: "manual" });
5
- if (res.status >= 300 && res.status < 400) {
6
- throw new Error(`Failed to load remote image ${src}. The request was redirected.`);
7
- }
2
+ import { fetchWithRedirects } from "../utils/redirectValidation.js";
3
+ async function loadRemoteImage(src, fetchFn = globalThis.fetch, imageConfig = { remotePatterns: [], domains: [] }) {
4
+ const res = await fetchWithRedirects({
5
+ url: src,
6
+ fetchFn,
7
+ imageConfig
8
+ });
8
9
  if (!res.ok) {
9
10
  throw new Error(
10
11
  `Failed to load remote image ${src}. The request did not return a 200 OK response. (received ${res.status}))`
11
12
  );
12
13
  }
14
+ const req = new Request(src);
13
15
  const policy = new CachePolicy(webToCachePolicyRequest(req), webToCachePolicyResponse(res));
14
16
  const expires = policy.storable() ? policy.timeToLive() : 0;
15
17
  return {
@@ -19,13 +21,18 @@ async function loadRemoteImage(src, fetchFn = globalThis.fetch) {
19
21
  lastModified: res.headers.get("Last-Modified") ?? void 0
20
22
  };
21
23
  }
22
- async function revalidateRemoteImage(src, revalidationData, fetchFn = globalThis.fetch) {
24
+ async function revalidateRemoteImage(src, revalidationData, fetchFn = globalThis.fetch, imageConfig = { remotePatterns: [], domains: [] }) {
23
25
  const headers = {
24
26
  ...revalidationData.etag && { "If-None-Match": revalidationData.etag },
25
27
  ...revalidationData.lastModified && { "If-Modified-Since": revalidationData.lastModified }
26
28
  };
27
29
  const req = new Request(src, { headers, cache: "no-cache" });
28
- const res = await fetchFn(req, { redirect: "manual" });
30
+ const res = await fetchWithRedirects({
31
+ url: src,
32
+ headers: new Headers(headers),
33
+ imageConfig,
34
+ fetchFn
35
+ });
29
36
  if (!res.ok && res.status !== 304) {
30
37
  if (res.status >= 300 && res.status < 400) {
31
38
  throw new Error(
@@ -38,7 +45,7 @@ async function revalidateRemoteImage(src, revalidationData, fetchFn = globalThis
38
45
  }
39
46
  const data = Buffer.from(await res.arrayBuffer());
40
47
  if (res.ok && !data.length) {
41
- return await loadRemoteImage(src, fetchFn);
48
+ return await loadRemoteImage(src, fetchFn, imageConfig);
42
49
  }
43
50
  const policy = new CachePolicy(
44
51
  webToCachePolicyRequest(req),
@@ -38,7 +38,7 @@ async function loadLocalImage(src, url) {
38
38
  } else {
39
39
  const sourceUrl = new URL(src, url.origin);
40
40
  if (sourceUrl.origin !== url.origin) {
41
- returnValue = void 0;
41
+ return void 0;
42
42
  }
43
43
  return loadRemoteImage(sourceUrl);
44
44
  }
@@ -4,24 +4,7 @@ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
4
4
  import * as mime from "mrmime";
5
5
  import { getConfiguredImageService } from "../internal.js";
6
6
  import { etag } from "../utils/etag.js";
7
- async function loadRemoteImage(src, headers) {
8
- try {
9
- const res = await fetch(src, {
10
- // Forward all headers from the original request
11
- headers,
12
- redirect: "manual"
13
- });
14
- if (res.status >= 300 && res.status < 400) {
15
- return void 0;
16
- }
17
- if (!res.ok) {
18
- return void 0;
19
- }
20
- return await res.arrayBuffer();
21
- } catch {
22
- return void 0;
23
- }
24
- }
7
+ import { loadImage } from "./loadImage.js";
25
8
  const GET = async ({ request }) => {
26
9
  try {
27
10
  const imageService = await getConfiguredImageService();
@@ -42,7 +25,12 @@ const GET = async ({ request }) => {
42
25
  if (!isRemoteImage && sourceUrl.origin !== url.origin) {
43
26
  return new Response("Forbidden", { status: 403 });
44
27
  }
45
- inputBuffer = await loadRemoteImage(sourceUrl, isRemoteImage ? new Headers() : request.headers);
28
+ inputBuffer = await loadImage(
29
+ sourceUrl,
30
+ isRemoteImage ? new Headers() : request.headers,
31
+ imageConfig,
32
+ isRemoteImage
33
+ );
46
34
  if (!inputBuffer) {
47
35
  return new Response("Not Found", { status: 404 });
48
36
  }
@@ -62,7 +50,7 @@ const GET = async ({ request }) => {
62
50
  });
63
51
  } catch (err) {
64
52
  console.error("Could not process image request:", err);
65
- return new Response(`Server Error: ${err}`, { status: 500 });
53
+ return new Response("Internal Server Error", { status: 500 });
66
54
  }
67
55
  };
68
56
  export {
@@ -0,0 +1,11 @@
1
+ import type { RemoteImageConfig } from '../utils/redirectValidation.js';
2
+ /**
3
+ * Fetches an image by URL. Used by the generic image endpoint for both
4
+ * remote images and local images (self-fetched from the same origin).
5
+ *
6
+ * For remote images, the final URL (after any redirects) is validated
7
+ * against `imageConfig.domains` and `imageConfig.remotePatterns`.
8
+ * Local images skip this check — they are already guarded by the
9
+ * same-origin check in the caller.
10
+ */
11
+ export declare function loadImage(src: URL, headers: Headers, imageConfig: RemoteImageConfig, isRemote: boolean, fetchFn?: typeof globalThis.fetch): Promise<ArrayBuffer | undefined>;
@@ -0,0 +1,19 @@
1
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
+ import { fetchWithRedirects } from "../utils/redirectValidation.js";
3
+ async function loadImage(src, headers, imageConfig, isRemote, fetchFn) {
4
+ try {
5
+ const res = await fetchWithRedirects({ url: src, headers, imageConfig, fetchFn });
6
+ if (isRemote && !isRemoteAllowed(res.url, imageConfig)) {
7
+ return void 0;
8
+ }
9
+ if (!res.ok) {
10
+ return void 0;
11
+ }
12
+ return await res.arrayBuffer();
13
+ } catch {
14
+ return void 0;
15
+ }
16
+ }
17
+ export {
18
+ loadImage
19
+ };
@@ -5,10 +5,15 @@ import * as mime from "mrmime";
5
5
  import { getConfiguredImageService } from "../internal.js";
6
6
  import { etag } from "../utils/etag.js";
7
7
  import { inferSourceFormat } from "../utils/inferSourceFormat.js";
8
+ import { fetchWithRedirects } from "../utils/redirectValidation.js";
9
+ const isLocal = (url) => {
10
+ const hostname = new URL(url).hostname;
11
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
12
+ };
8
13
  async function loadRemoteImage(src) {
9
14
  try {
10
- const res = await fetch(src, { redirect: "manual" });
11
- if (res.status >= 300 && res.status < 400) {
15
+ const res = await fetchWithRedirects({ url: src, imageConfig });
16
+ if (!isRemoteAllowed(res.url, imageConfig) && !isLocal(res.url)) {
12
17
  return void 0;
13
18
  }
14
19
  if (!res.ok) {
@@ -7,11 +7,11 @@ export declare const StyleSchema: z.ZodEnum<{
7
7
  oblique: "oblique";
8
8
  }>;
9
9
  export declare const DisplaySchema: z.ZodEnum<{
10
- optional: "optional";
11
10
  auto: "auto";
11
+ optional: "optional";
12
+ fallback: "fallback";
12
13
  block: "block";
13
14
  swap: "swap";
14
- fallback: "fallback";
15
15
  }>;
16
16
  export declare const FontProviderSchema: z.ZodCustom<FontProvider<never>, FontProvider<never>>;
17
17
  export declare const FontFamilySchema: z.ZodObject<{
@@ -45,11 +45,11 @@ export declare const FontFamilySchema: z.ZodObject<{
45
45
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
46
  optimizedFallbacks: z.ZodOptional<z.ZodBoolean>;
47
47
  display: z.ZodOptional<z.ZodEnum<{
48
- optional: "optional";
49
48
  auto: "auto";
49
+ optional: "optional";
50
+ fallback: "fallback";
50
51
  block: "block";
51
52
  swap: "swap";
52
- fallback: "fallback";
53
53
  }>>;
54
54
  stretch: z.ZodOptional<z.ZodString>;
55
55
  featureSettings: z.ZodOptional<z.ZodString>;
@@ -1,4 +1,17 @@
1
1
  import { isGenericFontFamily, unifontFontFaceDataToProperties } from "../utils.js";
2
+ function deriveFallbackVariant(data) {
3
+ const weight = data.weight;
4
+ if (typeof weight === "number" && weight >= 700) {
5
+ return "bold";
6
+ }
7
+ if (typeof weight === "string") {
8
+ if (weight === "bold") return "bold";
9
+ if (weight.includes(" ")) return "normal";
10
+ const n = Number.parseInt(weight, 10);
11
+ if (!Number.isNaN(n) && n >= 700) return "bold";
12
+ }
13
+ return "normal";
14
+ }
2
15
  async function optimizeFallbacks({
3
16
  family,
4
17
  fallbacks: _fallbacks,
@@ -14,28 +27,40 @@ async function optimizeFallbacks({
14
27
  if (!isGenericFontFamily(lastFallback)) {
15
28
  return null;
16
29
  }
17
- const localFonts = systemFallbacksProvider.getLocalFonts(lastFallback);
18
- if (!localFonts || localFonts.length === 0) {
30
+ const collectedWithLocalFonts = collectedFonts.map((collected) => ({
31
+ collected,
32
+ localFonts: systemFallbacksProvider.getLocalFonts(lastFallback, deriveFallbackVariant(collected.data)) ?? []
33
+ }));
34
+ const uniqueLocalFonts = [];
35
+ for (const { localFonts } of collectedWithLocalFonts) {
36
+ for (const font of localFonts) {
37
+ if (!uniqueLocalFonts.includes(font)) {
38
+ uniqueLocalFonts.push(font);
39
+ }
40
+ }
41
+ }
42
+ if (uniqueLocalFonts.length === 0) {
19
43
  return null;
20
44
  }
21
- if (localFonts.includes(family.name)) {
45
+ if (uniqueLocalFonts.includes(family.name)) {
22
46
  return null;
23
47
  }
24
- const localFontsMappings = localFonts.map((font) => ({
25
- font,
48
+ const nameForFont = (font) => (
26
49
  // We mustn't wrap in quote because that's handled by the CSS renderer
27
- name: `${family.uniqueName} fallback: ${font}`
28
- }));
29
- fallbacks = [...localFontsMappings.map((m) => m.name), ...fallbacks];
50
+ `${family.uniqueName} fallback: ${font}`
51
+ );
52
+ fallbacks = [...uniqueLocalFonts.map(nameForFont), ...fallbacks];
30
53
  let css = "";
31
- for (const { font, name } of localFontsMappings) {
32
- for (const collected of collectedFonts) {
54
+ for (const { collected, localFonts } of collectedWithLocalFonts) {
55
+ const properties = unifontFontFaceDataToProperties(collected.data);
56
+ const metrics = await fontMetricsResolver.getMetrics(family.name, collected);
57
+ for (const font of localFonts) {
33
58
  css += fontMetricsResolver.generateFontFace({
34
- metrics: await fontMetricsResolver.getMetrics(family.name, collected),
59
+ metrics,
35
60
  fallbackMetrics: systemFallbacksProvider.getMetricsForLocalFont(font),
36
61
  font,
37
- name,
38
- properties: unifontFontFaceDataToProperties(collected.data)
62
+ name: nameForFont(font),
63
+ properties
39
64
  });
40
65
  }
41
66
  }
@@ -1,6 +1,6 @@
1
1
  import type * as unifont from 'unifont';
2
2
  import type { CollectedFontForMetrics } from './core/optimize-fallbacks.js';
3
- import type { CssProperties, FontFaceMetrics, FontFileData, FontProvider, FontType, GenericFallbackName, ResolveFontOptions, Style } from './types.js';
3
+ import type { CssProperties, FallbackVariant, FontFaceMetrics, FontFileData, FontProvider, FontType, GenericFallbackName, ResolveFontOptions, Style } from './types.js';
4
4
  export interface Hasher {
5
5
  hashString: (input: string) => string;
6
6
  hashObject: (input: Record<string, any>) => string;
@@ -28,7 +28,7 @@ export interface FontMetricsResolver {
28
28
  }) => string;
29
29
  }
30
30
  export interface SystemFallbacksProvider {
31
- getLocalFonts: (fallback: GenericFallbackName) => Array<string> | null;
31
+ getLocalFonts: (fallback: GenericFallbackName, variant: FallbackVariant) => Array<string> | null;
32
32
  getMetricsForLocalFont: (family: string) => FontFaceMetrics;
33
33
  }
34
34
  export interface FontFetcher {
@@ -1,6 +1,6 @@
1
1
  import type { SystemFallbacksProvider } from '../definitions.js';
2
- import type { FontFaceMetrics, GenericFallbackName } from '../types.js';
2
+ import type { FallbackVariant, FontFaceMetrics, GenericFallbackName } from '../types.js';
3
3
  export declare class RealSystemFallbacksProvider implements SystemFallbacksProvider {
4
- getLocalFonts(fallback: GenericFallbackName): Array<string> | null;
4
+ getLocalFonts(fallback: GenericFallbackName, variant: FallbackVariant): Array<string> | null;
5
5
  getMetricsForLocalFont(family: string): FontFaceMetrics;
6
6
  }
@@ -6,6 +6,14 @@ const SYSTEM_METRICS = {
6
6
  unitsPerEm: 2048,
7
7
  xWidthAvg: 832
8
8
  },
9
+ "Times New Roman Bold": {
10
+ ascent: 1825,
11
+ descent: -443,
12
+ lineGap: 87,
13
+ unitsPerEm: 2048,
14
+ xWidthAvg: 886
15
+ },
16
+ // Times New Roman Italic almost has the same properties as Times New Roman, we don't include it
9
17
  Arial: {
10
18
  ascent: 1854,
11
19
  descent: -434,
@@ -13,6 +21,14 @@ const SYSTEM_METRICS = {
13
21
  unitsPerEm: 2048,
14
22
  xWidthAvg: 913
15
23
  },
24
+ "Arial Bold": {
25
+ ascent: 1854,
26
+ descent: -434,
27
+ lineGap: 67,
28
+ unitsPerEm: 2048,
29
+ xWidthAvg: 983
30
+ },
31
+ // Arial Italic has the same properties as Arial, we don't include it
16
32
  "Courier New": {
17
33
  ascent: 1705,
18
34
  descent: -615,
@@ -20,6 +36,8 @@ const SYSTEM_METRICS = {
20
36
  unitsPerEm: 2048,
21
37
  xWidthAvg: 1229
22
38
  },
39
+ // Courier New Bold has the same properties as Courier New, we don't include it
40
+ // Courier New Italic has the same properties as Courier New, we don't include it
23
41
  BlinkMacSystemFont: {
24
42
  ascent: 1980,
25
43
  descent: -432,
@@ -50,17 +68,36 @@ const SYSTEM_METRICS = {
50
68
  }
51
69
  };
52
70
  const DEFAULT_FALLBACKS = {
53
- serif: ["Times New Roman"],
54
- "sans-serif": ["Arial"],
55
- monospace: ["Courier New"],
56
- "system-ui": ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
57
- "ui-serif": ["Times New Roman"],
58
- "ui-sans-serif": ["Arial"],
59
- "ui-monospace": ["Courier New"]
71
+ serif: {
72
+ normal: ["Times New Roman"],
73
+ bold: ["Times New Roman Bold"]
74
+ },
75
+ "sans-serif": {
76
+ normal: ["Arial"],
77
+ bold: ["Arial Bold"]
78
+ },
79
+ monospace: { normal: ["Courier New"] },
80
+ "system-ui": {
81
+ normal: ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
82
+ bold: ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial Bold"]
83
+ },
84
+ "ui-serif": {
85
+ normal: ["Times New Roman"],
86
+ bold: ["Times New Roman Bold"]
87
+ },
88
+ "ui-sans-serif": {
89
+ normal: ["Arial"],
90
+ bold: ["Arial Bold"]
91
+ },
92
+ "ui-monospace": { normal: ["Courier New"] }
60
93
  };
61
94
  class RealSystemFallbacksProvider {
62
- getLocalFonts(fallback) {
63
- return DEFAULT_FALLBACKS[fallback] ?? null;
95
+ getLocalFonts(fallback, variant) {
96
+ const entry = DEFAULT_FALLBACKS[fallback];
97
+ if (!entry) {
98
+ return null;
99
+ }
100
+ return entry[variant] ?? entry.normal ?? null;
64
101
  }
65
102
  getMetricsForLocalFont(family) {
66
103
  return SYSTEM_METRICS[family];
@@ -191,6 +191,7 @@ export interface PreloadData {
191
191
  }
192
192
  export type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
193
193
  export type GenericFallbackName = (typeof GENERIC_FALLBACK_NAMES)[number];
194
+ export type FallbackVariant = 'normal' | 'bold';
194
195
  export type Defaults = Required<Pick<ResolvedFontFamily, 'weights' | 'styles' | 'subsets' | 'fallbacks' | 'optimizedFallbacks' | 'formats'>>;
195
196
  export interface FontFileData {
196
197
  id: string;
@@ -2,3 +2,4 @@ export { getConfiguredImageService, getImage, verifyOptions } from './internal.j
2
2
  export { baseService, isLocalService } from './services/service.js';
3
3
  export { hashTransform, propsToFilename } from './utils/hash.js';
4
4
  export type { LocalImageProps, RemoteImageProps } from './types.js';
5
+ export { fetchWithRedirects } from './utils/redirectValidation.js';
@@ -1,8 +1,10 @@
1
1
  import { getConfiguredImageService, getImage, verifyOptions } from "./internal.js";
2
2
  import { baseService, isLocalService } from "./services/service.js";
3
3
  import { hashTransform, propsToFilename } from "./utils/hash.js";
4
+ import { fetchWithRedirects } from "./utils/redirectValidation.js";
4
5
  export {
5
6
  baseService,
7
+ fetchWithRedirects,
6
8
  getConfiguredImageService,
7
9
  getImage,
8
10
  hashTransform,
@@ -13,6 +13,7 @@ import {
13
13
  isImageMetadata
14
14
  } from "./types.js";
15
15
  import { isESMImportedImage, isRemoteImage, resolveSrc } from "./utils/imageKind.js";
16
+ import { resolveDefaultOutputFormat } from "./utils/inferSourceFormat.js";
16
17
  import { inferRemoteSize } from "./utils/remoteProbe.js";
17
18
  import { createPlaceholderURL, stringifyPlaceholderURL } from "./utils/url.js";
18
19
  import { verifyOptions } from "./services/service.js";
@@ -73,6 +74,9 @@ async function getImage(options, imageConfig) {
73
74
  const result = await getRemoteSize(resolvedOptions.src);
74
75
  resolvedOptions.width ??= result.width;
75
76
  resolvedOptions.height ??= result.height;
77
+ if (result.format) {
78
+ resolvedOptions.format ??= resolveDefaultOutputFormat(result.format);
79
+ }
76
80
  originalWidth = result.width;
77
81
  originalHeight = result.height;
78
82
  }
@@ -122,11 +126,15 @@ async function getImage(options, imageConfig) {
122
126
  if (resolvedOptions.fit && cssFitValues.includes(resolvedOptions.fit)) {
123
127
  resolvedOptions["data-astro-image-fit"] = resolvedOptions.fit;
124
128
  }
125
- if (resolvedOptions.position) {
126
- resolvedOptions["data-astro-image-pos"] = resolvedOptions.position.replace(/\s+/g, "-");
127
- }
129
+ const currentPosition = resolvedOptions.position || "center";
130
+ resolvedOptions["data-astro-image-pos"] = currentPosition.replace(/\s+/g, "-");
128
131
  }
129
132
  const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
133
+ validatedOptions.format ??= await peekRemoteFormatForStaticEmit(
134
+ validatedOptions,
135
+ imageConfig,
136
+ service
137
+ );
130
138
  const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
131
139
  const lazyImageURLFactory = (getValue) => {
132
140
  let cached = null;
@@ -188,6 +196,17 @@ async function getImage(options, imageConfig) {
188
196
  attributes: service.getHTMLAttributes !== void 0 ? await service.getHTMLAttributes(validatedOptions, imageConfig) : {}
189
197
  };
190
198
  }
199
+ async function peekRemoteFormatForStaticEmit(options, imageConfig, service) {
200
+ if (!isRemoteImage(options.src) || !isRemoteAllowed(options.src, imageConfig) || !globalThis.astroAsset?.addStaticImage || !isLocalService(service) || !service.getRemoteSize) {
201
+ return void 0;
202
+ }
203
+ try {
204
+ const probed = await service.getRemoteSize(options.src, imageConfig);
205
+ return resolveDefaultOutputFormat(probed.format);
206
+ } catch {
207
+ return void 0;
208
+ }
209
+ }
191
210
  export {
192
211
  cssFitValues,
193
212
  getConfiguredImageService,
@@ -82,7 +82,7 @@ export type BaseServiceTransform = {
82
82
  src: string;
83
83
  width?: number;
84
84
  height?: number;
85
- format: string;
85
+ format?: string;
86
86
  quality?: string | null;
87
87
  fit?: ImageFit;
88
88
  position?: string;
@@ -1,8 +1,9 @@
1
1
  import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
2
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
3
3
  import { isRemotePath, joinPaths } from "../../core/path.js";
4
- import { DEFAULT_HASH_PROPS, DEFAULT_OUTPUT_FORMAT, VALID_SUPPORTED_FORMATS } from "../consts.js";
4
+ import { DEFAULT_HASH_PROPS, VALID_SUPPORTED_FORMATS } from "../consts.js";
5
5
  import { isESMImportedImage, isRemoteImage } from "../utils/imageKind.js";
6
+ import { inferSourceFormat, resolveDefaultOutputFormat } from "../utils/inferSourceFormat.js";
6
7
  import { inferRemoteSize } from "../utils/remoteProbe.js";
7
8
  function isLocalService(service) {
8
9
  if (!service) {
@@ -74,10 +75,11 @@ const baseService = {
74
75
  validateOptions(options) {
75
76
  verifyOptions(options);
76
77
  if (!options.format) {
77
- if (isESMImportedImage(options.src) && options.src.format === "svg") {
78
- options.format = "svg";
78
+ if (isESMImportedImage(options.src)) {
79
+ options.format = resolveDefaultOutputFormat(options.src.format);
79
80
  } else {
80
- options.format = DEFAULT_OUTPUT_FORMAT;
81
+ const inferred = inferSourceFormat(options.src);
82
+ if (inferred) options.format = resolveDefaultOutputFormat(inferred);
81
83
  }
82
84
  }
83
85
  if (options.width) options.width = Math.round(options.width);
@@ -120,7 +122,7 @@ const baseService = {
120
122
  const { targetWidth, targetHeight } = getTargetDimensions(options);
121
123
  const aspectRatio = targetWidth / targetHeight;
122
124
  const { widths, densities } = options;
123
- const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
125
+ const targetFormat = options.format;
124
126
  let transformedWidths = (widths ?? []).sort(sortNumeric);
125
127
  let imageWidth = options.width;
126
128
  let maxWidth = Number.POSITIVE_INFINITY;
@@ -164,9 +166,7 @@ const baseService = {
164
166
  return {
165
167
  transform,
166
168
  descriptor,
167
- attributes: {
168
- type: `image/${targetFormat}`
169
- }
169
+ attributes: targetFormat ? { type: `image/${targetFormat}` } : {}
170
170
  };
171
171
  });
172
172
  },
@@ -210,7 +210,7 @@ const baseService = {
210
210
  src: params.get("href"),
211
211
  width: params.has("w") ? Number.parseInt(params.get("w")) : void 0,
212
212
  height: params.has("h") ? Number.parseInt(params.get("h")) : void 0,
213
- format: params.get("f"),
213
+ format: params.has("f") ? params.get("f") : void 0,
214
214
  quality: params.get("q"),
215
215
  fit: params.get("fit"),
216
216
  position: params.get("position") ?? void 0,