astro 6.4.2 → 6.4.4
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/assets/fonts/core/optimize-fallbacks.js +38 -13
- package/dist/assets/fonts/definitions.d.ts +2 -2
- package/dist/assets/fonts/infra/system-fallbacks-provider.d.ts +2 -2
- package/dist/assets/fonts/infra/system-fallbacks-provider.js +46 -9
- package/dist/assets/fonts/types.d.ts +1 -0
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/content/content-layer.js +3 -3
- package/dist/core/app/base.d.ts +9 -0
- package/dist/core/app/base.js +29 -61
- package/dist/core/base-pipeline.d.ts +3 -0
- package/dist/core/base-pipeline.js +2 -0
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/fetch/fetch-state.js +41 -13
- package/dist/core/i18n/domain.d.ts +12 -0
- package/dist/core/i18n/domain.js +66 -0
- package/dist/core/i18n/handler.js +3 -0
- package/dist/core/logger/core.d.ts +1 -1
- package/dist/core/logger/core.js +1 -1
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/astro-middleware.js +0 -4
- package/dist/core/routing/create-manifest.js +1 -1
- package/dist/core/routing/handler.js +2 -1
- package/dist/core/routing/parse-route.js +1 -1
- package/dist/core/routing/rewrite.js +1 -1
- package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +9 -0
- package/dist/runtime/server/render/head.js +1 -5
- package/dist/runtime/server/render/util.js +1 -1
- package/dist/virtual-modules/i18n.d.ts +2 -2
- package/dist/vite-plugin-hmr-reload/index.js +3 -0
- package/dist/vite-plugin-utils/index.js +7 -1
- package/package.json +2 -2
|
@@ -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
|
|
18
|
-
|
|
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 (
|
|
45
|
+
if (uniqueLocalFonts.includes(family.name)) {
|
|
22
46
|
return null;
|
|
23
47
|
}
|
|
24
|
-
const
|
|
25
|
-
font,
|
|
48
|
+
const nameForFont = (font) => (
|
|
26
49
|
// We mustn't wrap in quote because that's handled by the CSS renderer
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
fallbacks = [...
|
|
50
|
+
`${family.uniqueName} fallback: ${font}`
|
|
51
|
+
);
|
|
52
|
+
fallbacks = [...uniqueLocalFonts.map(nameForFont), ...fallbacks];
|
|
30
53
|
let css = "";
|
|
31
|
-
for (const {
|
|
32
|
-
|
|
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
|
|
59
|
+
metrics,
|
|
35
60
|
fallbackMetrics: systemFallbacksProvider.getMetricsForLocalFont(font),
|
|
36
61
|
font,
|
|
37
|
-
name,
|
|
38
|
-
properties
|
|
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:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -197,7 +197,7 @@ ${contentConfig.error.message}`
|
|
|
197
197
|
logger.info("Content config changed");
|
|
198
198
|
shouldClear = true;
|
|
199
199
|
}
|
|
200
|
-
if (previousAstroVersion && previousAstroVersion !== "6.4.
|
|
200
|
+
if (previousAstroVersion && previousAstroVersion !== "6.4.4") {
|
|
201
201
|
logger.info("Astro version changed");
|
|
202
202
|
shouldClear = true;
|
|
203
203
|
}
|
|
@@ -205,8 +205,8 @@ ${contentConfig.error.message}`
|
|
|
205
205
|
logger.info("Clearing content store");
|
|
206
206
|
this.#store.clearAll();
|
|
207
207
|
}
|
|
208
|
-
if ("6.4.
|
|
209
|
-
this.#store.metaStore().set("astro-version", "6.4.
|
|
208
|
+
if ("6.4.4") {
|
|
209
|
+
this.#store.metaStore().set("astro-version", "6.4.4");
|
|
210
210
|
}
|
|
211
211
|
if (currentConfigDigest) {
|
|
212
212
|
this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
package/dist/core/app/base.d.ts
CHANGED
|
@@ -131,6 +131,15 @@ export declare abstract class BaseApp<P extends Pipeline = AppPipeline> {
|
|
|
131
131
|
abstract createPipeline(streaming: boolean, manifest: SSRManifest, ...args: any[]): P;
|
|
132
132
|
set setManifestData(newManifestData: RoutesList);
|
|
133
133
|
removeBase(pathname: string): string;
|
|
134
|
+
/**
|
|
135
|
+
* Decodes a pathname with `decodeURI`, falling back to the raw pathname when it
|
|
136
|
+
* contains an invalid percent-sequence (e.g. `%C0%AF`, an overlong-UTF-8 encoding of
|
|
137
|
+
* `/` commonly sent by path-traversal scanners). A raw `decodeURI()` would throw
|
|
138
|
+
* `URIError: URI malformed`, and because `match()` runs before `render()` that error
|
|
139
|
+
* escapes the adapter's request handler as an uncaught exception (HTTP 500) that user
|
|
140
|
+
* middleware can't catch.
|
|
141
|
+
*/
|
|
142
|
+
private safeDecodeURI;
|
|
134
143
|
/**
|
|
135
144
|
* Extracts the base-stripped, decoded pathname from a request.
|
|
136
145
|
* Used by adapters to compute the pathname for dev-mode route matching.
|
package/dist/core/app/base.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
appendForwardSlash,
|
|
3
2
|
collapseDuplicateLeadingSlashes,
|
|
4
|
-
joinPaths,
|
|
5
3
|
prependForwardSlash,
|
|
6
4
|
removeTrailingForwardSlash
|
|
7
5
|
} from "@astrojs/internal-helpers/path";
|
|
8
6
|
import { matchPattern } from "@astrojs/internal-helpers/remote";
|
|
9
|
-
import {
|
|
7
|
+
import { computePathnameFromDomain } from "../i18n/domain.js";
|
|
10
8
|
import { PipelineFeatures } from "../base-pipeline.js";
|
|
11
9
|
import { ASTRO_ERROR_HEADER, clientAddressSymbol } from "../constants.js";
|
|
12
10
|
import { getSetCookiesFromResponse } from "../cookies/index.js";
|
|
@@ -117,19 +115,30 @@ class BaseApp {
|
|
|
117
115
|
return pathname;
|
|
118
116
|
}
|
|
119
117
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
118
|
+
* Decodes a pathname with `decodeURI`, falling back to the raw pathname when it
|
|
119
|
+
* contains an invalid percent-sequence (e.g. `%C0%AF`, an overlong-UTF-8 encoding of
|
|
120
|
+
* `/` commonly sent by path-traversal scanners). A raw `decodeURI()` would throw
|
|
121
|
+
* `URIError: URI malformed`, and because `match()` runs before `render()` that error
|
|
122
|
+
* escapes the adapter's request handler as an uncaught exception (HTTP 500) that user
|
|
123
|
+
* middleware can't catch.
|
|
122
124
|
*/
|
|
123
|
-
|
|
124
|
-
const url = new URL(request.url);
|
|
125
|
-
const pathname = prependForwardSlash(this.removeBase(url.pathname));
|
|
125
|
+
safeDecodeURI(pathname) {
|
|
126
126
|
try {
|
|
127
127
|
return decodeURI(pathname);
|
|
128
128
|
} catch (e) {
|
|
129
|
-
this.adapterLogger.
|
|
129
|
+
this.adapterLogger.debug(e.toString());
|
|
130
130
|
return pathname;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Extracts the base-stripped, decoded pathname from a request.
|
|
135
|
+
* Used by adapters to compute the pathname for dev-mode route matching.
|
|
136
|
+
*/
|
|
137
|
+
getPathnameFromRequest(request) {
|
|
138
|
+
const url = new URL(request.url);
|
|
139
|
+
const pathname = prependForwardSlash(this.removeBase(url.pathname));
|
|
140
|
+
return this.safeDecodeURI(pathname);
|
|
141
|
+
}
|
|
133
142
|
/**
|
|
134
143
|
* Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
|
|
135
144
|
* routes aren't returned, even if they are matched.
|
|
@@ -145,14 +154,14 @@ class BaseApp {
|
|
|
145
154
|
if (!pathname) {
|
|
146
155
|
pathname = prependForwardSlash(this.removeBase(url.pathname));
|
|
147
156
|
}
|
|
148
|
-
const routeData = this.pipeline.matchRoute(
|
|
157
|
+
const routeData = this.pipeline.matchRoute(this.safeDecodeURI(pathname));
|
|
149
158
|
if (!routeData) return void 0;
|
|
150
159
|
if (allowPrerenderedRoutes) {
|
|
151
160
|
return routeData;
|
|
152
161
|
}
|
|
153
162
|
if (routeData.prerender) {
|
|
154
163
|
if (routeData.params.length > 0) {
|
|
155
|
-
const allMatches = this.pipeline.matchAllRoutes(
|
|
164
|
+
const allMatches = this.pipeline.matchAllRoutes(this.safeDecodeURI(pathname));
|
|
156
165
|
return allMatches.find((r) => !r.prerender);
|
|
157
166
|
}
|
|
158
167
|
return void 0;
|
|
@@ -170,55 +179,14 @@ class BaseApp {
|
|
|
170
179
|
return void 0;
|
|
171
180
|
}
|
|
172
181
|
computePathnameFromDomain(request) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
protocol = url.protocol;
|
|
182
|
-
}
|
|
183
|
-
if (!host) {
|
|
184
|
-
host = request.headers.get("Host");
|
|
185
|
-
}
|
|
186
|
-
if (host && protocol) {
|
|
187
|
-
host = host.split(":")[0];
|
|
188
|
-
try {
|
|
189
|
-
let locale;
|
|
190
|
-
const hostAsUrl = new URL(`${protocol}//${host}`);
|
|
191
|
-
for (const [domainKey, localeValue] of Object.entries(
|
|
192
|
-
this.manifest.i18n.domainLookupTable
|
|
193
|
-
)) {
|
|
194
|
-
const domainKeyAsUrl = new URL(domainKey);
|
|
195
|
-
if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
|
|
196
|
-
locale = localeValue;
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (locale) {
|
|
201
|
-
pathname = prependForwardSlash(
|
|
202
|
-
joinPaths(normalizeTheLocale(locale), this.removeBase(url.pathname))
|
|
203
|
-
);
|
|
204
|
-
if (this.manifest.trailingSlash === "always") {
|
|
205
|
-
pathname = appendForwardSlash(pathname);
|
|
206
|
-
} else if (this.manifest.trailingSlash === "never") {
|
|
207
|
-
pathname = removeTrailingForwardSlash(pathname);
|
|
208
|
-
} else if (url.pathname.endsWith("/")) {
|
|
209
|
-
pathname = appendForwardSlash(pathname);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
} catch (e) {
|
|
213
|
-
this.logger.error(
|
|
214
|
-
"router",
|
|
215
|
-
`Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
|
|
216
|
-
);
|
|
217
|
-
this.logger.error("router", `Error: ${e}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return pathname;
|
|
182
|
+
return computePathnameFromDomain(
|
|
183
|
+
request,
|
|
184
|
+
new URL(request.url),
|
|
185
|
+
this.manifest.i18n,
|
|
186
|
+
this.manifest.base,
|
|
187
|
+
this.manifest.trailingSlash,
|
|
188
|
+
this.logger
|
|
189
|
+
);
|
|
222
190
|
}
|
|
223
191
|
async render(request, {
|
|
224
192
|
addCookieHeader = false,
|
|
@@ -259,7 +227,7 @@ class BaseApp {
|
|
|
259
227
|
if (!routeData) {
|
|
260
228
|
const domainPathname = this.computePathnameFromDomain(request);
|
|
261
229
|
if (domainPathname) {
|
|
262
|
-
routeData = this.pipeline.matchRoute(
|
|
230
|
+
routeData = this.pipeline.matchRoute(this.safeDecodeURI(domainPathname));
|
|
263
231
|
}
|
|
264
232
|
}
|
|
265
233
|
const resolvedOptions = {
|
|
@@ -29,6 +29,9 @@ export declare const PipelineFeatures: {
|
|
|
29
29
|
readonly i18n: number;
|
|
30
30
|
readonly cache: number;
|
|
31
31
|
};
|
|
32
|
+
/** All feature bits ORed together. Keep next to `PipelineFeatures` so
|
|
33
|
+
* new flags are hard to forget. */
|
|
34
|
+
export declare const ALL_PIPELINE_FEATURES: number;
|
|
32
35
|
/**
|
|
33
36
|
* The `Pipeline` represents the static parts of rendering that do not change between requests.
|
|
34
37
|
* These are mostly known when the server first starts up and do not change.
|
|
@@ -21,6 +21,7 @@ const PipelineFeatures = {
|
|
|
21
21
|
i18n: 1 << 4,
|
|
22
22
|
cache: 1 << 5
|
|
23
23
|
};
|
|
24
|
+
const ALL_PIPELINE_FEATURES = PipelineFeatures.redirects | PipelineFeatures.sessions | PipelineFeatures.actions | PipelineFeatures.middleware | PipelineFeatures.i18n | PipelineFeatures.cache;
|
|
24
25
|
class Pipeline {
|
|
25
26
|
internalMiddleware;
|
|
26
27
|
resolvedMiddleware = void 0;
|
|
@@ -295,6 +296,7 @@ class Pipeline {
|
|
|
295
296
|
}
|
|
296
297
|
}
|
|
297
298
|
export {
|
|
299
|
+
ALL_PIPELINE_FEATURES,
|
|
298
300
|
Pipeline,
|
|
299
301
|
PipelineFeatures
|
|
300
302
|
};
|
package/dist/core/constants.js
CHANGED
package/dist/core/dev/dev.js
CHANGED
|
@@ -37,7 +37,7 @@ async function dev(inlineConfig) {
|
|
|
37
37
|
await telemetry.record([]);
|
|
38
38
|
const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
|
|
39
39
|
const logger = restart.container.logger;
|
|
40
|
-
const currentVersion = "6.4.
|
|
40
|
+
const currentVersion = "6.4.4";
|
|
41
41
|
const isPrerelease = currentVersion.includes("-");
|
|
42
42
|
if (!isPrerelease) {
|
|
43
43
|
try {
|
|
@@ -10,7 +10,6 @@ import { AstroCookies } from "../cookies/index.js";
|
|
|
10
10
|
import { Slots } from "../render/index.js";
|
|
11
11
|
import {
|
|
12
12
|
ASTRO_GENERATOR,
|
|
13
|
-
DEFAULT_404_COMPONENT,
|
|
14
13
|
fetchStateSymbol,
|
|
15
14
|
originPathnameSymbol,
|
|
16
15
|
pipelineSymbol,
|
|
@@ -30,7 +29,8 @@ import { Rewrites } from "../rewrites/handler.js";
|
|
|
30
29
|
import { isRoute404or500, isRouteServerIsland } from "../routing/match.js";
|
|
31
30
|
import { normalizeUrl } from "../util/normalized-url.js";
|
|
32
31
|
import { getOriginPathname, setOriginPathname } from "../routing/rewrite.js";
|
|
33
|
-
import {
|
|
32
|
+
import { computePathnameFromDomain } from "../i18n/domain.js";
|
|
33
|
+
import { getCustom404Route, routeHasHtmlExtension } from "../routing/helpers.js";
|
|
34
34
|
import { getRenderOptions } from "../app/render-options.js";
|
|
35
35
|
import { getFirstForwardedValue, validateForwardedHeaders } from "../app/validate-headers.js";
|
|
36
36
|
function getFetchStateFromAPIContext(context) {
|
|
@@ -137,6 +137,13 @@ class FetchState {
|
|
|
137
137
|
#rewrites;
|
|
138
138
|
/** Memoized Astro page partial. */
|
|
139
139
|
#astroPagePartial;
|
|
140
|
+
/**
|
|
141
|
+
* Locale-prefixed pathname derived from the Host header for domain-based
|
|
142
|
+
* i18n routing (e.g. `/en/boats/1/foo`), or `undefined` when the request
|
|
143
|
+
* isn't served from a locale-mapped domain. When set, `this.pathname` is
|
|
144
|
+
* derived from it so locale/param resolution match the route pattern.
|
|
145
|
+
*/
|
|
146
|
+
#domainPathname;
|
|
140
147
|
/** Memoized current locale. */
|
|
141
148
|
#currentLocale;
|
|
142
149
|
/** Memoized preferred locale. */
|
|
@@ -159,7 +166,24 @@ class FetchState {
|
|
|
159
166
|
this.componentInstance = void 0;
|
|
160
167
|
this.slots = void 0;
|
|
161
168
|
const url = new URL(request.url);
|
|
162
|
-
|
|
169
|
+
const domainPathname = computePathnameFromDomain(
|
|
170
|
+
request,
|
|
171
|
+
url,
|
|
172
|
+
pipeline.manifest.i18n,
|
|
173
|
+
pipeline.manifest.base,
|
|
174
|
+
pipeline.manifest.trailingSlash,
|
|
175
|
+
pipeline.logger
|
|
176
|
+
);
|
|
177
|
+
if (domainPathname) {
|
|
178
|
+
this.#domainPathname = domainPathname;
|
|
179
|
+
try {
|
|
180
|
+
this.pathname = decodeURI(domainPathname);
|
|
181
|
+
} catch {
|
|
182
|
+
this.pathname = domainPathname;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
this.pathname = this.#computePathname(url);
|
|
186
|
+
}
|
|
163
187
|
this.timeStart = performance.now();
|
|
164
188
|
this.clientAddress = options?.clientAddress;
|
|
165
189
|
this.locals = options?.locals ?? {};
|
|
@@ -422,10 +446,8 @@ class FetchState {
|
|
|
422
446
|
}
|
|
423
447
|
return {
|
|
424
448
|
insertDirective(payload) {
|
|
425
|
-
if (state
|
|
449
|
+
if (state.result) {
|
|
426
450
|
state.result.directives = pushDirective(state.result.directives, payload);
|
|
427
|
-
} else {
|
|
428
|
-
state?.result?.directives.push(payload);
|
|
429
451
|
}
|
|
430
452
|
},
|
|
431
453
|
insertScriptResource(resource) {
|
|
@@ -465,7 +487,9 @@ class FetchState {
|
|
|
465
487
|
}
|
|
466
488
|
} else {
|
|
467
489
|
let pathname = routeData.pathname;
|
|
468
|
-
if (
|
|
490
|
+
if (this.#domainPathname) {
|
|
491
|
+
pathname = this.pathname;
|
|
492
|
+
} else if (url && !routeData.pattern.test(url.pathname)) {
|
|
469
493
|
for (const fallbackRoute of routeData.fallbackRoutes) {
|
|
470
494
|
if (fallbackRoute.pattern.test(url.pathname)) {
|
|
471
495
|
pathname = fallbackRoute.pathname;
|
|
@@ -592,11 +616,14 @@ class FetchState {
|
|
|
592
616
|
*/
|
|
593
617
|
/**
|
|
594
618
|
* Strip `.html` / `/index.html` suffixes from the pathname so the
|
|
595
|
-
* rendering pipeline sees the canonical route path.
|
|
596
|
-
*
|
|
619
|
+
* rendering pipeline sees the canonical route path. Only applies to
|
|
620
|
+
* page routes where `.html` is framework-injected. Endpoint routes
|
|
621
|
+
* preserve `.html` because any such suffix is user-provided (e.g.
|
|
622
|
+
* from `getStaticPaths` params). Skipped when the matched route
|
|
623
|
+
* itself has an `.html` extension in its definition.
|
|
597
624
|
*/
|
|
598
625
|
#stripHtmlExtension() {
|
|
599
|
-
if (this.routeData && !routeHasHtmlExtension(this.routeData)) {
|
|
626
|
+
if (this.routeData && this.routeData.type === "page" && !routeHasHtmlExtension(this.routeData)) {
|
|
600
627
|
this.pathname = this.pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
|
|
601
628
|
}
|
|
602
629
|
}
|
|
@@ -620,9 +647,10 @@ class FetchState {
|
|
|
620
647
|
pipeline.logger.debug("router", "Astro matched the following route for " + this.request.url);
|
|
621
648
|
pipeline.logger.debug("router", "RouteData:\n" + this.routeData);
|
|
622
649
|
if (!this.routeData) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
650
|
+
const custom404 = getCustom404Route(pipeline.manifestData);
|
|
651
|
+
if (custom404 && !custom404.prerender) {
|
|
652
|
+
this.routeData = custom404;
|
|
653
|
+
}
|
|
626
654
|
}
|
|
627
655
|
if (!this.routeData) {
|
|
628
656
|
pipeline.logger.debug("router", "Astro hasn't found routes that match " + this.request.url);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SSRManifest } from '../app/types.js';
|
|
2
|
+
import type { AstroLogger } from '../logger/core.js';
|
|
3
|
+
/**
|
|
4
|
+
* For domain-based i18n routing strategies, derives the locale-prefixed
|
|
5
|
+
* pathname from the request's `Host` header rather than its URL. For example,
|
|
6
|
+
* a request for `/foo` served from `https://example.fr` resolves to `/fr/foo`.
|
|
7
|
+
*
|
|
8
|
+
* Returns `undefined` when the strategy isn't domain-based or the host isn't
|
|
9
|
+
* mapped to a locale — in which case normal pathname routing applies.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export declare function computePathnameFromDomain(request: Request, url: URL, i18n: SSRManifest['i18n'], base: SSRManifest['base'], trailingSlash: SSRManifest['trailingSlash'], logger: AstroLogger): string | undefined;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendForwardSlash,
|
|
3
|
+
collapseDuplicateLeadingSlashes,
|
|
4
|
+
joinPaths,
|
|
5
|
+
prependForwardSlash,
|
|
6
|
+
removeTrailingForwardSlash
|
|
7
|
+
} from "@astrojs/internal-helpers/path";
|
|
8
|
+
import { normalizeTheLocale } from "../../i18n/index.js";
|
|
9
|
+
function computePathnameFromDomain(request, url, i18n, base, trailingSlash, logger) {
|
|
10
|
+
let pathname = void 0;
|
|
11
|
+
if (i18n && (i18n.strategy === "domains-prefix-always" || i18n.strategy === "domains-prefix-other-locales" || i18n.strategy === "domains-prefix-always-no-redirect")) {
|
|
12
|
+
let host = request.headers.get("X-Forwarded-Host");
|
|
13
|
+
let protocol = request.headers.get("X-Forwarded-Proto");
|
|
14
|
+
if (protocol) {
|
|
15
|
+
protocol = protocol + ":";
|
|
16
|
+
} else {
|
|
17
|
+
protocol = url.protocol;
|
|
18
|
+
}
|
|
19
|
+
if (!host) {
|
|
20
|
+
host = request.headers.get("Host");
|
|
21
|
+
}
|
|
22
|
+
if (host && protocol) {
|
|
23
|
+
host = host.split(":")[0];
|
|
24
|
+
try {
|
|
25
|
+
let locale;
|
|
26
|
+
const hostAsUrl = new URL(`${protocol}//${host}`);
|
|
27
|
+
for (const [domainKey, localeValue] of Object.entries(i18n.domainLookupTable)) {
|
|
28
|
+
const domainKeyAsUrl = new URL(domainKey);
|
|
29
|
+
if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
|
|
30
|
+
locale = localeValue;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (locale) {
|
|
35
|
+
pathname = prependForwardSlash(
|
|
36
|
+
joinPaths(normalizeTheLocale(locale), removeBase(url.pathname, base))
|
|
37
|
+
);
|
|
38
|
+
if (trailingSlash === "always") {
|
|
39
|
+
pathname = appendForwardSlash(pathname);
|
|
40
|
+
} else if (trailingSlash === "never") {
|
|
41
|
+
pathname = removeTrailingForwardSlash(pathname);
|
|
42
|
+
} else if (url.pathname.endsWith("/")) {
|
|
43
|
+
pathname = appendForwardSlash(pathname);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
logger.error(
|
|
48
|
+
"router",
|
|
49
|
+
`Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
|
|
50
|
+
);
|
|
51
|
+
logger.error("router", `Error: ${e}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return pathname;
|
|
56
|
+
}
|
|
57
|
+
function removeBase(pathname, base) {
|
|
58
|
+
pathname = collapseDuplicateLeadingSlashes(pathname);
|
|
59
|
+
if (pathname.startsWith(base)) {
|
|
60
|
+
return pathname.slice(removeTrailingForwardSlash(base).length + 1);
|
|
61
|
+
}
|
|
62
|
+
return pathname;
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
computePathnameFromDomain
|
|
66
|
+
};
|
|
@@ -37,6 +37,9 @@ class I18n {
|
|
|
37
37
|
state.pipeline.usedFeatures |= PipelineFeatures.i18n;
|
|
38
38
|
const i18n = this.#i18n;
|
|
39
39
|
const typeHeader = response.headers.get(ROUTE_TYPE_HEADER);
|
|
40
|
+
if (typeHeader) {
|
|
41
|
+
response.headers.delete(ROUTE_TYPE_HEADER);
|
|
42
|
+
}
|
|
40
43
|
const isReroute = response.headers.get(REROUTE_DIRECTIVE_HEADER);
|
|
41
44
|
if (isReroute === "no" && typeof i18n.fallback === "undefined") {
|
|
42
45
|
return response;
|
package/dist/core/logger/core.js
CHANGED
|
@@ -136,7 +136,7 @@ class AstroLogger {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
* It calls the `flush` function of the provided
|
|
139
|
+
* It calls the `flush` function of the provided destination, if it exists.
|
|
140
140
|
*/
|
|
141
141
|
flush() {
|
|
142
142
|
if (this.options.destination.flush) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { PipelineFeatures } from "../base-pipeline.js";
|
|
2
|
-
import { ROUTE_TYPE_HEADER } from "../constants.js";
|
|
3
2
|
import { attachCookiesToResponse } from "../cookies/index.js";
|
|
4
3
|
import { applyRewriteToState } from "../rewrites/handler.js";
|
|
5
4
|
import { callMiddleware } from "./callMiddleware.js";
|
|
@@ -43,9 +42,6 @@ class AstroMiddleware {
|
|
|
43
42
|
return response;
|
|
44
43
|
}
|
|
45
44
|
#finalize(state, response) {
|
|
46
|
-
if (response.headers.get(ROUTE_TYPE_HEADER)) {
|
|
47
|
-
response.headers.delete(ROUTE_TYPE_HEADER);
|
|
48
|
-
}
|
|
49
45
|
attachCookiesToResponse(response, state.cookies);
|
|
50
46
|
return response;
|
|
51
47
|
}
|
|
@@ -677,7 +677,7 @@ function joinSegments(segments) {
|
|
|
677
677
|
const arr = segments.map((segment) => {
|
|
678
678
|
return segment.map((rp) => rp.dynamic ? `[${rp.content}]` : rp.content).join("");
|
|
679
679
|
});
|
|
680
|
-
return `/${arr.join("/")}
|
|
680
|
+
return `/${arr.join("/")}`;
|
|
681
681
|
}
|
|
682
682
|
function replaceOrKeep(original, from, to) {
|
|
683
683
|
if (original.startsWith(`/${to}/`) || original === `/${to}`) return original;
|
|
@@ -12,7 +12,7 @@ import { PagesHandler } from "../pages/handler.js";
|
|
|
12
12
|
import { renderRedirect } from "../redirects/render.js";
|
|
13
13
|
import { provideSession } from "../session/handler.js";
|
|
14
14
|
import { prepareResponse } from "../app/prepare-response.js";
|
|
15
|
-
import { PipelineFeatures } from "../base-pipeline.js";
|
|
15
|
+
import { ALL_PIPELINE_FEATURES, PipelineFeatures } from "../base-pipeline.js";
|
|
16
16
|
class AstroHandler {
|
|
17
17
|
#app;
|
|
18
18
|
#trailingSlashHandler;
|
|
@@ -64,6 +64,7 @@ class AstroHandler {
|
|
|
64
64
|
return this.#pagesHandler.handle(state, ctx);
|
|
65
65
|
}
|
|
66
66
|
async handle(state) {
|
|
67
|
+
state.pipeline.usedFeatures |= ALL_PIPELINE_FEATURES;
|
|
67
68
|
const trailingSlashRedirect = this.#trailingSlashHandler.handle(state);
|
|
68
69
|
if (trailingSlashRedirect) {
|
|
69
70
|
return trailingSlashRedirect;
|
|
@@ -58,7 +58,7 @@ function joinSegments(segments) {
|
|
|
58
58
|
const arr = segments.map((segment) => {
|
|
59
59
|
return segment.map((part) => part.dynamic ? `[${part.content}]` : part.content).join("");
|
|
60
60
|
});
|
|
61
|
-
return `/${arr.join("/")}
|
|
61
|
+
return `/${arr.join("/")}`;
|
|
62
62
|
}
|
|
63
63
|
export {
|
|
64
64
|
parseRoute
|
|
@@ -101,7 +101,7 @@ function copyRequest(newUrl, oldRequest, isPrerendered, logger, routePattern) {
|
|
|
101
101
|
signal: oldRequest.signal,
|
|
102
102
|
keepalive: oldRequest.keepalive,
|
|
103
103
|
// https://fetch.spec.whatwg.org/#dom-request-duplex
|
|
104
|
-
// @ts-expect-error It isn't part of the types, but undici accepts it and it allows
|
|
104
|
+
// @ts-expect-error It isn't part of the types, but undici accepts it and it allows carrying over the body to a new request
|
|
105
105
|
duplex: "half"
|
|
106
106
|
}
|
|
107
107
|
});
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
import { aria, roles } from "aria-query";
|
|
26
26
|
import { AXObjectRoles, elementAXObjects } from "axobject-query";
|
|
27
27
|
const WHITESPACE_REGEX = /\s+/;
|
|
28
|
+
function isHiddenByClosedDetails(element) {
|
|
29
|
+
for (let parent = element.parentElement; parent; parent = parent.parentElement) {
|
|
30
|
+
if (parent.localName !== "details" || parent.open) continue;
|
|
31
|
+
const summary = Array.from(parent.children).find((child) => child.localName === "summary");
|
|
32
|
+
if (!summary?.contains(element)) return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
28
36
|
const a11y_required_attributes = {
|
|
29
37
|
a: ["href"],
|
|
30
38
|
area: ["alt", "aria-label", "aria-labelledby"],
|
|
@@ -321,6 +329,7 @@ const a11y = [
|
|
|
321
329
|
message: "Headings and anchors must have an accessible name, which can come from: inner text, aria-label, aria-labelledby, an img with alt property, or an svg with a tag <title></title>.",
|
|
322
330
|
selector: a11y_required_content.join(","),
|
|
323
331
|
match(element) {
|
|
332
|
+
if (isHiddenByClosedDetails(element)) return false;
|
|
324
333
|
const innerText = element.innerText?.trim();
|
|
325
334
|
if (innerText && innerText !== "") return false;
|
|
326
335
|
const ariaLabel = element.getAttribute("aria-label")?.trim();
|
|
@@ -52,11 +52,7 @@ function renderAllHeadContent(result) {
|
|
|
52
52
|
(link) => renderElement("link", link, false)
|
|
53
53
|
);
|
|
54
54
|
content += styles.join("\n") + links.join("\n") + scripts.join("\n");
|
|
55
|
-
|
|
56
|
-
for (const part of result._metadata.extraHead) {
|
|
57
|
-
content += part;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
55
|
+
content += result._metadata.extraHead.join("");
|
|
60
56
|
return markHTMLString(content);
|
|
61
57
|
}
|
|
62
58
|
function renderHead() {
|
|
@@ -149,7 +149,7 @@ class BufferedRenderer {
|
|
|
149
149
|
function createBufferedRenderer(destination, renderFunction) {
|
|
150
150
|
return new BufferedRenderer(destination, renderFunction);
|
|
151
151
|
}
|
|
152
|
-
const isNode = typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]";
|
|
152
|
+
const isNode = typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]" && !(typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers");
|
|
153
153
|
const isDeno = typeof Deno !== "undefined";
|
|
154
154
|
function promiseWithResolvers() {
|
|
155
155
|
let resolve, reject;
|
|
@@ -207,9 +207,9 @@ export type I18nMiddlewareOptions = {
|
|
|
207
207
|
/**
|
|
208
208
|
* @param {AstroConfig['i18n']['routing']} customOptions
|
|
209
209
|
*
|
|
210
|
-
* A function that allows to programmatically create the Astro i18n middleware.
|
|
210
|
+
* A function that allows you to programmatically create the Astro i18n middleware.
|
|
211
211
|
*
|
|
212
|
-
* This is
|
|
212
|
+
* This is useful when you still want to use the default i18n logic, but add only a few exceptions to your website.
|
|
213
213
|
*
|
|
214
214
|
* ## Examples
|
|
215
215
|
*
|
|
@@ -26,12 +26,18 @@ function normalizeFilename(filename, root) {
|
|
|
26
26
|
} else if (filename.startsWith(".")) {
|
|
27
27
|
const url = new URL(filename, root);
|
|
28
28
|
filename = viteID(url);
|
|
29
|
-
} else if (filename.startsWith("/") && !
|
|
29
|
+
} else if (filename.startsWith("/") && !isPathInRoot(filename, fileURLToPath(root))) {
|
|
30
30
|
const url = new URL("." + filename, root);
|
|
31
31
|
filename = viteID(url);
|
|
32
32
|
}
|
|
33
33
|
return removeLeadingForwardSlashWindows(filename);
|
|
34
34
|
}
|
|
35
|
+
function isPathInRoot(filename, rootPath) {
|
|
36
|
+
if (commonAncestorPath(filename, rootPath)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return commonAncestorPath(filename.toLowerCase(), rootPath.toLowerCase()) !== "";
|
|
40
|
+
}
|
|
35
41
|
const postfixRE = /[?#].*$/s;
|
|
36
42
|
function cleanUrl(url) {
|
|
37
43
|
return url.replace(postfixRE, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro",
|
|
3
|
-
"version": "6.4.
|
|
3
|
+
"version": "6.4.4",
|
|
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",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"clsx": "^2.1.1",
|
|
125
125
|
"common-ancestor-path": "^2.0.0",
|
|
126
126
|
"cookie": "^1.1.1",
|
|
127
|
-
"devalue": "^5.
|
|
127
|
+
"devalue": "^5.8.1",
|
|
128
128
|
"diff": "^8.0.3",
|
|
129
129
|
"dset": "^3.1.4",
|
|
130
130
|
"es-module-lexer": "^2.0.0",
|