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,4 +1,6 @@
1
1
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
2
+ import { resolveDefaultOutputFormat } from "../utils/inferSourceFormat.js";
3
+ import { detector } from "../utils/vendor/image-size/detector.js";
2
4
  import {
3
5
  baseService,
4
6
  parseQuality
@@ -20,6 +22,9 @@ function resolveSharpQuality(quality) {
20
22
  }
21
23
  function resolveSharpEncoderOptions(transform, inputFormat, serviceConfig = {}) {
22
24
  const quality = resolveSharpQuality(transform.quality);
25
+ if (transform.format === void 0) {
26
+ return quality === void 0 ? void 0 : { quality };
27
+ }
23
28
  switch (transform.format) {
24
29
  case "jpg":
25
30
  case "jpeg":
@@ -81,14 +86,35 @@ const sharpService = {
81
86
  if (!sharp) sharp = await loadSharp();
82
87
  const transform = transformOptions;
83
88
  const kernel = config.service.config.kernel;
84
- if (transform.format === "svg") return { data: inputBuffer, format: "svg" };
89
+ const bufferFormat = detector(inputBuffer);
90
+ const outputFormat = transform.format ?? resolveDefaultOutputFormat(bufferFormat);
91
+ if (outputFormat === "svg") {
92
+ if (bufferFormat && bufferFormat !== "svg") {
93
+ console.warn(
94
+ `\u26A0\uFE0F Astro expected an SVG for "${transform.src}" but the source is ${bufferFormat}. Passing it through as ${bufferFormat} instead.`
95
+ );
96
+ return { data: inputBuffer, format: bufferFormat };
97
+ }
98
+ return { data: inputBuffer, format: "svg" };
99
+ }
100
+ if (!bufferFormat) {
101
+ throw new AstroError({
102
+ ...AstroErrorData.NoImageMetadata,
103
+ message: AstroErrorData.NoImageMetadata.message(transform.src)
104
+ });
105
+ }
106
+ if (bufferFormat === "svg" && !config.dangerouslyProcessSVG) {
107
+ throw new AstroError({
108
+ ...AstroErrorData.UnsupportedImageFormat,
109
+ message: `SVG image processing is disabled, but the source for "${transform.src}" is an SVG. Pass it through unchanged by setting \`format="svg"\` on the component, or set \`image.dangerouslyProcessSVG: true\` to rasterize SVG sources.`
110
+ });
111
+ }
85
112
  const result = sharp(inputBuffer, {
86
113
  failOnError: false,
87
114
  pages: -1,
88
115
  limitInputPixels: config.service.config.limitInputPixels
89
116
  });
90
117
  result.rotate();
91
- const { format } = await result.metadata();
92
118
  if (transform.width && transform.height) {
93
119
  const fit = transform.fit ? fitMap[transform.fit] ?? "inside" : void 0;
94
120
  result.resize({
@@ -115,23 +141,32 @@ const sharpService = {
115
141
  if (transform.background) {
116
142
  result.flatten({ background: transform.background });
117
143
  }
118
- if (transform.format) {
119
- const encoderOptions = resolveSharpEncoderOptions(transform, format, config.service.config);
120
- if (transform.format === "webp" && format === "gif") {
121
- result.webp(encoderOptions);
122
- } else if (transform.format === "webp") {
123
- result.webp(encoderOptions);
124
- } else if (transform.format === "png") {
125
- result.png(encoderOptions);
126
- } else if (transform.format === "avif") {
127
- result.avif(encoderOptions);
128
- } else if (transform.format === "jpeg" || transform.format === "jpg") {
129
- result.jpeg(encoderOptions);
130
- } else {
131
- result.toFormat(transform.format, encoderOptions);
132
- }
144
+ const encoderOptions = resolveSharpEncoderOptions(
145
+ { format: outputFormat, quality: transform.quality },
146
+ bufferFormat,
147
+ config.service.config
148
+ );
149
+ if (outputFormat === "webp") {
150
+ result.webp(encoderOptions);
151
+ } else if (outputFormat === "png") {
152
+ result.png(encoderOptions);
153
+ } else if (outputFormat === "avif") {
154
+ result.avif(encoderOptions);
155
+ } else if (outputFormat === "jpeg" || outputFormat === "jpg") {
156
+ result.jpeg(encoderOptions);
157
+ } else {
158
+ result.toFormat(outputFormat, encoderOptions);
159
+ }
160
+ let data;
161
+ let info;
162
+ try {
163
+ ({ data, info } = await result.toBuffer({ resolveWithObject: true }));
164
+ } catch {
165
+ console.warn(
166
+ `\u26A0\uFE0F Astro could not optimize image "${transform.src}". Sharp doesn't support this format. The image will be used unoptimized. Consider converting to WebP or placing in the public/ folder.`
167
+ );
168
+ return { data: inputBuffer, format: bufferFormat };
133
169
  }
134
- const { data, info } = await result.toBuffer({ resolveWithObject: true });
135
170
  const needsCopy = "buffer" in data && data.buffer instanceof SharedArrayBuffer;
136
171
  return {
137
172
  data: needsCopy ? new Uint8Array(data) : data,
@@ -1,4 +1,20 @@
1
1
  import { cssFitValues } from "../internal.js";
2
+ const POSITION_KEYWORDS = ["top", "bottom", "left", "right", "center"];
3
+ function getPositionEntries() {
4
+ const entries = [];
5
+ for (const kw of POSITION_KEYWORDS) {
6
+ entries.push([kw, kw]);
7
+ }
8
+ for (const a of POSITION_KEYWORDS) {
9
+ for (const b of POSITION_KEYWORDS) {
10
+ if (a === b) continue;
11
+ const cssValue = `${a} ${b}`;
12
+ const dataAttr = `${a}-${b}`;
13
+ entries.push([dataAttr, cssValue]);
14
+ }
15
+ }
16
+ return entries;
17
+ }
2
18
  function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
3
19
  const fitStyles = cssFitValues.map(
4
20
  (fit) => `
@@ -10,11 +26,14 @@ function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
10
26
  :where([data-astro-image]:not([data-astro-image-fit])) {
11
27
  object-fit: ${defaultObjectFit};
12
28
  }` : "";
13
- const positionStyle = defaultObjectPosition ? `
14
- [data-astro-image-pos="${defaultObjectPosition.replace(/\s+/g, "-")}"] {
15
- object-position: ${defaultObjectPosition};
16
- }
17
-
29
+ const positionEntries = getPositionEntries();
30
+ const positionStyles = positionEntries.map(
31
+ ([dataAttr, cssValue]) => `
32
+ [data-astro-image-pos="${dataAttr}"] {
33
+ object-position: ${cssValue};
34
+ }`
35
+ ).join("\n");
36
+ const defaultPositionStyle = defaultObjectPosition ? `
18
37
  :where([data-astro-image]:not([data-astro-image-pos])) {
19
38
  object-position: ${defaultObjectPosition};
20
39
  }` : "";
@@ -30,7 +49,8 @@ function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
30
49
  }
31
50
  ${fitStyles}
32
51
  ${defaultFitStyle}
33
- ${positionStyle}
52
+ ${positionStyles}
53
+ ${defaultPositionStyle}
34
54
  `.trim();
35
55
  }
36
56
  export {
@@ -10,3 +10,4 @@ export { isESMImportedImage, isRemoteImage, resolveSrc } from './imageKind.js';
10
10
  export { imageMetadata } from './metadata.js';
11
11
  export { getOrigQueryParams } from './queryParams.js';
12
12
  export { inferRemoteSize } from './remoteProbe.js';
13
+ export { fetchWithRedirects } from './redirectValidation.js';
@@ -7,8 +7,10 @@ import { isESMImportedImage, isRemoteImage, resolveSrc } from "./imageKind.js";
7
7
  import { imageMetadata } from "./metadata.js";
8
8
  import { getOrigQueryParams } from "./queryParams.js";
9
9
  import { inferRemoteSize } from "./remoteProbe.js";
10
+ import { fetchWithRedirects } from "./redirectValidation.js";
10
11
  export {
11
12
  emitClientAsset,
13
+ fetchWithRedirects,
12
14
  getOrigQueryParams,
13
15
  imageMetadata,
14
16
  inferRemoteSize,
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Infer the image format from a source path or URL by examining
3
- * the file extension. For data: URIs, the MIME type is extracted.
4
- * Returns undefined if the format cannot be determined.
2
+ * Infer the image format from a source path or URL.
3
+ *
4
+ * For `data:` URIs the MIME is read up to the first `;` or `,` (whichever comes first),
5
+ * so both `data:image/svg+xml;base64,...` and `data:image/svg+xml,<svg>...` work.
6
+ * `image/svg+xml` normalizes to `svg`; otherwise the subtype after the slash is returned.
7
+ *
8
+ * Returns undefined when the format cannot be determined.
5
9
  */
6
10
  export declare function inferSourceFormat(src: string): string | undefined;
11
+ export declare function resolveDefaultOutputFormat(sourceFormat: string | undefined): string;
@@ -1,21 +1,32 @@
1
1
  import { removeQueryString } from "@astrojs/internal-helpers/path";
2
+ import { DEFAULT_OUTPUT_FORMAT } from "../consts.js";
2
3
  const DATA_PREFIX = "data:";
3
4
  function inferSourceFormat(src) {
4
5
  if (src.startsWith(DATA_PREFIX)) {
5
- const mime = src.slice(DATA_PREFIX.length, src.indexOf(";"));
6
+ const sepIndex = src.indexOf(";");
7
+ const commaIndex = src.indexOf(",");
8
+ const mimeEnd = sepIndex === -1 ? commaIndex : commaIndex === -1 ? sepIndex : Math.min(sepIndex, commaIndex);
9
+ if (mimeEnd === -1) return void 0;
10
+ const mime = src.slice(DATA_PREFIX.length, mimeEnd);
6
11
  if (mime === "image/svg+xml") return "svg";
7
12
  const sub = mime.split("/")[1];
8
13
  return sub || void 0;
9
14
  }
10
15
  try {
11
16
  const cleanSrc = removeQueryString(src).split("#")[0];
12
- const lastDot = cleanSrc.lastIndexOf(".");
17
+ const lastSlash = cleanSrc.lastIndexOf("/");
18
+ const basename = lastSlash === -1 ? cleanSrc : cleanSrc.slice(lastSlash + 1);
19
+ const lastDot = basename.lastIndexOf(".");
13
20
  if (lastDot === -1) return void 0;
14
- return cleanSrc.slice(lastDot + 1).toLowerCase();
21
+ return basename.slice(lastDot + 1).toLowerCase();
15
22
  } catch {
16
23
  return void 0;
17
24
  }
18
25
  }
26
+ function resolveDefaultOutputFormat(sourceFormat) {
27
+ return sourceFormat === "svg" ? "svg" : DEFAULT_OUTPUT_FORMAT;
28
+ }
19
29
  export {
20
- inferSourceFormat
30
+ inferSourceFormat,
31
+ resolveDefaultOutputFormat
21
32
  };
@@ -10,7 +10,7 @@ async function imageMetadata(data, src) {
10
10
  message: AstroErrorData.NoImageMetadata.message(src)
11
11
  });
12
12
  }
13
- if (!result.height || !result.width || !result.type) {
13
+ if (result.height == null || result.width == null || !result.type) {
14
14
  throw new AstroError({
15
15
  ...AstroErrorData.NoImageMetadata,
16
16
  message: AstroErrorData.NoImageMetadata.message(src)
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Utilities for handling HTTP redirects with validation
3
+ */
4
+ import type { AstroConfig } from '../../types/public/config.js';
5
+ export type RemoteImageConfig = Pick<AstroConfig['image'], 'remotePatterns' | 'domains'>;
6
+ export type FetchRedirectOptions = {
7
+ /**
8
+ * URL to fetch (either string or URL object)
9
+ */
10
+ url: string | URL;
11
+ /**
12
+ * Headers to include in the request (optional)
13
+ */
14
+ headers?: Headers;
15
+ /**
16
+ * Image config for validating redirect destinations (optional)
17
+ */
18
+ imageConfig: RemoteImageConfig;
19
+ /**
20
+ * Fetch function to use (default: globalThis.fetch)
21
+ */
22
+ fetchFn?: typeof fetch;
23
+ /**
24
+ * Maximum number of redirects to follow (default: 10)
25
+ */
26
+ redirectLimit?: number;
27
+ /**
28
+ * Error handler for redirect depth exceeded (default: generic Error)
29
+ */
30
+ onMaxRedirectsExceeded?: (url: string) => Error;
31
+ /**
32
+ * Error handler for missing Location header (default: generic Error)
33
+ */
34
+ onMissingLocationHeader?: (status: number, url: string) => Error;
35
+ /**
36
+ * Error handler for disallowed redirect (default: generic Error)
37
+ */
38
+ onDisallowedRedirect?: (currentUrl: string, targetUrl: string) => Error;
39
+ };
40
+ /**
41
+ * Recursively follows HTTP redirects with validation according to the image configuration.
42
+ *
43
+ * If any of the domains in the redirect chain are not allowed by either `image.remotePatterns`
44
+ * or `image.domains`, this function will throw an error for a disallowed redirect.
45
+ *
46
+ * @param options The options for this fetch call.
47
+ */
48
+ export declare function fetchWithRedirects(options: FetchRedirectOptions): Promise<Response>;
@@ -0,0 +1,48 @@
1
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
+ async function fetchWithRedirects(options) {
3
+ const {
4
+ url,
5
+ headers,
6
+ imageConfig,
7
+ fetchFn = globalThis.fetch,
8
+ redirectLimit = 10,
9
+ onMaxRedirectsExceeded = (_u) => new Error("Maximum redirect depth exceeded"),
10
+ onMissingLocationHeader = (_s, _u) => new Error(`Redirect response ${_s} missing Location header`),
11
+ onDisallowedRedirect = (_current, _target) => new Error(
12
+ `The image at ${_current} redirected to ${_target}, which is not an allowed remote location.`
13
+ )
14
+ } = options;
15
+ if (redirectLimit <= 0) {
16
+ throw onMaxRedirectsExceeded(typeof url === "string" ? url : url.toString());
17
+ }
18
+ const urlString = typeof url === "string" ? url : url.toString();
19
+ const req = new Request(url, { headers });
20
+ const res = await fetchFn(req, { redirect: "manual" });
21
+ if ([301, 302, 303, 307, 308].includes(res.status)) {
22
+ const location = res.headers.get("Location");
23
+ if (!location) {
24
+ throw onMissingLocationHeader(res.status, urlString);
25
+ }
26
+ const redirectUrl = new URL(location, urlString).toString();
27
+ if (!isRemoteAllowed(redirectUrl, {
28
+ domains: imageConfig.domains ?? [],
29
+ remotePatterns: imageConfig.remotePatterns ?? []
30
+ })) {
31
+ throw onDisallowedRedirect(urlString, redirectUrl);
32
+ }
33
+ return fetchWithRedirects({
34
+ url: redirectUrl,
35
+ headers,
36
+ imageConfig,
37
+ fetchFn,
38
+ redirectLimit: redirectLimit - 1,
39
+ onMaxRedirectsExceeded,
40
+ onMissingLocationHeader,
41
+ onDisallowedRedirect
42
+ });
43
+ }
44
+ return res;
45
+ }
46
+ export {
47
+ fetchWithRedirects
48
+ };
@@ -1,6 +1,7 @@
1
1
  import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
2
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
3
3
  import { imageMetadata } from "./metadata.js";
4
+ import { fetchWithRedirects } from "./redirectValidation.js";
4
5
  async function inferRemoteSize(url, imageConfig) {
5
6
  if (!URL.canParse(url)) {
6
7
  throw new AstroError({
@@ -27,13 +28,35 @@ async function inferRemoteSize(url, imageConfig) {
27
28
  message: AstroErrorData.RemoteImageNotAllowed.message(url)
28
29
  });
29
30
  }
30
- const response = await fetch(url, { redirect: "manual" });
31
- if (response.status >= 300 && response.status < 400) {
31
+ let response;
32
+ try {
33
+ response = await fetchWithRedirects({
34
+ url,
35
+ onMaxRedirectsExceeded: (u) => new AstroError({
36
+ ...AstroErrorData.FailedToFetchRemoteImageDimensions,
37
+ message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(u)
38
+ }),
39
+ onMissingLocationHeader: (_status, u) => new AstroError({
40
+ ...AstroErrorData.FailedToFetchRemoteImageDimensions,
41
+ message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(u)
42
+ }),
43
+ imageConfig: imageConfig ?? {
44
+ remotePatterns: [],
45
+ domains: []
46
+ }
47
+ });
48
+ } catch (_err) {
32
49
  throw new AstroError({
33
50
  ...AstroErrorData.FailedToFetchRemoteImageDimensions,
34
51
  message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
35
52
  });
36
53
  }
54
+ if (allowlistConfig && !isRemoteAllowed(response.url, allowlistConfig)) {
55
+ throw new AstroError({
56
+ ...AstroErrorData.RemoteImageNotAllowed,
57
+ message: AstroErrorData.RemoteImageNotAllowed.message(url)
58
+ });
59
+ }
37
60
  if (!response.body || !response.ok) {
38
61
  throw new AstroError({
39
62
  ...AstroErrorData.FailedToFetchRemoteImageDimensions,
@@ -77,7 +77,7 @@ const SVG = {
77
77
  const root = extractorRegExps.root.exec(toUTF8String(input));
78
78
  if (root) {
79
79
  const attrs = parseAttributes(root[0]);
80
- if (attrs.width && attrs.height) {
80
+ if (attrs.width != null && attrs.height != null) {
81
81
  return calculateByDimensions(attrs);
82
82
  }
83
83
  if (attrs.viewbox) {
@@ -197,12 +197,6 @@ async function add(names, { flags }) {
197
197
  logger,
198
198
  scripts: { "generate-types": "wrangler types" }
199
199
  });
200
- await updatePackageJsonOverrides({
201
- configURL,
202
- flags,
203
- logger,
204
- overrides: { vite: "^7" }
205
- });
206
200
  }
207
201
  if (integrations.find((integration) => integration.id === "tailwind")) {
208
202
  const dir = new URL("./styles/", new URL(userConfig.srcDir ?? "./src/", root));
@@ -590,54 +584,6 @@ async function updateAstroConfig({
590
584
  return "cancelled";
591
585
  }
592
586
  }
593
- async function updatePackageJsonOverrides({
594
- configURL,
595
- flags,
596
- logger,
597
- overrides
598
- }) {
599
- const pkgURL = new URL("./package.json", configURL);
600
- if (!existsSync(pkgURL)) {
601
- logger.debug("add", "No package.json found, skipping overrides update");
602
- return "none";
603
- }
604
- const pkgPath = fileURLToPath(pkgURL);
605
- const input = await fs.readFile(pkgPath, { encoding: "utf-8" });
606
- const pkgJson = JSON.parse(input);
607
- pkgJson.overrides ??= {};
608
- let hasChanges = false;
609
- for (const [name, range] of Object.entries(overrides)) {
610
- if (!(name in pkgJson.overrides)) {
611
- pkgJson.overrides[name] = range;
612
- hasChanges = true;
613
- }
614
- }
615
- if (!hasChanges) {
616
- return "none";
617
- }
618
- const output = JSON.stringify(pkgJson, null, 2);
619
- const diff = getDiffContent(input, output);
620
- if (!diff) {
621
- return "none";
622
- }
623
- logger.info(
624
- "SKIP_FORMAT",
625
- `
626
- ${magenta("Astro will add the following overrides to your package.json:")}`
627
- );
628
- clack.box(diff, "package.json", {
629
- rounded: true,
630
- withGuide: false,
631
- width: "auto"
632
- });
633
- if (await askToContinue({ flags, logger })) {
634
- await fs.writeFile(pkgPath, output, { encoding: "utf-8" });
635
- logger.debug("add", "Updated package.json overrides");
636
- return "updated";
637
- } else {
638
- return "cancelled";
639
- }
640
- }
641
587
  async function updatePackageJsonScripts({
642
588
  configURL,
643
589
  flags,
@@ -898,14 +844,17 @@ async function updateTSConfig(cwd = process.cwd(), logger, integrationsInfo, fla
898
844
  }
899
845
  let inputConfig = await loadTSConfig(cwd);
900
846
  let inputConfigText = "";
901
- if (inputConfig === "invalid-config" || inputConfig === "unknown-error") {
847
+ if (inputConfig.error === "invalid-config") {
848
+ logger.warn(`add`, `Couldn't parse tsconfig.json or jsconfig.json: ${inputConfig.message}`);
902
849
  return "failure";
903
- } else if (inputConfig === "missing-config") {
850
+ } else if (inputConfig.error === "missing-config") {
904
851
  logger.debug("add", "Couldn't find tsconfig.json or jsconfig.json, generating one");
852
+ const tsconfigFile = path.join(cwd, "tsconfig.json");
905
853
  inputConfig = {
906
854
  tsconfig: defaultTSConfig,
907
- tsconfigFile: path.join(cwd, "tsconfig.json"),
908
- rawConfig: defaultTSConfig
855
+ tsconfigFile,
856
+ rawConfig: defaultTSConfig,
857
+ sources: [tsconfigFile]
909
858
  };
910
859
  } else {
911
860
  inputConfigText = JSON.stringify(inputConfig.rawConfig, null, 2);
@@ -0,0 +1,16 @@
1
+ import type { AstroLogger } from '../../core/logger/core.js';
2
+ import type { Flags } from '../flags.js';
3
+ export interface BackgroundResult {
4
+ pid: number;
5
+ url: string;
6
+ existing?: boolean;
7
+ }
8
+ export interface BackgroundErrorResult {
9
+ error: string;
10
+ message: string;
11
+ }
12
+ export declare function formatBackgroundOutput(result: BackgroundResult | BackgroundErrorResult): string;
13
+ export declare function background({ flags, logger, }: {
14
+ flags: Flags;
15
+ logger: AstroLogger;
16
+ }): Promise<void>;
@@ -0,0 +1,116 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, openSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import {
6
+ checkExistingServer,
7
+ getLogFileURL,
8
+ readLockFile,
9
+ removeLockFile,
10
+ isProcessAlive,
11
+ GRACEFUL_SHUTDOWN_TIMEOUT
12
+ } from "../../core/dev/lockfile.js";
13
+ import { resolveRoot } from "../../core/config/config.js";
14
+ function formatBackgroundOutput(result) {
15
+ return JSON.stringify(result);
16
+ }
17
+ async function background({
18
+ flags,
19
+ logger
20
+ }) {
21
+ const root = pathToFileURL(resolveRoot(flags.root) + "/");
22
+ const existing = checkExistingServer(root);
23
+ if (existing && !flags.force) {
24
+ logger.info(
25
+ "SKIP_FORMAT",
26
+ `Dev server already running at ${existing.url} (pid ${existing.pid})
27
+ Stop: astro dev stop
28
+ Status: astro dev status
29
+ Logs: astro dev logs`
30
+ );
31
+ return;
32
+ }
33
+ if (existing && flags.force) {
34
+ try {
35
+ process.kill(existing.pid, "SIGTERM");
36
+ } catch {
37
+ }
38
+ const deadline2 = Date.now() + GRACEFUL_SHUTDOWN_TIMEOUT;
39
+ while (Date.now() < deadline2) {
40
+ if (!isProcessAlive(existing.pid)) break;
41
+ await new Promise((r) => setTimeout(r, 100));
42
+ }
43
+ if (isProcessAlive(existing.pid)) {
44
+ try {
45
+ process.kill(existing.pid, "SIGKILL");
46
+ } catch {
47
+ }
48
+ }
49
+ removeLockFile(root);
50
+ }
51
+ const args = ["dev"];
52
+ if (flags.port) args.push("--port", String(flags.port));
53
+ if (flags.host != null) {
54
+ if (typeof flags.host === "string") {
55
+ args.push("--host", flags.host);
56
+ } else {
57
+ args.push("--host");
58
+ }
59
+ }
60
+ if (flags.config) args.push("--config", String(flags.config));
61
+ if (flags.root) args.push("--root", String(flags.root));
62
+ if (flags.allowedHosts) args.push("--allowed-hosts", String(flags.allowedHosts));
63
+ if (flags.experimentalJson) args.push("--experimental-json");
64
+ const logFileURL = getLogFileURL(root);
65
+ const logFilePath = fileURLToPath(logFileURL);
66
+ const dotAstroDir = fileURLToPath(new URL(".astro/", root));
67
+ if (!existsSync(dotAstroDir)) {
68
+ mkdirSync(dotAstroDir, { recursive: true });
69
+ }
70
+ const logFd = openSync(logFilePath, "w");
71
+ const rootPath = fileURLToPath(root);
72
+ const astroBin = resolve(rootPath, "node_modules", ".bin", "astro");
73
+ const child = spawn(astroBin, args, {
74
+ detached: true,
75
+ stdio: ["ignore", logFd, logFd],
76
+ cwd: rootPath,
77
+ env: { ...process.env, ASTRO_DEV_BACKGROUND: "1" }
78
+ });
79
+ child.unref();
80
+ const childPid = child.pid;
81
+ if (!childPid) {
82
+ logger.error("SKIP_FORMAT", "Failed to spawn background dev server process.");
83
+ process.exit(1);
84
+ }
85
+ const timeout = 3e4;
86
+ const deadline = Date.now() + timeout;
87
+ while (Date.now() < deadline) {
88
+ if (!isProcessAlive(childPid)) {
89
+ logger.error("SKIP_FORMAT", "Dev server process exited before becoming ready.");
90
+ process.exit(1);
91
+ }
92
+ const lockData = readLockFile(root);
93
+ if (lockData && lockData.pid === childPid) {
94
+ logger.info(
95
+ "SKIP_FORMAT",
96
+ `Dev server running at ${lockData.url} (pid ${lockData.pid})
97
+ Stop: astro dev stop
98
+ Status: astro dev status
99
+ Logs: astro dev logs`
100
+ );
101
+ return;
102
+ }
103
+ await new Promise((r) => setTimeout(r, 200));
104
+ }
105
+ try {
106
+ process.kill(childPid, "SIGTERM");
107
+ } catch {
108
+ }
109
+ removeLockFile(root);
110
+ logger.error("SKIP_FORMAT", `Dev server failed to start within ${timeout / 1e3}s.`);
111
+ process.exit(1);
112
+ }
113
+ export {
114
+ background,
115
+ formatBackgroundOutput
116
+ };