astro 5.17.2 → 5.17.3
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.
- package/dist/actions/runtime/server.js +61 -2
- package/dist/assets/build/remote.js +8 -2
- package/dist/assets/endpoint/generic.js +5 -1
- package/dist/assets/endpoint/shared.js +4 -1
- package/dist/assets/internal.js +15 -6
- package/dist/assets/utils/remoteProbe.d.ts +5 -1
- package/dist/assets/utils/remoteProbe.js +34 -2
- package/dist/assets/vite-plugin-assets.js +8 -5
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/content/content-layer.js +3 -3
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/errors-data.d.ts +13 -0
- package/dist/core/errors/errors-data.js +7 -0
- package/dist/core/messages.js +2 -2
- package/package.json +4 -4
|
@@ -163,6 +163,9 @@ function getActionContext(context) {
|
|
|
163
163
|
try {
|
|
164
164
|
input = await parseRequestBody(context.request);
|
|
165
165
|
} catch (e) {
|
|
166
|
+
if (e instanceof ActionError) {
|
|
167
|
+
return { data: void 0, error: e };
|
|
168
|
+
}
|
|
166
169
|
if (e instanceof TypeError) {
|
|
167
170
|
return { data: void 0, error: new ActionError({ code: "UNSUPPORTED_MEDIA_TYPE" }) };
|
|
168
171
|
}
|
|
@@ -206,18 +209,74 @@ function getCallerInfo(ctx) {
|
|
|
206
209
|
}
|
|
207
210
|
return void 0;
|
|
208
211
|
}
|
|
212
|
+
const DEFAULT_ACTION_BODY_SIZE_LIMIT = 1024 * 1024;
|
|
209
213
|
async function parseRequestBody(request) {
|
|
210
214
|
const contentType = request.headers.get("content-type");
|
|
211
|
-
const
|
|
215
|
+
const contentLengthHeader = request.headers.get("content-length");
|
|
216
|
+
const contentLength = contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : void 0;
|
|
217
|
+
const hasContentLength = typeof contentLength === "number" && Number.isFinite(contentLength);
|
|
212
218
|
if (!contentType) return void 0;
|
|
219
|
+
if (hasContentLength && contentLength > DEFAULT_ACTION_BODY_SIZE_LIMIT) {
|
|
220
|
+
throw new ActionError({
|
|
221
|
+
code: "CONTENT_TOO_LARGE",
|
|
222
|
+
message: `Request body exceeds ${DEFAULT_ACTION_BODY_SIZE_LIMIT} bytes`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
213
225
|
if (hasContentType(contentType, formContentTypes)) {
|
|
226
|
+
if (!hasContentLength) {
|
|
227
|
+
const body = await readRequestBodyWithLimit(request.clone(), DEFAULT_ACTION_BODY_SIZE_LIMIT);
|
|
228
|
+
const formRequest = new Request(request.url, {
|
|
229
|
+
method: request.method,
|
|
230
|
+
headers: request.headers,
|
|
231
|
+
body: toArrayBuffer(body)
|
|
232
|
+
});
|
|
233
|
+
return await formRequest.formData();
|
|
234
|
+
}
|
|
214
235
|
return await request.clone().formData();
|
|
215
236
|
}
|
|
216
237
|
if (hasContentType(contentType, ["application/json"])) {
|
|
217
|
-
|
|
238
|
+
if (contentLength === 0) return void 0;
|
|
239
|
+
if (!hasContentLength) {
|
|
240
|
+
const body = await readRequestBodyWithLimit(request.clone(), DEFAULT_ACTION_BODY_SIZE_LIMIT);
|
|
241
|
+
if (body.byteLength === 0) return void 0;
|
|
242
|
+
return JSON.parse(new TextDecoder().decode(body));
|
|
243
|
+
}
|
|
244
|
+
return await request.clone().json();
|
|
218
245
|
}
|
|
219
246
|
throw new TypeError("Unsupported content type");
|
|
220
247
|
}
|
|
248
|
+
async function readRequestBodyWithLimit(request, limit) {
|
|
249
|
+
if (!request.body) return new Uint8Array();
|
|
250
|
+
const reader = request.body.getReader();
|
|
251
|
+
const chunks = [];
|
|
252
|
+
let received = 0;
|
|
253
|
+
while (true) {
|
|
254
|
+
const { done, value } = await reader.read();
|
|
255
|
+
if (done) break;
|
|
256
|
+
if (value) {
|
|
257
|
+
received += value.byteLength;
|
|
258
|
+
if (received > limit) {
|
|
259
|
+
throw new ActionError({
|
|
260
|
+
code: "CONTENT_TOO_LARGE",
|
|
261
|
+
message: `Request body exceeds ${limit} bytes`
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
chunks.push(value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const buffer = new Uint8Array(received);
|
|
268
|
+
let offset = 0;
|
|
269
|
+
for (const chunk of chunks) {
|
|
270
|
+
buffer.set(chunk, offset);
|
|
271
|
+
offset += chunk.byteLength;
|
|
272
|
+
}
|
|
273
|
+
return buffer;
|
|
274
|
+
}
|
|
275
|
+
function toArrayBuffer(buffer) {
|
|
276
|
+
const copy = new Uint8Array(buffer.byteLength);
|
|
277
|
+
copy.set(buffer);
|
|
278
|
+
return copy.buffer;
|
|
279
|
+
}
|
|
221
280
|
export {
|
|
222
281
|
defineAction,
|
|
223
282
|
formDataToObject,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import CachePolicy from "http-cache-semantics";
|
|
2
2
|
async function loadRemoteImage(src) {
|
|
3
3
|
const req = new Request(src);
|
|
4
|
-
const res = await fetch(req);
|
|
4
|
+
const res = await fetch(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
|
+
}
|
|
5
8
|
if (!res.ok) {
|
|
6
9
|
throw new Error(
|
|
7
10
|
`Failed to load remote image ${src}. The request did not return a 200 OK response. (received ${res.status}))`
|
|
@@ -22,7 +25,10 @@ async function revalidateRemoteImage(src, revalidationData) {
|
|
|
22
25
|
...revalidationData.lastModified && { "If-Modified-Since": revalidationData.lastModified }
|
|
23
26
|
};
|
|
24
27
|
const req = new Request(src, { headers, cache: "no-cache" });
|
|
25
|
-
const res = await fetch(req);
|
|
28
|
+
const res = await fetch(req, { redirect: "manual" });
|
|
29
|
+
if (res.status >= 300 && res.status < 400) {
|
|
30
|
+
throw new Error(`Failed to revalidate cached remote image ${src}. The request was redirected.`);
|
|
31
|
+
}
|
|
26
32
|
if (!res.ok && res.status !== 304) {
|
|
27
33
|
throw new Error(
|
|
28
34
|
`Failed to revalidate cached remote image ${src}. The request did not return a 200 OK / 304 NOT MODIFIED response. (received ${res.status} ${res.statusText})`
|
|
@@ -8,8 +8,12 @@ async function loadRemoteImage(src, headers) {
|
|
|
8
8
|
try {
|
|
9
9
|
const res = await fetch(src, {
|
|
10
10
|
// Forward all headers from the original request
|
|
11
|
-
headers
|
|
11
|
+
headers,
|
|
12
|
+
redirect: "manual"
|
|
12
13
|
});
|
|
14
|
+
if (res.status >= 300 && res.status < 400) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
13
17
|
if (!res.ok) {
|
|
14
18
|
return void 0;
|
|
15
19
|
}
|
|
@@ -6,7 +6,10 @@ import { getConfiguredImageService } from "../internal.js";
|
|
|
6
6
|
import { etag } from "../utils/etag.js";
|
|
7
7
|
async function loadRemoteImage(src) {
|
|
8
8
|
try {
|
|
9
|
-
const res = await fetch(src);
|
|
9
|
+
const res = await fetch(src, { redirect: "manual" });
|
|
10
|
+
if (res.status >= 300 && res.status < 400) {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
10
13
|
if (!res.ok) {
|
|
11
14
|
return void 0;
|
|
12
15
|
}
|
package/dist/assets/internal.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRemotePath } from "@astrojs/internal-helpers/path";
|
|
2
|
+
import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
|
|
2
3
|
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
|
3
4
|
import { DEFAULT_HASH_PROPS } from "./consts.js";
|
|
4
5
|
import {
|
|
@@ -58,13 +59,21 @@ async function getImage(options, imageConfig) {
|
|
|
58
59
|
};
|
|
59
60
|
let originalWidth;
|
|
60
61
|
let originalHeight;
|
|
61
|
-
if (options.inferSize
|
|
62
|
-
const result = await inferRemoteSize(resolvedOptions.src);
|
|
63
|
-
resolvedOptions.width ??= result.width;
|
|
64
|
-
resolvedOptions.height ??= result.height;
|
|
65
|
-
originalWidth = result.width;
|
|
66
|
-
originalHeight = result.height;
|
|
62
|
+
if (options.inferSize) {
|
|
67
63
|
delete resolvedOptions.inferSize;
|
|
64
|
+
if (isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
|
|
65
|
+
if (!isRemoteAllowed(resolvedOptions.src, imageConfig)) {
|
|
66
|
+
throw new AstroError({
|
|
67
|
+
...AstroErrorData.RemoteImageNotAllowed,
|
|
68
|
+
message: AstroErrorData.RemoteImageNotAllowed.message(resolvedOptions.src)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const result = await inferRemoteSize(resolvedOptions.src, imageConfig);
|
|
72
|
+
resolvedOptions.width ??= result.width;
|
|
73
|
+
resolvedOptions.height ??= result.height;
|
|
74
|
+
originalWidth = result.width;
|
|
75
|
+
originalHeight = result.height;
|
|
76
|
+
}
|
|
68
77
|
}
|
|
69
78
|
const originalFilePath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : void 0;
|
|
70
79
|
const clonedSrc = isESMImportedImage(resolvedOptions.src) ? (
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import type { AstroConfig } from '../../types/public/config.js';
|
|
1
2
|
import type { ImageMetadata } from '../types.js';
|
|
3
|
+
type RemoteImageConfig = Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>;
|
|
2
4
|
/**
|
|
3
5
|
* Infers the dimensions of a remote image by streaming its data and analyzing it progressively until sufficient metadata is available.
|
|
4
6
|
*
|
|
5
7
|
* @param {string} url - The URL of the remote image from which to infer size metadata.
|
|
8
|
+
* @param {RemoteImageConfig} [imageConfig] - Optional image config used to validate remote allowlists.
|
|
6
9
|
* @return {Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>} Returns a promise that resolves to an object containing the image dimensions metadata excluding `src` and `fsPath`.
|
|
7
10
|
* @throws {AstroError} Thrown when the fetching fails or metadata cannot be extracted.
|
|
8
11
|
*/
|
|
9
|
-
export declare function inferRemoteSize(url: string): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>;
|
|
12
|
+
export declare function inferRemoteSize(url: string, imageConfig?: RemoteImageConfig): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>;
|
|
13
|
+
export {};
|
|
@@ -1,7 +1,39 @@
|
|
|
1
|
+
import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
|
|
1
2
|
import { AstroError, AstroErrorData } from "../../core/errors/index.js";
|
|
2
3
|
import { imageMetadata } from "./metadata.js";
|
|
3
|
-
async function inferRemoteSize(url) {
|
|
4
|
-
|
|
4
|
+
async function inferRemoteSize(url, imageConfig) {
|
|
5
|
+
if (!URL.canParse(url)) {
|
|
6
|
+
throw new AstroError({
|
|
7
|
+
...AstroErrorData.FailedToFetchRemoteImageDimensions,
|
|
8
|
+
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
const allowlistConfig = imageConfig ? {
|
|
12
|
+
domains: imageConfig.domains ?? [],
|
|
13
|
+
remotePatterns: imageConfig.remotePatterns ?? []
|
|
14
|
+
} : void 0;
|
|
15
|
+
if (!allowlistConfig) {
|
|
16
|
+
const parsedUrl = new URL(url);
|
|
17
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
18
|
+
throw new AstroError({
|
|
19
|
+
...AstroErrorData.FailedToFetchRemoteImageDimensions,
|
|
20
|
+
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (allowlistConfig && !isRemoteAllowed(url, allowlistConfig)) {
|
|
25
|
+
throw new AstroError({
|
|
26
|
+
...AstroErrorData.RemoteImageNotAllowed,
|
|
27
|
+
message: AstroErrorData.RemoteImageNotAllowed.message(url)
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
31
|
+
if (response.status >= 300 && response.status < 400) {
|
|
32
|
+
throw new AstroError({
|
|
33
|
+
...AstroErrorData.FailedToFetchRemoteImageDimensions,
|
|
34
|
+
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
5
37
|
if (!response.body || !response.ok) {
|
|
6
38
|
throw new AstroError({
|
|
7
39
|
...AstroErrorData.FailedToFetchRemoteImageDimensions,
|
|
@@ -101,11 +101,11 @@ function assets({ fs, settings, sync, logger }) {
|
|
|
101
101
|
if (id === resolvedVirtualModuleId) {
|
|
102
102
|
return {
|
|
103
103
|
code: `
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
export { getConfiguredImageService, isLocalService } from "astro/assets";
|
|
105
|
+
import { getImage as getImageInternal } from "astro/assets";
|
|
106
|
+
import { inferRemoteSize as inferRemoteSizeInternal } from "astro/assets/utils/inferRemoteSize.js";
|
|
107
|
+
export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
|
|
108
|
+
export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
|
|
109
109
|
|
|
110
110
|
export { default as Font } from "astro/components/Font.astro";
|
|
111
111
|
export * from "${RUNTIME_VIRTUAL_MODULE_ID}";
|
|
@@ -126,6 +126,9 @@ function assets({ fs, settings, sync, logger }) {
|
|
|
126
126
|
enumerable: false,
|
|
127
127
|
configurable: true,
|
|
128
128
|
});
|
|
129
|
+
export const inferRemoteSize = async (url) => {
|
|
130
|
+
return inferRemoteSizeInternal(url, imageConfig)
|
|
131
|
+
}
|
|
129
132
|
// This is used by the @astrojs/node integration to locate images.
|
|
130
133
|
// It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
|
|
131
134
|
// new URL("dist/...") is interpreted by the bundler as a signal to include that directory
|
|
@@ -164,7 +164,7 @@ ${contentConfig.error.message}`);
|
|
|
164
164
|
logger.info("Content config changed");
|
|
165
165
|
shouldClear = true;
|
|
166
166
|
}
|
|
167
|
-
if (previousAstroVersion && previousAstroVersion !== "5.17.
|
|
167
|
+
if (previousAstroVersion && previousAstroVersion !== "5.17.3") {
|
|
168
168
|
logger.info("Astro version changed");
|
|
169
169
|
shouldClear = true;
|
|
170
170
|
}
|
|
@@ -172,8 +172,8 @@ ${contentConfig.error.message}`);
|
|
|
172
172
|
logger.info("Clearing content store");
|
|
173
173
|
this.#store.clearAll();
|
|
174
174
|
}
|
|
175
|
-
if ("5.17.
|
|
176
|
-
await this.#store.metaStore().set("astro-version", "5.17.
|
|
175
|
+
if ("5.17.3") {
|
|
176
|
+
await this.#store.metaStore().set("astro-version", "5.17.3");
|
|
177
177
|
}
|
|
178
178
|
if (currentConfigDigest) {
|
|
179
179
|
await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
package/dist/core/constants.js
CHANGED
package/dist/core/dev/dev.js
CHANGED
|
@@ -22,7 +22,7 @@ async function dev(inlineConfig) {
|
|
|
22
22
|
await telemetry.record([]);
|
|
23
23
|
const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
|
|
24
24
|
const logger = restart.container.logger;
|
|
25
|
-
const currentVersion = "5.17.
|
|
25
|
+
const currentVersion = "5.17.3";
|
|
26
26
|
const isPrerelease = currentVersion.includes("-");
|
|
27
27
|
if (!isPrerelease) {
|
|
28
28
|
try {
|
|
@@ -491,6 +491,19 @@ export declare const FailedToFetchRemoteImageDimensions: {
|
|
|
491
491
|
message: (imageURL: string) => string;
|
|
492
492
|
hint: string;
|
|
493
493
|
};
|
|
494
|
+
/**
|
|
495
|
+
* @docs
|
|
496
|
+
* @message
|
|
497
|
+
* Remote image `IMAGE_URL` is not allowed by your image configuration.
|
|
498
|
+
* @description
|
|
499
|
+
* The remote image URL does not match your configured `image.domains` or `image.remotePatterns`.
|
|
500
|
+
*/
|
|
501
|
+
export declare const RemoteImageNotAllowed: {
|
|
502
|
+
name: string;
|
|
503
|
+
title: string;
|
|
504
|
+
message: (imageURL: string) => string;
|
|
505
|
+
hint: string;
|
|
506
|
+
};
|
|
494
507
|
/**
|
|
495
508
|
* @docs
|
|
496
509
|
* @description
|
|
@@ -175,6 +175,12 @@ const FailedToFetchRemoteImageDimensions = {
|
|
|
175
175
|
message: (imageURL) => `Failed to get the dimensions for ${imageURL}.`,
|
|
176
176
|
hint: "Verify your remote image URL is accurate, and that you are not using `inferSize` with a file located in your `public/` folder."
|
|
177
177
|
};
|
|
178
|
+
const RemoteImageNotAllowed = {
|
|
179
|
+
name: "RemoteImageNotAllowed",
|
|
180
|
+
title: "Remote image is not allowed",
|
|
181
|
+
message: (imageURL) => `Remote image ${imageURL} is not allowed by your image configuration.`,
|
|
182
|
+
hint: "Update `image.domains` or `image.remotePatterns`, or remove `inferSize` for this image."
|
|
183
|
+
};
|
|
178
184
|
const UnsupportedImageFormat = {
|
|
179
185
|
name: "UnsupportedImageFormat",
|
|
180
186
|
title: "Unsupported image format",
|
|
@@ -832,6 +838,7 @@ export {
|
|
|
832
838
|
PrerenderDynamicEndpointPathCollide,
|
|
833
839
|
PrerenderRouteConflict,
|
|
834
840
|
RedirectWithNoLocation,
|
|
841
|
+
RemoteImageNotAllowed,
|
|
835
842
|
RenderUndefinedEntryError,
|
|
836
843
|
ReservedSlotName,
|
|
837
844
|
ResponseSentError,
|
package/dist/core/messages.js
CHANGED
|
@@ -38,7 +38,7 @@ function serverStart({
|
|
|
38
38
|
host,
|
|
39
39
|
base
|
|
40
40
|
}) {
|
|
41
|
-
const version = "5.17.
|
|
41
|
+
const version = "5.17.3";
|
|
42
42
|
const localPrefix = `${dim("\u2503")} Local `;
|
|
43
43
|
const networkPrefix = `${dim("\u2503")} Network `;
|
|
44
44
|
const emptyPrefix = " ".repeat(11);
|
|
@@ -275,7 +275,7 @@ function printHelp({
|
|
|
275
275
|
message.push(
|
|
276
276
|
linebreak(),
|
|
277
277
|
` ${bgGreen(black(` ${commandName} `))} ${green(
|
|
278
|
-
`v${"5.17.
|
|
278
|
+
`v${"5.17.3"}`
|
|
279
279
|
)} ${headline}`
|
|
280
280
|
);
|
|
281
281
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro",
|
|
3
|
-
"version": "5.17.
|
|
3
|
+
"version": "5.17.3",
|
|
4
4
|
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "withastro",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"dlv": "^1.1.3",
|
|
114
114
|
"dset": "^3.1.4",
|
|
115
115
|
"es-module-lexer": "^1.7.0",
|
|
116
|
-
"esbuild": "^0.27.
|
|
116
|
+
"esbuild": "^0.27.3",
|
|
117
117
|
"estree-walker": "^3.0.3",
|
|
118
118
|
"flattie": "^1.1.1",
|
|
119
119
|
"fontace": "~0.4.0",
|
|
@@ -154,8 +154,8 @@
|
|
|
154
154
|
"zod-to-json-schema": "^3.25.1",
|
|
155
155
|
"zod-to-ts": "^1.2.0",
|
|
156
156
|
"@astrojs/internal-helpers": "0.7.5",
|
|
157
|
-
"@astrojs/
|
|
158
|
-
"@astrojs/
|
|
157
|
+
"@astrojs/telemetry": "3.3.0",
|
|
158
|
+
"@astrojs/markdown-remark": "6.3.10"
|
|
159
159
|
},
|
|
160
160
|
"optionalDependencies": {
|
|
161
161
|
"sharp": "^0.34.0"
|