astro 4.8.4 → 4.8.5

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.
@@ -4,11 +4,13 @@ import { formContentTypes, getAction, hasContentType } from "./utils.js";
4
4
  import { callSafely } from "./virtual/shared.js";
5
5
  const onRequest = defineMiddleware(async (context, next) => {
6
6
  const locals = context.locals;
7
+ if (locals._actionsInternal) return next();
7
8
  const { request, url } = context;
8
9
  const contentType = request.headers.get("Content-Type");
9
10
  if (url.pathname.startsWith("/_actions")) return nextWithLocalsStub(next, locals);
10
- if (!contentType || !hasContentType(contentType, formContentTypes))
11
+ if (!contentType || !hasContentType(contentType, formContentTypes)) {
11
12
  return nextWithLocalsStub(next, locals);
13
+ }
12
14
  const formData = await request.clone().formData();
13
15
  const actionPath = formData.get("_astroAction");
14
16
  if (typeof actionPath !== "string") return nextWithLocalsStub(next, locals);
@@ -1,21 +1,28 @@
1
1
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
2
2
  import { lookup as probe } from "../utils/vendor/image-size/lookup.js";
3
3
  async function imageMetadata(data, src) {
4
- const result = probe(data);
5
- if (!result.height || !result.width || !result.type) {
4
+ try {
5
+ const result = probe(data);
6
+ if (!result.height || !result.width || !result.type) {
7
+ throw new AstroError({
8
+ ...AstroErrorData.NoImageMetadata,
9
+ message: AstroErrorData.NoImageMetadata.message(src)
10
+ });
11
+ }
12
+ const { width, height, type, orientation } = result;
13
+ const isPortrait = (orientation || 0) >= 5;
14
+ return {
15
+ width: isPortrait ? height : width,
16
+ height: isPortrait ? width : height,
17
+ format: type,
18
+ orientation
19
+ };
20
+ } catch (e) {
6
21
  throw new AstroError({
7
22
  ...AstroErrorData.NoImageMetadata,
8
23
  message: AstroErrorData.NoImageMetadata.message(src)
9
24
  });
10
25
  }
11
- const { width, height, type, orientation } = result;
12
- const isPortrait = (orientation || 0) >= 5;
13
- return {
14
- width: isPortrait ? height : width,
15
- height: isPortrait ? width : height,
16
- format: type,
17
- orientation
18
- };
19
26
  }
20
27
  export {
21
28
  imageMetadata
@@ -186,6 +186,14 @@ class App {
186
186
  this.#logRenderOptionsDeprecationWarning();
187
187
  }
188
188
  }
189
+ if (routeData) {
190
+ this.#logger.debug(
191
+ "router",
192
+ "The adapter " + this.#manifest.adapterName + " provided a custom RouteData for ",
193
+ request.url
194
+ );
195
+ this.#logger.debug("router", "RouteData:\n" + routeData);
196
+ }
189
197
  if (locals) {
190
198
  if (typeof locals !== "object") {
191
199
  this.#logger.error(null, new AstroError(AstroErrorData.LocalsNotAnObject).stack);
@@ -201,8 +209,12 @@ class App {
201
209
  }
202
210
  if (!routeData) {
203
211
  routeData = this.match(request);
212
+ this.#logger.debug("router", "Astro matched the following route for " + request.url);
213
+ this.#logger.debug("router", "RouteData:\n" + routeData);
204
214
  }
205
215
  if (!routeData) {
216
+ this.#logger.debug("router", "Astro hasn't found routes that match " + request.url);
217
+ this.#logger.debug("router", "Here's the available routes:\n", this.#manifestData);
206
218
  return this.#renderError(request, { locals, status: 404 });
207
219
  }
208
220
  const pathname = this.#getPathnameFromRequest(request);
@@ -7,6 +7,6 @@ export declare class AppPipeline extends Pipeline {
7
7
  headElements(routeData: RouteData): Pick<SSRResult, 'scripts' | 'styles' | 'links'>;
8
8
  componentMetadata(): void;
9
9
  getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
10
- tryRewrite(payload: RewritePayload): Promise<[RouteData, ComponentInstance]>;
10
+ tryRewrite(payload: RewritePayload, request: Request): Promise<[RouteData, ComponentInstance]>;
11
11
  getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule>;
12
12
  }
@@ -50,7 +50,7 @@ class AppPipeline extends Pipeline {
50
50
  const module = await this.getModuleForRoute(routeData);
51
51
  return module.page();
52
52
  }
53
- async tryRewrite(payload) {
53
+ async tryRewrite(payload, request) {
54
54
  let foundRoute;
55
55
  for (const route of this.#manifestData.routes) {
56
56
  if (payload instanceof URL) {
@@ -64,9 +64,12 @@ class AppPipeline extends Pipeline {
64
64
  foundRoute = route;
65
65
  break;
66
66
  }
67
- } else if (route.pattern.test(decodeURI(payload))) {
68
- foundRoute = route;
69
- break;
67
+ } else {
68
+ const newUrl = new URL(payload, new URL(request.url).origin);
69
+ if (route.pattern.test(decodeURI(newUrl.pathname))) {
70
+ foundRoute = route;
71
+ break;
72
+ }
70
73
  }
71
74
  }
72
75
  if (foundRoute) {
@@ -64,7 +64,7 @@ export declare abstract class Pipeline {
64
64
  *
65
65
  * @param {RewritePayload} rewritePayload
66
66
  */
67
- abstract tryRewrite(rewritePayload: RewritePayload): Promise<[RouteData, ComponentInstance]>;
67
+ abstract tryRewrite(rewritePayload: RewritePayload, request: Request): Promise<[RouteData, ComponentInstance]>;
68
68
  /**
69
69
  * Tells the pipeline how to retrieve a component give a `RouteData`
70
70
  * @param routeData
@@ -38,6 +38,6 @@ export declare class BuildPipeline extends Pipeline {
38
38
  */
39
39
  retrieveRoutesToGenerate(): Map<PageBuildData, string>;
40
40
  getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
41
- tryRewrite(payload: RewritePayload): Promise<[RouteData, ComponentInstance]>;
41
+ tryRewrite(payload: RewritePayload, request: Request): Promise<[RouteData, ComponentInstance]>;
42
42
  retrieveSsrEntry(route: RouteData, filePath: string): Promise<SinglePageBuiltModule>;
43
43
  }
@@ -206,7 +206,7 @@ class BuildPipeline extends Pipeline {
206
206
  return module.page();
207
207
  }
208
208
  }
209
- async tryRewrite(payload) {
209
+ async tryRewrite(payload, request) {
210
210
  let foundRoute;
211
211
  for (const route of this.options.manifest.routes) {
212
212
  if (payload instanceof URL) {
@@ -220,9 +220,12 @@ class BuildPipeline extends Pipeline {
220
220
  foundRoute = route;
221
221
  break;
222
222
  }
223
- } else if (route.pattern.test(decodeURI(payload))) {
224
- foundRoute = route;
225
- break;
223
+ } else {
224
+ const newUrl = new URL(payload, new URL(request.url).origin);
225
+ if (route.pattern.test(decodeURI(newUrl.pathname))) {
226
+ foundRoute = route;
227
+ break;
228
+ }
226
229
  }
227
230
  }
228
231
  if (foundRoute) {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.8.4";
1
+ const ASTRO_VERSION = "4.8.5";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404";
@@ -19,7 +19,7 @@ async function dev(inlineConfig) {
19
19
  await telemetry.record([]);
20
20
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
21
21
  const logger = restart.container.logger;
22
- const currentVersion = "4.8.4";
22
+ const currentVersion = "4.8.5";
23
23
  const isPrerelease = currentVersion.includes("-");
24
24
  if (!isPrerelease) {
25
25
  try {
@@ -37,7 +37,7 @@ function serverStart({
37
37
  host,
38
38
  base
39
39
  }) {
40
- const version = "4.8.4";
40
+ const version = "4.8.5";
41
41
  const localPrefix = `${dim("\u2503")} Local `;
42
42
  const networkPrefix = `${dim("\u2503")} Network `;
43
43
  const emptyPrefix = " ".repeat(11);
@@ -269,7 +269,7 @@ function printHelp({
269
269
  message.push(
270
270
  linebreak(),
271
271
  ` ${bgGreen(black(` ${commandName} `))} ${green(
272
- `v${"4.8.4"}`
272
+ `v${"4.8.5"}`
273
273
  )} ${headline}`
274
274
  );
275
275
  }
@@ -95,7 +95,7 @@ class RenderContext {
95
95
  if (payload) {
96
96
  if (this.pipeline.manifest.rewritingEnabled) {
97
97
  try {
98
- const [routeData, component] = await pipeline.tryRewrite(payload);
98
+ const [routeData, component] = await pipeline.tryRewrite(payload, this.request);
99
99
  this.routeData = routeData;
100
100
  componentInstance = component;
101
101
  } catch (e) {
@@ -173,7 +173,7 @@ class RenderContext {
173
173
  const rewrite = async (reroutePayload) => {
174
174
  pipeline.logger.debug("router", "Called rewriting to:", reroutePayload);
175
175
  try {
176
- const [routeData, component] = await pipeline.tryRewrite(reroutePayload);
176
+ const [routeData, component] = await pipeline.tryRewrite(reroutePayload, this.request);
177
177
  this.routeData = routeData;
178
178
  if (reroutePayload instanceof Request) {
179
179
  this.request = reroutePayload;
@@ -328,7 +328,7 @@ class RenderContext {
328
328
  const rewrite = async (reroutePayload) => {
329
329
  try {
330
330
  pipeline.logger.debug("router", "Calling rewrite: ", reroutePayload);
331
- const [routeData, component] = await pipeline.tryRewrite(reroutePayload);
331
+ const [routeData, component] = await pipeline.tryRewrite(reroutePayload, this.request);
332
332
  this.routeData = routeData;
333
333
  if (reroutePayload instanceof Request) {
334
334
  this.request = reroutePayload;
@@ -1,2 +1,2 @@
1
1
  import type { AstroConfig, RoutePart } from '../../../@types/astro.js';
2
- export declare function getRouteGenerator(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']): (params: object) => string;
2
+ export declare function getRouteGenerator(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']): (params: Record<string, string | number | undefined>) => string;
@@ -1,4 +1,14 @@
1
1
  import { compile } from "path-to-regexp";
2
+ function sanitizeParams(params) {
3
+ return Object.fromEntries(
4
+ Object.entries(params).map(([key, value]) => {
5
+ if (typeof value === "string") {
6
+ return [key, value.normalize().replace(/#/g, "%23").replace(/\?/g, "%3F")];
7
+ }
8
+ return [key, value];
9
+ })
10
+ );
11
+ }
2
12
  function getRouteGenerator(segments, addTrailingSlash) {
3
13
  const template = segments.map((segment) => {
4
14
  return "/" + segment.map((part) => {
@@ -17,7 +27,8 @@ function getRouteGenerator(segments, addTrailingSlash) {
17
27
  }
18
28
  const toPath = compile(template + trailing);
19
29
  return (params) => {
20
- const path = toPath(params);
30
+ const sanitizedParams = sanitizeParams(params);
31
+ const path = toPath(sanitizedParams);
21
32
  return path || "/";
22
33
  };
23
34
  }
@@ -1,6 +1,7 @@
1
1
  import type { APIContext, AstroConfig, Locales, SSRManifest, ValidRedirectStatus } from '../@types/astro.js';
2
2
  import type { RoutingStrategies } from './utils.js';
3
3
  export declare function requestHasLocale(locales: Locales): (context: APIContext) => boolean;
4
+ export declare function requestIs404Or500(request: Request, base?: string): boolean;
4
5
  export declare function pathHasLocale(path: string, locales: Locales): boolean;
5
6
  type GetLocaleRelativeUrl = GetLocaleOptions & {
6
7
  locale: string;
@@ -9,6 +9,10 @@ function requestHasLocale(locales) {
9
9
  return pathHasLocale(context.url.pathname, locales);
10
10
  };
11
11
  }
12
+ function requestIs404Or500(request, base = "") {
13
+ const url = new URL(request.url);
14
+ return url.pathname.startsWith(`${base}/404`) || url.pathname.startsWith(`${base}/500`);
15
+ }
12
16
  function pathHasLocale(path, locales) {
13
17
  const segments = path.split("/");
14
18
  for (const segment of segments) {
@@ -188,7 +192,7 @@ function notFound({ base, locales }) {
188
192
  if (!(isRoot || pathHasLocale(url.pathname, locales))) {
189
193
  if (response) {
190
194
  response.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
191
- return new Response(null, {
195
+ return new Response(response.body, {
192
196
  status: 404,
193
197
  headers: response.headers
194
198
  });
@@ -263,6 +267,7 @@ export {
263
267
  redirectToDefaultLocale,
264
268
  redirectToFallback,
265
269
  requestHasLocale,
270
+ requestIs404Or500,
266
271
  toCodes,
267
272
  toPaths
268
273
  };
@@ -4,7 +4,8 @@ import {
4
4
  notFound,
5
5
  redirectToDefaultLocale,
6
6
  redirectToFallback,
7
- requestHasLocale
7
+ requestHasLocale,
8
+ requestIs404Or500
8
9
  } from "./index.js";
9
10
  function createI18nMiddleware(i18n, base, trailingSlash, format) {
10
11
  if (!i18n) return (_, next) => next();
@@ -50,6 +51,9 @@ function createI18nMiddleware(i18n, base, trailingSlash, format) {
50
51
  if (type !== "page" && type !== "fallback") {
51
52
  return response;
52
53
  }
54
+ if (requestIs404Or500(context.request, base)) {
55
+ return response;
56
+ }
53
57
  const { currentLocale } = context;
54
58
  switch (i18n.strategy) {
55
59
  case "manual": {
@@ -128,12 +128,18 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
128
128
  await bufferHeadContent(result);
129
129
  }
130
130
  let error = null;
131
- let next = promiseWithResolvers();
131
+ let next = null;
132
132
  const buffer = [];
133
+ let renderingComplete = false;
133
134
  const iterator = {
134
135
  async next() {
135
136
  if (result.cancelled) return { done: true, value: void 0 };
136
- await next.promise;
137
+ if (next !== null) {
138
+ await next.promise;
139
+ }
140
+ if (!renderingComplete) {
141
+ next = promiseWithResolvers();
142
+ }
137
143
  if (error) {
138
144
  throw error;
139
145
  }
@@ -176,17 +182,18 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
176
182
  const bytes = chunkToByteArray(result, chunk);
177
183
  if (bytes.length > 0) {
178
184
  buffer.push(bytes);
179
- next.resolve();
180
- next = promiseWithResolvers();
185
+ next?.resolve();
181
186
  }
182
187
  }
183
188
  };
184
189
  const renderPromise = templateResult.render(destination);
185
190
  renderPromise.then(() => {
186
- next.resolve();
191
+ renderingComplete = true;
192
+ next?.resolve();
187
193
  }).catch((err) => {
188
194
  error = err;
189
- next.resolve();
195
+ renderingComplete = true;
196
+ next?.resolve();
190
197
  });
191
198
  return {
192
199
  [Symbol.asyncIterator]() {
@@ -19,6 +19,6 @@ export declare class DevPipeline extends Pipeline {
19
19
  preload(routeData: RouteData, filePath: URL): Promise<ComponentInstance>;
20
20
  clearRouteCache(): void;
21
21
  getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
22
- tryRewrite(payload: RewritePayload): Promise<[RouteData, ComponentInstance]>;
22
+ tryRewrite(payload: RewritePayload, request: Request): Promise<[RouteData, ComponentInstance]>;
23
23
  setManifestData(manifestData: ManifestData): void;
24
24
  }
@@ -132,7 +132,7 @@ class DevPipeline extends Pipeline {
132
132
  return await this.preload(routeData, filePath);
133
133
  }
134
134
  }
135
- async tryRewrite(payload) {
135
+ async tryRewrite(payload, request) {
136
136
  let foundRoute;
137
137
  if (!this.manifestData) {
138
138
  throw new Error("Missing manifest data. This is an internal error, please file an issue.");
@@ -149,9 +149,12 @@ class DevPipeline extends Pipeline {
149
149
  foundRoute = route;
150
150
  break;
151
151
  }
152
- } else if (route.pattern.test(decodeURI(payload))) {
153
- foundRoute = route;
154
- break;
152
+ } else {
153
+ const newUrl = new URL(payload, new URL(request.url).origin);
154
+ if (route.pattern.test(decodeURI(newUrl.pathname))) {
155
+ foundRoute = route;
156
+ break;
157
+ }
155
158
  }
156
159
  }
157
160
  if (foundRoute) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.8.4",
3
+ "version": "4.8.5",
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",