astro 6.0.0-beta.19 → 6.0.0-beta.20
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/components/ClientRouter.astro +3 -3
- package/dist/actions/runtime/server.js +29 -45
- package/dist/assets/fonts/providers/local.d.ts +1 -1
- package/dist/assets/utils/imageKind.d.ts +2 -2
- package/dist/assets/utils/resolveImports.d.ts +2 -2
- package/dist/assets/vite-plugin-assets.js +40 -35
- package/dist/cli/index.js +3 -5
- package/dist/cli/info/infra/tinyclip-clipboard.d.ts +10 -0
- package/dist/cli/info/infra/tinyclip-clipboard.js +32 -0
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/container/index.d.ts +1 -1
- package/dist/container/index.js +1 -0
- package/dist/content/content-layer.js +3 -3
- package/dist/content/data-store.d.ts +1 -1
- package/dist/core/app/base.d.ts +1 -1
- package/dist/core/app/base.js +2 -0
- package/dist/core/app/dev/pipeline.js +3 -1
- package/dist/core/app/manifest.d.ts +0 -1
- package/dist/core/app/manifest.js +0 -4
- package/dist/core/app/node.d.ts +3 -1
- package/dist/core/app/node.js +22 -8
- package/dist/core/app/types.d.ts +2 -1
- package/dist/core/app/validate-headers.d.ts +1 -1
- package/dist/core/app/validate-headers.js +0 -2
- package/dist/core/base-pipeline.d.ts +1 -1
- package/dist/core/base-pipeline.js +2 -3
- package/dist/core/build/common.d.ts +1 -1
- package/dist/core/build/pipeline.js +3 -1
- package/dist/core/build/plugins/plugin-component-entry.d.ts +1 -1
- package/dist/core/build/plugins/plugin-manifest.js +1 -0
- package/dist/core/build/static-build.d.ts +1 -1
- package/dist/core/config/schemas/base.d.ts +2 -0
- package/dist/core/config/schemas/base.js +4 -2
- package/dist/core/config/schemas/refined.js +1 -1
- package/dist/core/config/schemas/relative.d.ts +3 -0
- package/dist/core/constants.js +1 -1
- package/dist/core/cookies/cookies.js +1 -1
- package/dist/core/create-vite.js +2 -0
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/errors-data.d.ts +46 -3
- package/dist/core/errors/errors-data.js +21 -4
- package/dist/core/errors/userError.d.ts +1 -0
- package/dist/core/errors/userError.js +3 -1
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/callMiddleware.d.ts +1 -1
- package/dist/core/middleware/index.d.ts +8 -1
- package/dist/core/middleware/index.js +5 -10
- package/dist/core/render/slots.js +1 -1
- package/dist/core/render-context.js +4 -4
- package/dist/core/request-body.d.ts +17 -0
- package/dist/core/request-body.js +43 -0
- package/dist/core/routing/create-manifest.js +2 -2
- package/dist/core/routing/match.d.ts +3 -3
- package/dist/core/server-islands/endpoint.d.ts +1 -1
- package/dist/core/server-islands/endpoint.js +12 -3
- package/dist/env/env-loader.js +25 -3
- package/dist/env/validators.d.ts +10 -0
- package/dist/env/validators.js +22 -0
- package/dist/i18n/index.d.ts +2 -2
- package/dist/manifest/serialized.js +2 -0
- package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +3 -3
- package/dist/runtime/client/dev-toolbar/apps/audit/ui/audit-list-window.js +1 -1
- package/dist/runtime/client/visible.d.ts +1 -1
- package/dist/runtime/server/html-string-cache.d.ts +12 -0
- package/dist/runtime/server/html-string-cache.js +71 -0
- package/dist/runtime/server/render/component.js +8 -1
- package/dist/runtime/server/render/queue/pool.d.ts +12 -185
- package/dist/runtime/server/render/queue/pool.js +10 -128
- package/dist/types/astro.d.ts +1 -1
- package/dist/types/public/config.d.ts +28 -2
- package/dist/types/public/context.d.ts +3 -3
- package/dist/types/public/integrations.d.ts +1 -1
- package/dist/types/public/internal.d.ts +1 -1
- package/dist/types/public/manifest.d.ts +1 -1
- package/dist/vite-plugin-app/pipeline.js +3 -1
- package/dist/vite-plugin-astro-server/plugin.js +2 -0
- package/package.json +5 -4
- package/tsconfigs/base.json +1 -3
- package/dist/cli/info/infra/cli-clipboard.d.ts +0 -13
- package/dist/cli/info/infra/cli-clipboard.js +0 -78
- package/dist/core/routing/request.d.ts +0 -9
- package/dist/core/routing/request.js +0 -9
|
@@ -115,14 +115,14 @@ const { fallback = 'animate' } = Astro.props;
|
|
|
115
115
|
const form = el as HTMLFormElement;
|
|
116
116
|
const formData = new FormData(form, submitter);
|
|
117
117
|
// form.action and form.method can point to an <input name="action"> or <input name="method">
|
|
118
|
-
// in which case should
|
|
118
|
+
// in which case should fall back to the form attribute
|
|
119
119
|
const formAction =
|
|
120
120
|
typeof form.action === 'string' ? form.action : form.getAttribute('action');
|
|
121
121
|
const formMethod =
|
|
122
122
|
typeof form.method === 'string' ? form.method : form.getAttribute('method');
|
|
123
|
-
// Use the form action, if defined
|
|
123
|
+
// Use the form action, if defined; otherwise, fall back to current path.
|
|
124
124
|
let action = submitter?.getAttribute('formaction') ?? formAction ?? location.pathname;
|
|
125
|
-
// Use the form method, if defined
|
|
125
|
+
// Use the form method, if defined; otherwise, fall back to "get"
|
|
126
126
|
const method = submitter?.getAttribute('formmethod') ?? formMethod ?? 'get';
|
|
127
127
|
|
|
128
128
|
// the "dialog" method is a special keyword used within <dialog> elements
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "../../core/errors/errors-data.js";
|
|
10
10
|
import { AstroError } from "../../core/errors/errors.js";
|
|
11
11
|
import { removeTrailingForwardSlash } from "../../core/path.js";
|
|
12
|
+
import { BodySizeLimitError, readBodyWithLimit } from "../../core/request-body.js";
|
|
12
13
|
import { ACTION_QUERY_PARAMS, ACTION_RPC_ROUTE_PATTERN } from "../consts.js";
|
|
13
14
|
import {
|
|
14
15
|
ActionError,
|
|
@@ -162,26 +163,36 @@ async function parseRequestBody(request, bodySizeLimit) {
|
|
|
162
163
|
message: `Request body exceeds ${bodySizeLimit} bytes`
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
try {
|
|
167
|
+
if (hasContentType(contentType, formContentTypes)) {
|
|
168
|
+
if (!hasContentLength) {
|
|
169
|
+
const body = await readBodyWithLimit(request.clone(), bodySizeLimit);
|
|
170
|
+
const formRequest = new Request(request.url, {
|
|
171
|
+
method: request.method,
|
|
172
|
+
headers: request.headers,
|
|
173
|
+
body: toArrayBuffer(body)
|
|
174
|
+
});
|
|
175
|
+
return await formRequest.formData();
|
|
176
|
+
}
|
|
177
|
+
return await request.clone().formData();
|
|
174
178
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return
|
|
179
|
+
if (hasContentType(contentType, ["application/json"])) {
|
|
180
|
+
if (contentLength === 0) return void 0;
|
|
181
|
+
if (!hasContentLength) {
|
|
182
|
+
const body = await readBodyWithLimit(request.clone(), bodySizeLimit);
|
|
183
|
+
if (body.byteLength === 0) return void 0;
|
|
184
|
+
return JSON.parse(new TextDecoder().decode(body));
|
|
185
|
+
}
|
|
186
|
+
return await request.clone().json();
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
if (e instanceof BodySizeLimitError) {
|
|
190
|
+
throw new ActionError({
|
|
191
|
+
code: "CONTENT_TOO_LARGE",
|
|
192
|
+
message: `Request body exceeds ${bodySizeLimit} bytes`
|
|
193
|
+
});
|
|
183
194
|
}
|
|
184
|
-
|
|
195
|
+
throw e;
|
|
185
196
|
}
|
|
186
197
|
throw new TypeError("Unsupported content type");
|
|
187
198
|
}
|
|
@@ -323,33 +334,6 @@ function serializeActionResult(res) {
|
|
|
323
334
|
body
|
|
324
335
|
};
|
|
325
336
|
}
|
|
326
|
-
async function readRequestBodyWithLimit(request, limit) {
|
|
327
|
-
if (!request.body) return new Uint8Array();
|
|
328
|
-
const reader = request.body.getReader();
|
|
329
|
-
const chunks = [];
|
|
330
|
-
let received = 0;
|
|
331
|
-
while (true) {
|
|
332
|
-
const { done, value } = await reader.read();
|
|
333
|
-
if (done) break;
|
|
334
|
-
if (value) {
|
|
335
|
-
received += value.byteLength;
|
|
336
|
-
if (received > limit) {
|
|
337
|
-
throw new ActionError({
|
|
338
|
-
code: "CONTENT_TOO_LARGE",
|
|
339
|
-
message: `Request body exceeds ${limit} bytes`
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
chunks.push(value);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
const buffer = new Uint8Array(received);
|
|
346
|
-
let offset = 0;
|
|
347
|
-
for (const chunk of chunks) {
|
|
348
|
-
buffer.set(chunk, offset);
|
|
349
|
-
offset += chunk.byteLength;
|
|
350
|
-
}
|
|
351
|
-
return buffer;
|
|
352
|
-
}
|
|
353
337
|
function toArrayBuffer(buffer) {
|
|
354
338
|
const copy = new Uint8Array(buffer.byteLength);
|
|
355
339
|
copy.set(buffer);
|
|
@@ -7,7 +7,7 @@ type RawSource = string | URL | {
|
|
|
7
7
|
};
|
|
8
8
|
interface Variant extends FamilyProperties {
|
|
9
9
|
/**
|
|
10
|
-
* Font [sources](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src). It can be a path relative to the root, a package import or a URL. URLs are particularly useful if you inject local fonts through an integration.
|
|
10
|
+
* Font [sources](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src). It can be a path relative to the root, a package import, or a URL. URLs are particularly useful if you inject local fonts through an integration.
|
|
11
11
|
*
|
|
12
12
|
* We recommend not putting your font files in [the `public/` directory](/en/reference/configuration-reference/#publicdir). Since Astro will copy these files into that folder at build time, this will result in duplicated files
|
|
13
13
|
* in your build output. Instead, store them somewhere else in your project, such as in `src/`.
|
|
@@ -3,14 +3,14 @@ import type { ImageMetadata, UnresolvedImageTransform } from '../types.js';
|
|
|
3
3
|
* Determines if the given source is an ECMAScript Module (ESM) imported image.
|
|
4
4
|
*
|
|
5
5
|
* @param {ImageMetadata | string} src - The source to check. It can be an `ImageMetadata` object or a string.
|
|
6
|
-
* @return {boolean} Returns `true` if the source is an `ImageMetadata` object
|
|
6
|
+
* @return {boolean} Returns `true` if the source is an `ImageMetadata` object; otherwise, `false`.
|
|
7
7
|
*/
|
|
8
8
|
export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
|
|
9
9
|
/**
|
|
10
10
|
* Determines if the provided source is a remote image URL in the form of a string.
|
|
11
11
|
*
|
|
12
12
|
* @param {ImageMetadata | string} src - The source to check, which can either be an `ImageMetadata` object or a string.
|
|
13
|
-
* @return {boolean} Returns `true` if the source is a string
|
|
13
|
+
* @return {boolean} Returns `true` if the source is a string; otherwise, `false`.
|
|
14
14
|
*/
|
|
15
15
|
export declare function isRemoteImage(src: ImageMetadata | string): src is string;
|
|
16
16
|
/**
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Resolves an image src from a content file (such as markdown) to a module ID or import that can be resolved by Vite.
|
|
3
3
|
*
|
|
4
4
|
* @param imageSrc The src attribute of an image tag
|
|
5
|
-
* @param filePath The path to the file that contains the
|
|
6
|
-
* @returns A module id of the image that can be
|
|
5
|
+
* @param filePath The path to the file that contains the image relative to the site root
|
|
6
|
+
* @returns A module id of the image that can be resolved by Vite, or undefined if it is not a local image
|
|
7
7
|
*/
|
|
8
8
|
export declare function imageSrcToImportId(imageSrc: string, filePath?: string): string | undefined;
|
|
9
9
|
export declare const importIdToSymbolName: (importId: string) => string;
|
|
@@ -118,59 +118,64 @@ function assets({ fs, settings, sync, logger }) {
|
|
|
118
118
|
id: new RegExp(`^(${RESOLVED_VIRTUAL_MODULE_ID})$`)
|
|
119
119
|
},
|
|
120
120
|
handler() {
|
|
121
|
+
const isServerEnvironment = isAstroServerEnvironment(this.environment);
|
|
122
|
+
const getImageExport = isServerEnvironment ? `import { getImage as getImageInternal } from "astro/assets";
|
|
123
|
+
export const getImage = async (options) => await getImageInternal(options, imageConfig);` : `import { AstroError, AstroErrorData } from "astro/errors";
|
|
124
|
+
export const getImage = async () => {
|
|
125
|
+
throw new AstroError(AstroErrorData.GetImageNotUsedOnServer);
|
|
126
|
+
};`;
|
|
121
127
|
return {
|
|
122
128
|
code: `
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
import { inferRemoteSize as inferRemoteSizeInternal } from "astro/assets/utils/inferRemoteSize.js";
|
|
129
|
+
import { getConfiguredImageService as _getConfiguredImageService } from "astro/assets";
|
|
130
|
+
export { isLocalService } from "astro/assets";
|
|
131
|
+
${settings.config.image.responsiveStyles ? `import "${VIRTUAL_IMAGE_STYLES_ID}";` : ""}
|
|
132
|
+
export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
|
|
133
|
+
export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
|
|
134
|
+
import { inferRemoteSize as inferRemoteSizeInternal } from "astro/assets/utils/inferRemoteSize.js";
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
export const getConfiguredImageService = _getConfiguredImageService;
|
|
136
|
+
export { default as Font } from "astro/components/Font.astro";
|
|
137
|
+
export * from "${RUNTIME_VIRTUAL_MODULE_ID}";
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
export const getConfiguredImageService = _getConfiguredImageService;
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
export const viteFSConfig = ${JSON.stringify(resolvedConfig.server.fs ?? {})};
|
|
142
|
+
|
|
143
|
+
export const safeModulePaths = new Set(${JSON.stringify(
|
|
139
144
|
// @ts-expect-error safeModulePaths is internal to Vite
|
|
140
145
|
Array.from(resolvedConfig.safeModulePaths ?? [])
|
|
141
146
|
)});
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
export const fsDenyGlob = ${serializeFsDenyGlob(resolvedConfig.server.fs?.deny ?? [])};
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
const assetQueryParams = ${settings.adapter?.client?.assetQueryParams ? `new URLSearchParams(${JSON.stringify(
|
|
146
151
|
Array.from(settings.adapter.client.assetQueryParams.entries())
|
|
147
152
|
)})` : "undefined"};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
export const imageConfig = ${JSON.stringify(settings.config.image)};
|
|
154
|
+
Object.defineProperty(imageConfig, 'assetQueryParams', {
|
|
155
|
+
value: assetQueryParams,
|
|
156
|
+
enumerable: false,
|
|
157
|
+
configurable: true,
|
|
158
|
+
});
|
|
159
|
+
export const inferRemoteSize = async (url) => {
|
|
160
|
+
const service = await _getConfiguredImageService();
|
|
161
|
+
return service.getRemoteSize?.(url, imageConfig) ?? inferRemoteSizeInternal(url, imageConfig);
|
|
162
|
+
}
|
|
163
|
+
// This is used by the @astrojs/node integration to locate images.
|
|
164
|
+
// It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel)
|
|
165
|
+
// new URL("dist/...") is interpreted by the bundler as a signal to include that directory
|
|
166
|
+
// in the Lambda bundle, which would bloat the bundle with images.
|
|
167
|
+
// To prevent this, we mark the URL construction as pure,
|
|
168
|
+
// so that it's tree-shaken away for all platforms that don't need it.
|
|
169
|
+
export const outDir = /* #__PURE__ */ new URL(${JSON.stringify(
|
|
165
170
|
new URL(
|
|
166
171
|
settings.buildOutput === "server" ? settings.config.build.client : settings.config.outDir
|
|
167
172
|
)
|
|
168
173
|
)});
|
|
169
|
-
|
|
174
|
+
export const serverDir = /* #__PURE__ */ new URL(${JSON.stringify(
|
|
170
175
|
new URL(settings.config.build.server)
|
|
171
176
|
)});
|
|
172
|
-
|
|
173
|
-
|
|
177
|
+
${getImageExport}
|
|
178
|
+
`
|
|
174
179
|
};
|
|
175
180
|
}
|
|
176
181
|
},
|
package/dist/cli/index.js
CHANGED
|
@@ -75,7 +75,7 @@ async function runCommand(cmd, flags) {
|
|
|
75
75
|
{ getPackageManager },
|
|
76
76
|
{ StyledDebugInfoFormatter },
|
|
77
77
|
{ ClackPrompt },
|
|
78
|
-
{
|
|
78
|
+
{ TinyclipClipboard },
|
|
79
79
|
{ PassthroughTextStyler },
|
|
80
80
|
{ infoCommand }
|
|
81
81
|
] = await Promise.all([
|
|
@@ -88,7 +88,7 @@ async function runCommand(cmd, flags) {
|
|
|
88
88
|
import("./info/core/get-package-manager.js"),
|
|
89
89
|
import("./info/infra/styled-debug-info-formatter.js"),
|
|
90
90
|
import("./info/infra/clack-prompt.js"),
|
|
91
|
-
import("./info/infra/
|
|
91
|
+
import("./info/infra/tinyclip-clipboard.js"),
|
|
92
92
|
import("./infra/passthrough-text-styler.js"),
|
|
93
93
|
import("./info/core/info.js")
|
|
94
94
|
]);
|
|
@@ -108,10 +108,8 @@ async function runCommand(cmd, flags) {
|
|
|
108
108
|
nodeVersionProvider
|
|
109
109
|
});
|
|
110
110
|
const prompt = new ClackPrompt({ force: flags.copy });
|
|
111
|
-
const clipboard = new
|
|
112
|
-
commandExecutor,
|
|
111
|
+
const clipboard = new TinyclipClipboard({
|
|
113
112
|
logger,
|
|
114
|
-
operatingSystemProvider,
|
|
115
113
|
prompt
|
|
116
114
|
});
|
|
117
115
|
return await runner.run(infoCommand, {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Logger } from '../../../core/logger/core.js';
|
|
2
|
+
import type { Clipboard, Prompt } from '../definitions.js';
|
|
3
|
+
export declare class TinyclipClipboard implements Clipboard {
|
|
4
|
+
#private;
|
|
5
|
+
constructor({ logger, prompt, }: {
|
|
6
|
+
logger: Logger;
|
|
7
|
+
prompt: Prompt;
|
|
8
|
+
});
|
|
9
|
+
copy(text: string): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { writeText } from "tinyclip";
|
|
2
|
+
class TinyclipClipboard {
|
|
3
|
+
#logger;
|
|
4
|
+
#prompt;
|
|
5
|
+
constructor({
|
|
6
|
+
logger,
|
|
7
|
+
prompt
|
|
8
|
+
}) {
|
|
9
|
+
this.#logger = logger;
|
|
10
|
+
this.#prompt = prompt;
|
|
11
|
+
}
|
|
12
|
+
async copy(text) {
|
|
13
|
+
if (!await this.#prompt.confirm({
|
|
14
|
+
message: "Copy to clipboard?",
|
|
15
|
+
defaultValue: true
|
|
16
|
+
})) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await writeText(text.trim());
|
|
21
|
+
this.#logger.info("SKIP_FORMAT", "Copied to clipboard!");
|
|
22
|
+
} catch {
|
|
23
|
+
this.#logger.error(
|
|
24
|
+
"SKIP_FORMAT",
|
|
25
|
+
"Sorry, something went wrong! Please copy the text above manually."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
TinyclipClipboard
|
|
32
|
+
};
|
|
@@ -53,7 +53,7 @@ export type ContainerRenderOptions = {
|
|
|
53
53
|
*/
|
|
54
54
|
params?: Record<string, string | undefined>;
|
|
55
55
|
/**
|
|
56
|
-
* Useful if your component needs to access some locals without the use
|
|
56
|
+
* Useful if your component needs to access some locals without the use of middleware.
|
|
57
57
|
* ```js
|
|
58
58
|
* container.renderToString(Component, { locals: { getSomeValue() {} } });
|
|
59
59
|
* ```
|
package/dist/container/index.js
CHANGED
|
@@ -49,6 +49,7 @@ function createManifest(manifest, renderers, middleware) {
|
|
|
49
49
|
checkOrigin: false,
|
|
50
50
|
allowedDomains: manifest?.allowedDomains ?? [],
|
|
51
51
|
actionBodySizeLimit: 1024 * 1024,
|
|
52
|
+
serverIslandBodySizeLimit: 1024 * 1024,
|
|
52
53
|
middleware: manifest?.middleware ?? middlewareInstance,
|
|
53
54
|
key: createKey(),
|
|
54
55
|
csp: manifest?.csp,
|
|
@@ -189,7 +189,7 @@ ${contentConfig.error.message}`
|
|
|
189
189
|
logger.info("Content config changed");
|
|
190
190
|
shouldClear = true;
|
|
191
191
|
}
|
|
192
|
-
if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.
|
|
192
|
+
if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.20") {
|
|
193
193
|
logger.info("Astro version changed");
|
|
194
194
|
shouldClear = true;
|
|
195
195
|
}
|
|
@@ -197,8 +197,8 @@ ${contentConfig.error.message}`
|
|
|
197
197
|
logger.info("Clearing content store");
|
|
198
198
|
this.#store.clearAll();
|
|
199
199
|
}
|
|
200
|
-
if ("6.0.0-beta.
|
|
201
|
-
this.#store.metaStore().set("astro-version", "6.0.0-beta.
|
|
200
|
+
if ("6.0.0-beta.20") {
|
|
201
|
+
this.#store.metaStore().set("astro-version", "6.0.0-beta.20");
|
|
202
202
|
}
|
|
203
203
|
if (currentConfigDigest) {
|
|
204
204
|
this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
|
@@ -7,7 +7,7 @@ export interface RenderedContent {
|
|
|
7
7
|
imagePaths?: Array<string>;
|
|
8
8
|
/** Any headings that are present in this file. */
|
|
9
9
|
headings?: MarkdownHeading[];
|
|
10
|
-
/** Raw frontmatter, parsed
|
|
10
|
+
/** Raw frontmatter, parsed from the file. This may include data from remark plugins. */
|
|
11
11
|
frontmatter?: Record<string, any>;
|
|
12
12
|
/** Any other metadata that is present in this file. */
|
|
13
13
|
[key: string]: unknown;
|
package/dist/core/app/base.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface RenderOptions {
|
|
|
34
34
|
/**
|
|
35
35
|
* A custom fetch function for retrieving prerendered pages - 404 or 500.
|
|
36
36
|
*
|
|
37
|
-
* If not provided, Astro will
|
|
37
|
+
* If not provided, Astro will fall back to its default behavior for fetching error pages.
|
|
38
38
|
*
|
|
39
39
|
* When a dynamic route is matched but ultimately results in a 404, this function will be used
|
|
40
40
|
* to fetch the prerendered 404 page if available. Similarly, it may be used to fetch a
|
package/dist/core/app/base.js
CHANGED
|
@@ -493,6 +493,8 @@ class BaseApp {
|
|
|
493
493
|
const status = override?.status ? override.status : originalResponse.status === 200 ? newResponse.status : originalResponse.status;
|
|
494
494
|
try {
|
|
495
495
|
originalResponse.headers.delete("Content-type");
|
|
496
|
+
originalResponse.headers.delete("Content-Length");
|
|
497
|
+
originalResponse.headers.delete("Transfer-Encoding");
|
|
496
498
|
} catch {
|
|
497
499
|
}
|
|
498
500
|
const newHeaders = new Headers();
|
|
@@ -35,7 +35,9 @@ class NonRunnablePipeline extends Pipeline {
|
|
|
35
35
|
);
|
|
36
36
|
if (queueRenderingEnabled(manifest.experimentalQueuedRendering)) {
|
|
37
37
|
pipeline.nodePool = newNodePool(manifest.experimentalQueuedRendering);
|
|
38
|
-
|
|
38
|
+
if (manifest.experimentalQueuedRendering.contentCache) {
|
|
39
|
+
pipeline.htmlStringCache = new HTMLStringCache(1e3);
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
return pipeline;
|
|
41
43
|
}
|
|
@@ -9,5 +9,4 @@ export declare function deserializeRouteData(rawRouteData: SerializedRouteData):
|
|
|
9
9
|
export declare function serializeRouteInfo(routeInfo: RouteInfo, trailingSlash: AstroConfig['trailingSlash']): SerializedRouteInfo;
|
|
10
10
|
export declare function deserializeRouteInfo(rawRouteInfo: SerializedRouteInfo): RouteInfo;
|
|
11
11
|
export declare function queuePoolSize(config: NonNullable<SSRManifest['experimentalQueuedRendering']>): number;
|
|
12
|
-
export declare function queueContentCache(config: NonNullable<SSRManifest['experimentalQueuedRendering']>): boolean;
|
|
13
12
|
export declare function queueRenderingEnabled(config: SSRManifest['experimentalQueuedRendering']): boolean;
|
|
@@ -101,9 +101,6 @@ function deserializeRouteInfo(rawRouteInfo) {
|
|
|
101
101
|
function queuePoolSize(config) {
|
|
102
102
|
return config?.poolSize ?? 1e3;
|
|
103
103
|
}
|
|
104
|
-
function queueContentCache(config) {
|
|
105
|
-
return config?.contentCache ?? false;
|
|
106
|
-
}
|
|
107
104
|
function queueRenderingEnabled(config) {
|
|
108
105
|
return config?.enabled ?? false;
|
|
109
106
|
}
|
|
@@ -111,7 +108,6 @@ export {
|
|
|
111
108
|
deserializeManifest,
|
|
112
109
|
deserializeRouteData,
|
|
113
110
|
deserializeRouteInfo,
|
|
114
|
-
queueContentCache,
|
|
115
111
|
queuePoolSize,
|
|
116
112
|
queueRenderingEnabled,
|
|
117
113
|
serializeRouteData,
|
package/dist/core/app/node.d.ts
CHANGED
|
@@ -25,9 +25,11 @@ interface NodeRequest extends IncomingMessage {
|
|
|
25
25
|
* })
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
export declare function createRequest(req: NodeRequest, { skipBody, allowedDomains, }?: {
|
|
28
|
+
export declare function createRequest(req: NodeRequest, { skipBody, allowedDomains, bodySizeLimit, port: serverPort, }?: {
|
|
29
29
|
skipBody?: boolean;
|
|
30
30
|
allowedDomains?: Partial<RemotePattern>[];
|
|
31
|
+
bodySizeLimit?: number;
|
|
32
|
+
port?: number;
|
|
31
33
|
}): Request;
|
|
32
34
|
/**
|
|
33
35
|
* Streams a web-standard Response into a NodeJS Server Response.
|
package/dist/core/app/node.js
CHANGED
|
@@ -7,7 +7,9 @@ import { App } from "./app.js";
|
|
|
7
7
|
import { validateForwardedHeaders, validateHost } from "./validate-headers.js";
|
|
8
8
|
function createRequest(req, {
|
|
9
9
|
skipBody = false,
|
|
10
|
-
allowedDomains = []
|
|
10
|
+
allowedDomains = [],
|
|
11
|
+
bodySizeLimit,
|
|
12
|
+
port: serverPort
|
|
11
13
|
} = {}) {
|
|
12
14
|
const controller = new AbortController();
|
|
13
15
|
const isEncrypted = "encrypted" in req.socket && req.socket.encrypted;
|
|
@@ -29,7 +31,7 @@ function createRequest(req, {
|
|
|
29
31
|
allowedDomains
|
|
30
32
|
);
|
|
31
33
|
const hostname = validated.host ?? validatedHostname ?? "localhost";
|
|
32
|
-
const port = validated.port;
|
|
34
|
+
const port = validated.port ?? (!validated.host && !validatedHostname && serverPort ? String(serverPort) : void 0);
|
|
33
35
|
let url;
|
|
34
36
|
try {
|
|
35
37
|
const hostnamePort = getHostnamePort(hostname, port);
|
|
@@ -45,7 +47,7 @@ function createRequest(req, {
|
|
|
45
47
|
};
|
|
46
48
|
const bodyAllowed = options.method !== "HEAD" && options.method !== "GET" && skipBody === false;
|
|
47
49
|
if (bodyAllowed) {
|
|
48
|
-
Object.assign(options, makeRequestBody(req));
|
|
50
|
+
Object.assign(options, makeRequestBody(req, bodySizeLimit));
|
|
49
51
|
}
|
|
50
52
|
const request = new Request(url, options);
|
|
51
53
|
const socket = getRequestSocket(req);
|
|
@@ -210,7 +212,7 @@ function makeRequestHeaders(req) {
|
|
|
210
212
|
}
|
|
211
213
|
return headers;
|
|
212
214
|
}
|
|
213
|
-
function makeRequestBody(req) {
|
|
215
|
+
function makeRequestBody(req, bodySizeLimit) {
|
|
214
216
|
if (req.body !== void 0) {
|
|
215
217
|
if (typeof req.body === "string" && req.body.length > 0) {
|
|
216
218
|
return { body: Buffer.from(req.body) };
|
|
@@ -219,23 +221,35 @@ function makeRequestBody(req) {
|
|
|
219
221
|
return { body: Buffer.from(JSON.stringify(req.body)) };
|
|
220
222
|
}
|
|
221
223
|
if (typeof req.body === "object" && req.body !== null && typeof req.body[Symbol.asyncIterator] !== "undefined") {
|
|
222
|
-
return asyncIterableToBodyProps(req.body);
|
|
224
|
+
return asyncIterableToBodyProps(req.body, bodySizeLimit);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
|
-
return asyncIterableToBodyProps(req);
|
|
227
|
+
return asyncIterableToBodyProps(req, bodySizeLimit);
|
|
226
228
|
}
|
|
227
|
-
function asyncIterableToBodyProps(iterable) {
|
|
229
|
+
function asyncIterableToBodyProps(iterable, bodySizeLimit) {
|
|
230
|
+
const source = bodySizeLimit != null ? limitAsyncIterable(iterable, bodySizeLimit) : iterable;
|
|
228
231
|
return {
|
|
229
232
|
// Node uses undici for the Request implementation. Undici accepts
|
|
230
233
|
// a non-standard async iterable for the body.
|
|
231
234
|
// @ts-expect-error
|
|
232
|
-
body:
|
|
235
|
+
body: source,
|
|
233
236
|
// The duplex property is required when using a ReadableStream or async
|
|
234
237
|
// iterable for the body. The type definitions do not include the duplex
|
|
235
238
|
// property because they are not up-to-date.
|
|
236
239
|
duplex: "half"
|
|
237
240
|
};
|
|
238
241
|
}
|
|
242
|
+
async function* limitAsyncIterable(iterable, limit) {
|
|
243
|
+
let received = 0;
|
|
244
|
+
for await (const chunk of iterable) {
|
|
245
|
+
const byteLength = chunk instanceof Uint8Array ? chunk.byteLength : typeof chunk === "string" ? Buffer.byteLength(chunk) : 0;
|
|
246
|
+
received += byteLength;
|
|
247
|
+
if (received > limit) {
|
|
248
|
+
throw new Error(`Body size limit exceeded: received more than ${limit} bytes`);
|
|
249
|
+
}
|
|
250
|
+
yield chunk;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
239
253
|
function getAbortControllerCleanup(req) {
|
|
240
254
|
if (!req) return void 0;
|
|
241
255
|
const cleanup = Reflect.get(req, nodeRequestAbortControllerCleanupSymbol);
|
package/dist/core/app/types.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export type SSRManifest = {
|
|
|
64
64
|
enabled: boolean;
|
|
65
65
|
/** Node pool size for memory reuse (default: 1000, set to 0 to disable pooling) */
|
|
66
66
|
poolSize?: number;
|
|
67
|
-
/** Whether to enable HTMLString caching (default: true) */
|
|
67
|
+
/** Whether to enable HTMLString caching for deduplicating repeated HTML fragments (default: true) */
|
|
68
68
|
contentCache?: boolean;
|
|
69
69
|
};
|
|
70
70
|
assetsPrefix?: AssetsPrefix;
|
|
@@ -106,6 +106,7 @@ export type SSRManifest = {
|
|
|
106
106
|
checkOrigin: boolean;
|
|
107
107
|
allowedDomains?: Partial<RemotePattern>[];
|
|
108
108
|
actionBodySizeLimit: number;
|
|
109
|
+
serverIslandBodySizeLimit: number;
|
|
109
110
|
sessionConfig?: SSRManifestSession;
|
|
110
111
|
cacheConfig?: SSRManifestCache;
|
|
111
112
|
cacheDir: URL;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type RemotePattern } from '@astrojs/internal-helpers/remote';
|
|
2
2
|
/**
|
|
3
3
|
* Validate a host against allowedDomains.
|
|
4
|
-
* Returns the host only if it matches an allowed pattern
|
|
4
|
+
* Returns the host only if it matches an allowed pattern; otherwise, undefined.
|
|
5
5
|
* This prevents SSRF attacks by ensuring the Host header is trusted.
|
|
6
6
|
*/
|
|
7
7
|
export declare function validateHost(host: string | undefined, protocol: string, allowedDomains?: Partial<RemotePattern>[]): string | undefined;
|
|
@@ -50,8 +50,6 @@ function validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPor
|
|
|
50
50
|
} else if (/^https?$/.test(forwardedProtocol)) {
|
|
51
51
|
result.protocol = forwardedProtocol;
|
|
52
52
|
}
|
|
53
|
-
} else if (/^https?$/.test(forwardedProtocol)) {
|
|
54
|
-
result.protocol = forwardedProtocol;
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
55
|
if (forwardedPort && allowedDomains && allowedDomains.length > 0) {
|
|
@@ -130,7 +130,7 @@ export declare abstract class Pipeline {
|
|
|
130
130
|
getServerIslands(): Promise<ServerIslandMappings>;
|
|
131
131
|
getAction(path: string): Promise<ActionClient<unknown, ActionAccept, $ZodType>>;
|
|
132
132
|
getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule>;
|
|
133
|
-
createNodePool(poolSize: number,
|
|
133
|
+
createNodePool(poolSize: number, stats: boolean): NodePool;
|
|
134
134
|
createStringCache(): HTMLStringCache;
|
|
135
135
|
}
|
|
136
136
|
export interface HeadElements extends Pick<SSRResult, 'scripts' | 'styles' | 'links'> {
|
|
@@ -41,7 +41,6 @@ class Pipeline {
|
|
|
41
41
|
if (manifest.experimentalQueuedRendering.enabled) {
|
|
42
42
|
this.nodePool = this.createNodePool(
|
|
43
43
|
manifest.experimentalQueuedRendering.poolSize ?? 1e3,
|
|
44
|
-
manifest.experimentalQueuedRendering.contentCache ?? false,
|
|
45
44
|
false
|
|
46
45
|
);
|
|
47
46
|
if (manifest.experimentalQueuedRendering.contentCache) {
|
|
@@ -172,8 +171,8 @@ class Pipeline {
|
|
|
172
171
|
);
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
|
-
createNodePool(poolSize,
|
|
176
|
-
return new NodePool(poolSize,
|
|
174
|
+
createNodePool(poolSize, stats) {
|
|
175
|
+
return new NodePool(poolSize, stats);
|
|
177
176
|
}
|
|
178
177
|
createStringCache() {
|
|
179
178
|
return new HTMLStringCache(1e3);
|
|
@@ -4,7 +4,7 @@ import type { RouteData } from '../../types/public/internal.js';
|
|
|
4
4
|
export declare function getOutFolder(astroSettings: AstroSettings, pathname: string, routeData: RouteData): URL;
|
|
5
5
|
export declare function getOutFile(buildFormat: NonNullable<AstroConfig['build']>['format'], outFolder: URL, pathname: string, routeData: RouteData): URL;
|
|
6
6
|
/**
|
|
7
|
-
* Ensures the `outDir` is within `process.cwd()`. If not it will
|
|
7
|
+
* Ensures the `outDir` is within `process.cwd()`. If not it will fall back to `<cwd>/.astro`.
|
|
8
8
|
* This is used for static `ssrBuild` so the output can access node_modules when we import
|
|
9
9
|
* the output files. A hardcoded fallback dir is fine as it would be cleaned up after build.
|
|
10
10
|
*/
|
|
@@ -37,7 +37,9 @@ class BuildPipeline extends Pipeline {
|
|
|
37
37
|
this.defaultRoutes = defaultRoutes;
|
|
38
38
|
if (queueRenderingEnabled(this.manifest.experimentalQueuedRendering)) {
|
|
39
39
|
this.nodePool = newNodePool(this.manifest.experimentalQueuedRendering);
|
|
40
|
-
this.
|
|
40
|
+
if (this.manifest.experimentalQueuedRendering.contentCache) {
|
|
41
|
+
this.htmlStringCache = new HTMLStringCache(1e3);
|
|
42
|
+
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
internals;
|