astro 5.1.3 → 5.1.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.
@@ -4,7 +4,6 @@ import type { UnresolvedImageTransform } from '../dist/assets/types';
4
4
  import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes.js';
5
5
  import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
6
6
  import type { HTMLAttributes } from '../types';
7
- import './image.css';
8
7
 
9
8
  // The TypeScript diagnostic for JSX props uses the last member of the union to suggest props, so it would be better for
10
9
  // LocalImageProps to be last. Unfortunately, when we do this the error messages that remote images get are complete nonsense
@@ -10,9 +10,8 @@ import type {
10
10
  UnresolvedImageTransform,
11
11
  } from '../dist/types/public/index.js';
12
12
  import type { HTMLAttributes } from '../types';
13
- import './image.css';
14
13
 
15
- type Props = (LocalImageProps | RemoteImageProps) & {
14
+ export type Props = (LocalImageProps | RemoteImageProps) & {
16
15
  formats?: ImageOutputFormat[];
17
16
  fallbackFormat?: ImageOutputFormat;
18
17
  pictureAttributes?: HTMLAttributes<'picture'>;
@@ -0,0 +1,13 @@
1
+ ---
2
+ import type { LocalImageProps, RemoteImageProps } from 'astro:assets';
3
+ import Image from './Image.astro';
4
+
5
+ type Props = LocalImageProps | RemoteImageProps;
6
+
7
+ const { class: className, ...props } = Astro.props;
8
+
9
+ import './image.css';
10
+ ---
11
+
12
+ {/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
13
+ <Image {...props} class={className} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import { default as Picture, type Props as PictureProps } from './Picture.astro';
3
+
4
+ type Props = PictureProps;
5
+
6
+ const { class: className, ...props } = Astro.props;
7
+ ---
8
+
9
+ {/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
10
+
11
+ <Picture {...props} class={className} />
@@ -85,8 +85,10 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
85
85
  const finalFileURL = new URL("." + filepath, env.clientRoot);
86
86
  const finalFolderURL = new URL("./", finalFileURL);
87
87
  await fs.promises.mkdir(finalFolderURL, { recursive: true });
88
- const cacheFile = basename(filepath) + (isLocalImage ? "" : ".json");
88
+ const cacheFile = basename(filepath);
89
89
  const cachedFileURL = new URL(cacheFile, env.assetsCacheDir);
90
+ const cacheMetaFile = cacheFile + ".json";
91
+ const cachedMetaFileURL = new URL(cacheMetaFile, env.assetsCacheDir);
90
92
  try {
91
93
  if (isLocalImage) {
92
94
  await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
@@ -94,15 +96,26 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
94
96
  cached: "hit"
95
97
  };
96
98
  } else {
97
- const JSONData = JSON.parse(readFileSync(cachedFileURL, "utf-8"));
98
- if (!JSONData.data || !JSONData.expires) {
99
- await fs.promises.unlink(cachedFileURL);
99
+ const JSONData = JSON.parse(readFileSync(cachedMetaFileURL, "utf-8"));
100
+ if (!JSONData.expires) {
101
+ try {
102
+ await fs.promises.unlink(cachedFileURL);
103
+ } catch {
104
+ }
105
+ await fs.promises.unlink(cachedMetaFileURL);
100
106
  throw new Error(
101
107
  `Malformed cache entry for ${filepath}, cache will be regenerated for this file.`
102
108
  );
103
109
  }
110
+ if (JSONData.data) {
111
+ const { data, ...meta } = JSONData;
112
+ await Promise.all([
113
+ fs.promises.writeFile(cachedFileURL, Buffer.from(data, "base64")),
114
+ writeCacheMetaFile(cachedMetaFileURL, meta, env)
115
+ ]);
116
+ }
104
117
  if (JSONData.expires > Date.now()) {
105
- await fs.promises.writeFile(finalFileURL, Buffer.from(JSONData.data, "base64"));
118
+ await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
106
119
  return {
107
120
  cached: "hit"
108
121
  };
@@ -116,9 +129,12 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
116
129
  if (revalidatedData.data.length) {
117
130
  originalImage = revalidatedData;
118
131
  } else {
119
- revalidatedData.data = Buffer.from(JSONData.data, "base64");
120
- await writeRemoteCacheFile(cachedFileURL, revalidatedData, env);
121
- await fs.promises.writeFile(finalFileURL, revalidatedData.data);
132
+ await writeCacheMetaFile(cachedMetaFileURL, revalidatedData, env);
133
+ await fs.promises.copyFile(
134
+ cachedFileURL,
135
+ finalFileURL,
136
+ fs.constants.COPYFILE_FICLONE
137
+ );
122
138
  return { cached: "revalidated" };
123
139
  }
124
140
  } catch (e) {
@@ -126,11 +142,12 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
126
142
  null,
127
143
  `An error was encountered while revalidating a cached remote asset. Proceeding with stale cache. ${e}`
128
144
  );
129
- await fs.promises.writeFile(finalFileURL, Buffer.from(JSONData.data, "base64"));
145
+ await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
130
146
  return { cached: "hit" };
131
147
  }
132
148
  }
133
149
  await fs.promises.unlink(cachedFileURL);
150
+ await fs.promises.unlink(cachedMetaFileURL);
134
151
  }
135
152
  } catch (e) {
136
153
  if (e.code !== "ENOENT") {
@@ -169,7 +186,10 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
169
186
  if (isLocalImage) {
170
187
  await fs.promises.writeFile(cachedFileURL, resultData.data);
171
188
  } else {
172
- await writeRemoteCacheFile(cachedFileURL, resultData, env);
189
+ await Promise.all([
190
+ fs.promises.writeFile(cachedFileURL, resultData.data),
191
+ writeCacheMetaFile(cachedMetaFileURL, resultData, env)
192
+ ]);
173
193
  }
174
194
  }
175
195
  } catch (e) {
@@ -190,12 +210,11 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
190
210
  };
191
211
  }
192
212
  }
193
- async function writeRemoteCacheFile(cachedFileURL, resultData, env) {
213
+ async function writeCacheMetaFile(cachedMetaFileURL, resultData, env) {
194
214
  try {
195
215
  return await fs.promises.writeFile(
196
- cachedFileURL,
216
+ cachedMetaFileURL,
197
217
  JSON.stringify({
198
- data: Buffer.from(resultData.data).toString("base64"),
199
218
  expires: resultData.expires,
200
219
  etag: resultData.etag,
201
220
  lastModified: resultData.lastModified
@@ -1,5 +1,5 @@
1
1
  export type RemoteCacheEntry = {
2
- data: string;
2
+ data?: string;
3
3
  expires: number;
4
4
  etag?: string;
5
5
  lastModified?: string;
@@ -70,6 +70,7 @@ function assets({ settings }) {
70
70
  globalThis.astroAsset = {
71
71
  referencedImages: /* @__PURE__ */ new Set()
72
72
  };
73
+ const imageComponentPrefix = settings.config.experimental.responsiveImages ? "Responsive" : "";
73
74
  return [
74
75
  // Expose the components and different utilities from `astro:assets`
75
76
  {
@@ -92,8 +93,8 @@ function assets({ settings }) {
92
93
  `
93
94
  export { getConfiguredImageService, isLocalService } from "astro/assets";
94
95
  import { getImage as getImageInternal } from "astro/assets";
95
- export { default as Image } from "astro/components/Image.astro";
96
- export { default as Picture } from "astro/components/Picture.astro";
96
+ export { default as Image } from "astro/components/${imageComponentPrefix}Image.astro";
97
+ export { default as Picture } from "astro/components/${imageComponentPrefix}Picture.astro";
97
98
  export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js";
98
99
 
99
100
  export const imageConfig = ${JSON.stringify({ ...settings.config.image, experimentalResponsiveImages: settings.config.experimental.responsiveImages })};
@@ -148,7 +148,7 @@ ${contentConfig.error.message}`);
148
148
  logger.info("Content config changed");
149
149
  shouldClear = true;
150
150
  }
151
- if (previousAstroVersion && previousAstroVersion !== "5.1.3") {
151
+ if (previousAstroVersion && previousAstroVersion !== "5.1.4") {
152
152
  logger.info("Astro version changed");
153
153
  shouldClear = true;
154
154
  }
@@ -156,8 +156,8 @@ ${contentConfig.error.message}`);
156
156
  logger.info("Clearing content store");
157
157
  this.#store.clearAll();
158
158
  }
159
- if ("5.1.3") {
160
- await this.#store.metaStore().set("astro-version", "5.1.3");
159
+ if ("5.1.4") {
160
+ await this.#store.metaStore().set("astro-version", "5.1.4");
161
161
  }
162
162
  if (currentConfigDigest) {
163
163
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -36,6 +36,16 @@ import {
36
36
  globWithUnderscoresIgnored,
37
37
  isDeferredModule
38
38
  } from "./utils.js";
39
+ function invalidateDataStore(server) {
40
+ const module = server.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
41
+ if (module) {
42
+ server.moduleGraph.invalidateModule(module);
43
+ }
44
+ server.ws.send({
45
+ type: "full-reload",
46
+ path: "*"
47
+ });
48
+ }
39
49
  function astroContentVirtualModPlugin({
40
50
  settings,
41
51
  fs
@@ -49,7 +59,10 @@ function astroContentVirtualModPlugin({
49
59
  dataStoreFile = getDataStoreFile(settings, env.command === "serve");
50
60
  },
51
61
  buildStart() {
52
- devServer?.watcher.add(fileURLToPath(dataStoreFile));
62
+ if (devServer) {
63
+ devServer.watcher.add(fileURLToPath(dataStoreFile));
64
+ invalidateDataStore(devServer);
65
+ }
53
66
  },
54
67
  async resolveId(id) {
55
68
  if (id === VIRTUAL_MODULE_ID) {
@@ -143,24 +156,14 @@ function astroContentVirtualModPlugin({
143
156
  configureServer(server) {
144
157
  devServer = server;
145
158
  const dataStorePath = fileURLToPath(dataStoreFile);
146
- function invalidateDataStore() {
147
- const module = server.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
148
- if (module) {
149
- server.moduleGraph.invalidateModule(module);
150
- }
151
- server.ws.send({
152
- type: "full-reload",
153
- path: "*"
154
- });
155
- }
156
159
  server.watcher.on("add", (addedPath) => {
157
160
  if (addedPath === dataStorePath) {
158
- invalidateDataStore();
161
+ invalidateDataStore(server);
159
162
  }
160
163
  });
161
164
  server.watcher.on("change", (changedPath) => {
162
165
  if (changedPath === dataStorePath) {
163
- invalidateDataStore();
166
+ invalidateDataStore(server);
164
167
  }
165
168
  });
166
169
  }
@@ -89,10 +89,22 @@ class App {
89
89
  }
90
90
  return pathname;
91
91
  }
92
+ /**
93
+ * It removes the base from the request URL, prepends it with a forward slash and attempts to decoded it.
94
+ *
95
+ * If the decoding fails, it logs the error and return the pathname as is.
96
+ * @param request
97
+ * @private
98
+ */
92
99
  #getPathnameFromRequest(request) {
93
100
  const url = new URL(request.url);
94
101
  const pathname = prependForwardSlash(this.removeBase(url.pathname));
95
- return pathname;
102
+ try {
103
+ return decodeURI(pathname);
104
+ } catch (e) {
105
+ this.getAdapterLogger().error(e.toString());
106
+ return pathname;
107
+ }
96
108
  }
97
109
  match(request) {
98
110
  const url = new URL(request.url);
@@ -128,7 +128,7 @@ async function generatePage(pageData, ssrEntry, builtPaths, pipeline) {
128
128
  async function generatePathWithLogs(path, route, index, paths, isConcurrent) {
129
129
  const timeStart = performance.now();
130
130
  pipeline.logger.debug("build", `Generating: ${path}`);
131
- const filePath = getOutputFilename(config, path, pageData.route.type);
131
+ const filePath = getOutputFilename(config, path, pageData.route);
132
132
  const lineIcon = index === paths.length - 1 && !isConcurrent || paths.length === 1 ? "\u2514\u2500" : "\u251C\u2500";
133
133
  if (!isConcurrent) {
134
134
  logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)}`, false);
@@ -272,7 +272,7 @@ async function cleanServerOutput(opts, ssrOutputChunkNames, internals) {
272
272
  files.map(async (filename) => {
273
273
  const url = new URL(filename, out);
274
274
  const map = new URL(url + ".map");
275
- await Promise.all([fs.promises.rm(url), fs.promises.rm(new URL(map)).catch((e) => {
275
+ await Promise.all([fs.promises.rm(url), fs.promises.rm(map).catch(() => {
276
276
  })]);
277
277
  })
278
278
  );
@@ -1,5 +1,5 @@
1
- import type { ShikiConfig, RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype } from '@astrojs/markdown-remark';
2
1
  import type { OutgoingHttpHeaders } from 'node:http';
2
+ import type { ShikiConfig, RehypePlugin as _RehypePlugin, RemarkPlugin as _RemarkPlugin, RemarkRehype as _RemarkRehype } from '@astrojs/markdown-remark';
3
3
  import { z } from 'zod';
4
4
  import type { ViteUserConfig } from '../../types/public/config.js';
5
5
  interface ComplexifyUnionObj {
@@ -449,34 +449,43 @@ export declare const AstroConfigSchema: z.ZodObject<{
449
449
  checkOrigin?: boolean | undefined;
450
450
  }>>>;
451
451
  env: z.ZodDefault<z.ZodOptional<z.ZodObject<{
452
- schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodUnion<[z.ZodObject<{
453
- context: z.ZodLiteral<"client">;
454
- access: z.ZodLiteral<"public">;
455
- }, "strip", z.ZodTypeAny, {
452
+ schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodEffects<z.ZodType<{
456
453
  context: "client";
457
454
  access: "public";
458
- }, {
455
+ } | {
456
+ context: "server";
457
+ access: "public";
458
+ } | {
459
+ context: "server";
460
+ access: "secret";
461
+ }, z.ZodTypeDef, {
459
462
  context: "client";
460
463
  access: "public";
461
- }>, z.ZodObject<{
462
- context: z.ZodLiteral<"server">;
463
- access: z.ZodLiteral<"public">;
464
- }, "strip", z.ZodTypeAny, {
464
+ } | {
465
465
  context: "server";
466
466
  access: "public";
467
- }, {
467
+ } | {
468
468
  context: "server";
469
+ access: "secret";
470
+ }>, {
471
+ context: "client";
469
472
  access: "public";
470
- }>, z.ZodObject<{
471
- context: z.ZodLiteral<"server">;
472
- access: z.ZodLiteral<"secret">;
473
- }, "strip", z.ZodTypeAny, {
473
+ } | {
474
+ context: "server";
475
+ access: "public";
476
+ } | {
474
477
  context: "server";
475
478
  access: "secret";
476
479
  }, {
480
+ context: "client";
481
+ access: "public";
482
+ } | {
483
+ context: "server";
484
+ access: "public";
485
+ } | {
477
486
  context: "server";
478
487
  access: "secret";
479
- }>]>, z.ZodUnion<[z.ZodObject<{
488
+ }>, z.ZodUnion<[z.ZodObject<{
480
489
  type: z.ZodLiteral<"string">;
481
490
  optional: z.ZodOptional<z.ZodBoolean>;
482
491
  default: z.ZodOptional<z.ZodString>;
@@ -1507,34 +1516,43 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1507
1516
  checkOrigin?: boolean | undefined;
1508
1517
  }>>>;
1509
1518
  env: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1510
- schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodUnion<[z.ZodObject<{
1511
- context: z.ZodLiteral<"client">;
1512
- access: z.ZodLiteral<"public">;
1513
- }, "strip", z.ZodTypeAny, {
1519
+ schema: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodEffects<z.ZodType<{
1514
1520
  context: "client";
1515
1521
  access: "public";
1516
- }, {
1522
+ } | {
1523
+ context: "server";
1524
+ access: "public";
1525
+ } | {
1526
+ context: "server";
1527
+ access: "secret";
1528
+ }, z.ZodTypeDef, {
1517
1529
  context: "client";
1518
1530
  access: "public";
1519
- }>, z.ZodObject<{
1520
- context: z.ZodLiteral<"server">;
1521
- access: z.ZodLiteral<"public">;
1522
- }, "strip", z.ZodTypeAny, {
1531
+ } | {
1523
1532
  context: "server";
1524
1533
  access: "public";
1525
- }, {
1534
+ } | {
1526
1535
  context: "server";
1536
+ access: "secret";
1537
+ }>, {
1538
+ context: "client";
1527
1539
  access: "public";
1528
- }>, z.ZodObject<{
1529
- context: z.ZodLiteral<"server">;
1530
- access: z.ZodLiteral<"secret">;
1531
- }, "strip", z.ZodTypeAny, {
1540
+ } | {
1541
+ context: "server";
1542
+ access: "public";
1543
+ } | {
1532
1544
  context: "server";
1533
1545
  access: "secret";
1534
1546
  }, {
1547
+ context: "client";
1548
+ access: "public";
1549
+ } | {
1550
+ context: "server";
1551
+ access: "public";
1552
+ } | {
1535
1553
  context: "server";
1536
1554
  access: "secret";
1537
- }>]>, z.ZodUnion<[z.ZodObject<{
1555
+ }>, z.ZodUnion<[z.ZodObject<{
1538
1556
  type: z.ZodLiteral<"string">;
1539
1557
  optional: z.ZodOptional<z.ZodBoolean>;
1540
1558
  default: z.ZodOptional<z.ZodString>;
@@ -1,7 +1,7 @@
1
- import { markdownConfigDefaults } from "@astrojs/markdown-remark";
2
- import { bundledThemes } from "shiki";
3
1
  import path from "node:path";
4
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { markdownConfigDefaults } from "@astrojs/markdown-remark";
4
+ import { bundledThemes } from "shiki";
5
5
  import { z } from "zod";
6
6
  import { EnvSchema } from "../../env/schema.js";
7
7
  import { appendForwardSlash, prependForwardSlash, removeTrailingForwardSlash } from "../path.js";
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.1.3";
1
+ const ASTRO_VERSION = "5.1.4";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
4
4
  const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
@@ -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.1.3";
25
+ const currentVersion = "5.1.4";
26
26
  const isPrerelease = currentVersion.includes("-");
27
27
  if (!isPrerelease) {
28
28
  try {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "5.1.3";
41
+ const version = "5.1.4";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -276,7 +276,7 @@ function printHelp({
276
276
  message.push(
277
277
  linebreak(),
278
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
279
- `v${"5.1.3"}`
279
+ `v${"5.1.4"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -39,7 +39,11 @@ function sequence(...handlers) {
39
39
  if (pipeline.serverLike === true && handleContext.isPrerendered === false && routeData.prerender === true) {
40
40
  throw new AstroError({
41
41
  ...ForbiddenRewrite,
42
- message: ForbiddenRewrite.message(pathname, pathname, routeData.component),
42
+ message: ForbiddenRewrite.message(
43
+ handleContext.url.pathname,
44
+ pathname,
45
+ routeData.component
46
+ ),
43
47
  hint: ForbiddenRewrite.hint(routeData.component)
44
48
  });
45
49
  }
@@ -19,7 +19,7 @@ async function getProps(opts) {
19
19
  ssr: serverLike,
20
20
  base
21
21
  });
22
- const params = getParams(route, decodeURI(pathname));
22
+ const params = getParams(route, pathname);
23
23
  const matchedStaticPath = findPathItemByKey(staticPaths, params, route, logger);
24
24
  if (!matchedStaticPath && (serverLike ? route.prerender : true)) {
25
25
  throw new AstroError({
@@ -72,7 +72,7 @@ class RenderContext {
72
72
  pipeline,
73
73
  locals,
74
74
  sequence(...pipeline.internalMiddleware, middleware ?? pipelineMiddleware),
75
- decodeURI(pathname),
75
+ pathname,
76
76
  request,
77
77
  routeData,
78
78
  status,
@@ -137,7 +137,14 @@ class RenderContext {
137
137
  if (payload instanceof Request) {
138
138
  this.request = payload;
139
139
  } else {
140
- this.request = copyRequest(newUrl, this.request);
140
+ this.request = copyRequest(
141
+ newUrl,
142
+ this.request,
143
+ // need to send the flag of the previous routeData
144
+ routeData.prerender,
145
+ this.pipeline.logger,
146
+ this.routeData.route
147
+ );
141
148
  }
142
149
  this.isRewriting = true;
143
150
  this.url = new URL(this.request.url);
@@ -228,7 +235,14 @@ class RenderContext {
228
235
  if (reroutePayload instanceof Request) {
229
236
  this.request = reroutePayload;
230
237
  } else {
231
- this.request = copyRequest(newUrl, this.request);
238
+ this.request = copyRequest(
239
+ newUrl,
240
+ this.request,
241
+ // need to send the flag of the previous routeData
242
+ routeData.prerender,
243
+ this.pipeline.logger,
244
+ this.routeData.route
245
+ );
232
246
  }
233
247
  this.url = new URL(this.request.url);
234
248
  this.cookies = new AstroCookies(this.request);
@@ -1,13 +1,13 @@
1
1
  import type { IncomingHttpHeaders } from 'node:http';
2
2
  import type { Logger } from './logger/core.js';
3
3
  type HeaderType = Headers | Record<string, any> | IncomingHttpHeaders;
4
- type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData;
4
+ type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData | ReadableStream<Uint8Array>;
5
5
  export interface CreateRequestOptions {
6
6
  url: URL | string;
7
7
  clientAddress?: string | undefined;
8
8
  headers: HeaderType;
9
9
  method?: string;
10
- body?: RequestBody | undefined;
10
+ body?: RequestBody | undefined | null;
11
11
  logger: Logger;
12
12
  locals?: object | undefined;
13
13
  /**
@@ -19,6 +19,7 @@ export interface CreateRequestOptions {
19
19
  */
20
20
  isPrerendered?: boolean;
21
21
  routePattern: string;
22
+ init?: RequestInit;
22
23
  }
23
24
  /**
24
25
  * Used by astro internals to create a web standard request object.
@@ -27,5 +28,5 @@ export interface CreateRequestOptions {
27
28
  *
28
29
  * This is used by the static build to create fake requests for prerendering, and by the dev server to convert node requests into the standard request object.
29
30
  */
30
- export declare function createRequest({ url, headers, method, body, logger, isPrerendered, routePattern, }: CreateRequestOptions): Request;
31
+ export declare function createRequest({ url, headers, method, body, logger, isPrerendered, routePattern, init, }: CreateRequestOptions): Request;
31
32
  export {};
@@ -5,7 +5,8 @@ function createRequest({
5
5
  body = void 0,
6
6
  logger,
7
7
  isPrerendered = false,
8
- routePattern
8
+ routePattern,
9
+ init
9
10
  }) {
10
11
  const headersObj = isPrerendered ? void 0 : headers instanceof Headers ? headers : new Headers(
11
12
  // Filter out HTTP/2 pseudo-headers. These are internally-generated headers added to all HTTP/2 requests with trusted metadata about the request.
@@ -22,7 +23,8 @@ function createRequest({
22
23
  method,
23
24
  headers: headersObj,
24
25
  // body is made available only if the request is for a page that will be on-demand rendered
25
- body: isPrerendered ? null : body
26
+ body: isPrerendered ? null : body,
27
+ ...init
26
28
  });
27
29
  if (isPrerendered) {
28
30
  let _headers = request.headers;
@@ -1,6 +1,7 @@
1
1
  import type { RewritePayload } from '../../types/public/common.js';
2
2
  import type { AstroConfig } from '../../types/public/config.js';
3
3
  import type { RouteData } from '../../types/public/internal.js';
4
+ import type { Logger } from '../logger/core.js';
4
5
  export type FindRouteToRewrite = {
5
6
  payload: RewritePayload;
6
7
  routes: RouteData[];
@@ -25,7 +26,10 @@ export declare function findRouteToRewrite({ payload, routes, request, trailingS
25
26
  *
26
27
  * @param newUrl The new `URL`
27
28
  * @param oldRequest The old `Request`
29
+ * @param isPrerendered It needs to be the flag of the previous routeData, before the rewrite
30
+ * @param logger
31
+ * @param routePattern
28
32
  */
29
- export declare function copyRequest(newUrl: URL, oldRequest: Request): Request;
33
+ export declare function copyRequest(newUrl: URL, oldRequest: Request, isPrerendered: boolean, logger: Logger, routePattern: string): Request;
30
34
  export declare function setOriginPathname(request: Request, pathname: string): void;
31
35
  export declare function getOriginPathname(request: Request): string;
@@ -2,6 +2,7 @@ import { shouldAppendForwardSlash } from "../build/util.js";
2
2
  import { originPathnameSymbol } from "../constants.js";
3
3
  import { AstroError, AstroErrorData } from "../errors/index.js";
4
4
  import { appendForwardSlash, removeTrailingForwardSlash } from "../path.js";
5
+ import { createRequest } from "../request.js";
5
6
  import { DEFAULT_404_ROUTE } from "./astro-designed-error-pages.js";
6
7
  function findRouteToRewrite({
7
8
  payload,
@@ -46,26 +47,32 @@ function findRouteToRewrite({
46
47
  }
47
48
  }
48
49
  }
49
- function copyRequest(newUrl, oldRequest) {
50
+ function copyRequest(newUrl, oldRequest, isPrerendered, logger, routePattern) {
50
51
  if (oldRequest.bodyUsed) {
51
52
  throw new AstroError(AstroErrorData.RewriteWithBodyUsed);
52
53
  }
53
- return new Request(newUrl, {
54
+ return createRequest({
55
+ url: newUrl,
54
56
  method: oldRequest.method,
55
- headers: oldRequest.headers,
56
57
  body: oldRequest.body,
57
- referrer: oldRequest.referrer,
58
- referrerPolicy: oldRequest.referrerPolicy,
59
- mode: oldRequest.mode,
60
- credentials: oldRequest.credentials,
61
- cache: oldRequest.cache,
62
- redirect: oldRequest.redirect,
63
- integrity: oldRequest.integrity,
64
- signal: oldRequest.signal,
65
- keepalive: oldRequest.keepalive,
66
- // https://fetch.spec.whatwg.org/#dom-request-duplex
67
- // @ts-expect-error It isn't part of the types, but undici accepts it and it allows to carry over the body to a new request
68
- duplex: "half"
58
+ isPrerendered,
59
+ logger,
60
+ headers: isPrerendered ? {} : oldRequest.headers,
61
+ routePattern,
62
+ init: {
63
+ referrer: oldRequest.referrer,
64
+ referrerPolicy: oldRequest.referrerPolicy,
65
+ mode: oldRequest.mode,
66
+ credentials: oldRequest.credentials,
67
+ cache: oldRequest.cache,
68
+ redirect: oldRequest.redirect,
69
+ integrity: oldRequest.integrity,
70
+ signal: oldRequest.signal,
71
+ keepalive: oldRequest.keepalive,
72
+ // https://fetch.spec.whatwg.org/#dom-request-duplex
73
+ // @ts-expect-error It isn't part of the types, but undici accepts it and it allows to carry over the body to a new request
74
+ duplex: "half"
75
+ }
69
76
  });
70
77
  }
71
78
  function setOriginPathname(request, pathname) {
@@ -1,6 +1,6 @@
1
1
  import type { AstroSettings } from '../types/astro.js';
2
2
  import type { AstroConfig } from '../types/public/config.js';
3
- import type { RouteType } from '../types/public/internal.js';
3
+ import type { RouteData } from '../types/public/internal.js';
4
4
  /** Returns true if argument is an object of any prototype/class (but not null). */
5
5
  export declare function isObject(value: unknown): value is Record<string, any>;
6
6
  /** Cross-realm compatible URL */
@@ -17,7 +17,7 @@ export declare function padMultilineString(source: string, n?: number): string;
17
17
  * Handles both "/foo" and "foo" `name` formats.
18
18
  * Handles `/404` and `/` correctly.
19
19
  */
20
- export declare function getOutputFilename(astroConfig: AstroConfig, name: string, type: RouteType): string;
20
+ export declare function getOutputFilename(astroConfig: AstroConfig, name: string, routeData: RouteData): string;
21
21
  /** is a specifier an npm package? */
22
22
  export declare function parseNpmName(spec: string): {
23
23
  scope?: string;
package/dist/core/util.js CHANGED
@@ -26,8 +26,8 @@ function padMultilineString(source, n = 2) {
26
26
  `);
27
27
  }
28
28
  const STATUS_CODE_PAGES = /* @__PURE__ */ new Set(["/404", "/500"]);
29
- function getOutputFilename(astroConfig, name, type) {
30
- if (type === "endpoint") {
29
+ function getOutputFilename(astroConfig, name, routeData) {
30
+ if (routeData.type === "endpoint") {
31
31
  return name;
32
32
  }
33
33
  if (name === "/" || name === "") {
@@ -36,6 +36,9 @@ function getOutputFilename(astroConfig, name, type) {
36
36
  if (astroConfig.build.format === "file" || STATUS_CODE_PAGES.has(name)) {
37
37
  return `${removeTrailingForwardSlash(name || "index")}.html`;
38
38
  }
39
+ if (astroConfig.build.format === "preserve" && !routeData.isIndex) {
40
+ return `${removeTrailingForwardSlash(name || "index")}.html`;
41
+ }
39
42
  return path.posix.join(name, "index.html");
40
43
  }
41
44
  function parseNpmName(spec) {
@@ -192,62 +192,80 @@ declare const EnvFieldType: z.ZodUnion<[z.ZodObject<{
192
192
  optional?: boolean | undefined;
193
193
  }>]>;
194
194
  export type EnvFieldType = z.infer<typeof EnvFieldType>;
195
- declare const EnvFieldMetadata: z.ZodUnion<[z.ZodObject<{
196
- context: z.ZodLiteral<"client">;
197
- access: z.ZodLiteral<"public">;
198
- }, "strip", z.ZodTypeAny, {
195
+ declare const EnvFieldMetadata: z.ZodEffects<z.ZodType<{
199
196
  context: "client";
200
197
  access: "public";
201
- }, {
198
+ } | {
199
+ context: "server";
200
+ access: "public";
201
+ } | {
202
+ context: "server";
203
+ access: "secret";
204
+ }, z.ZodTypeDef, {
202
205
  context: "client";
203
206
  access: "public";
204
- }>, z.ZodObject<{
205
- context: z.ZodLiteral<"server">;
206
- access: z.ZodLiteral<"public">;
207
- }, "strip", z.ZodTypeAny, {
207
+ } | {
208
208
  context: "server";
209
209
  access: "public";
210
- }, {
210
+ } | {
211
211
  context: "server";
212
+ access: "secret";
213
+ }>, {
214
+ context: "client";
212
215
  access: "public";
213
- }>, z.ZodObject<{
214
- context: z.ZodLiteral<"server">;
215
- access: z.ZodLiteral<"secret">;
216
- }, "strip", z.ZodTypeAny, {
216
+ } | {
217
+ context: "server";
218
+ access: "public";
219
+ } | {
217
220
  context: "server";
218
221
  access: "secret";
219
222
  }, {
223
+ context: "client";
224
+ access: "public";
225
+ } | {
226
+ context: "server";
227
+ access: "public";
228
+ } | {
220
229
  context: "server";
221
230
  access: "secret";
222
- }>]>;
223
- export declare const EnvSchema: z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodUnion<[z.ZodObject<{
224
- context: z.ZodLiteral<"client">;
225
- access: z.ZodLiteral<"public">;
226
- }, "strip", z.ZodTypeAny, {
231
+ }>;
232
+ export declare const EnvSchema: z.ZodRecord<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, z.ZodIntersection<z.ZodEffects<z.ZodType<{
227
233
  context: "client";
228
234
  access: "public";
229
- }, {
235
+ } | {
236
+ context: "server";
237
+ access: "public";
238
+ } | {
239
+ context: "server";
240
+ access: "secret";
241
+ }, z.ZodTypeDef, {
230
242
  context: "client";
231
243
  access: "public";
232
- }>, z.ZodObject<{
233
- context: z.ZodLiteral<"server">;
234
- access: z.ZodLiteral<"public">;
235
- }, "strip", z.ZodTypeAny, {
244
+ } | {
236
245
  context: "server";
237
246
  access: "public";
238
- }, {
247
+ } | {
239
248
  context: "server";
249
+ access: "secret";
250
+ }>, {
251
+ context: "client";
240
252
  access: "public";
241
- }>, z.ZodObject<{
242
- context: z.ZodLiteral<"server">;
243
- access: z.ZodLiteral<"secret">;
244
- }, "strip", z.ZodTypeAny, {
253
+ } | {
254
+ context: "server";
255
+ access: "public";
256
+ } | {
245
257
  context: "server";
246
258
  access: "secret";
247
259
  }, {
260
+ context: "client";
261
+ access: "public";
262
+ } | {
263
+ context: "server";
264
+ access: "public";
265
+ } | {
248
266
  context: "server";
249
267
  access: "secret";
250
- }>]>, z.ZodUnion<[z.ZodObject<{
268
+ }>, z.ZodUnion<[z.ZodObject<{
251
269
  type: z.ZodLiteral<"string">;
252
270
  optional: z.ZodOptional<z.ZodBoolean>;
253
271
  default: z.ZodOptional<z.ZodString>;
@@ -64,11 +64,30 @@ const SecretServerEnvFieldMetadata = z.object({
64
64
  context: z.literal("server"),
65
65
  access: z.literal("secret")
66
66
  });
67
- const EnvFieldMetadata = z.union([
67
+ const _EnvFieldMetadata = z.union([
68
68
  PublicClientEnvFieldMetadata,
69
69
  PublicServerEnvFieldMetadata,
70
70
  SecretServerEnvFieldMetadata
71
71
  ]);
72
+ const EnvFieldMetadata = z.custom().superRefine((data, ctx) => {
73
+ const result = _EnvFieldMetadata.safeParse(data);
74
+ if (result.success) {
75
+ return;
76
+ }
77
+ for (const issue of result.error.issues) {
78
+ if (issue.code === z.ZodIssueCode.invalid_union) {
79
+ ctx.addIssue({
80
+ code: z.ZodIssueCode.custom,
81
+ message: `**Invalid combination** of "access" and "context" options:
82
+ Secret client variables are not supported. Please review the configuration of \`env.schema.${ctx.path.at(-1)}\`.
83
+ Learn more at https://docs.astro.build/en/guides/environment-variables/#variable-types`,
84
+ path: ["context", "access"]
85
+ });
86
+ } else {
87
+ ctx.addIssue(issue);
88
+ }
89
+ }
90
+ });
72
91
  const EnvSchemaKey = z.string().min(1).refine(([firstChar]) => isNaN(Number.parseInt(firstChar)), {
73
92
  message: "A valid variable name cannot start with a number."
74
93
  }).refine((str) => /^[A-Z0-9_]+$/.test(str), {
@@ -12,8 +12,8 @@ async function compileAstro({
12
12
  try {
13
13
  transformResult = await compile(compileProps);
14
14
  esbuildResult = await transformWithEsbuild(transformResult.code, compileProps.filename, {
15
+ ...compileProps.viteConfig.esbuild,
15
16
  loader: "ts",
16
- target: "esnext",
17
17
  sourcemap: "external",
18
18
  tsconfigRaw: {
19
19
  compilerOptions: {
@@ -17,7 +17,7 @@ async function handleRequest({
17
17
  if (config.trailingSlash === "never" && !incomingRequest.url) {
18
18
  pathname = "";
19
19
  } else {
20
- pathname = url.pathname;
20
+ pathname = decodeURI(url.pathname);
21
21
  }
22
22
  url.pathname = removeTrailingForwardSlash(config.base) + url.pathname;
23
23
  let body = void 0;
@@ -179,7 +179,7 @@ async function handleRoute({
179
179
  const fourOhFourRoute = await matchRoute("/404", manifestData, pipeline);
180
180
  if (fourOhFourRoute) {
181
181
  renderContext = await RenderContext.create({
182
- locals: {},
182
+ locals,
183
183
  pipeline,
184
184
  pathname,
185
185
  middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? void 0 : middleware,
@@ -17,12 +17,7 @@ const markdownContentEntryType = {
17
17
  async getRenderFunction(config) {
18
18
  const processor = await createMarkdownProcessor(config.markdown);
19
19
  return async function renderToString(entry) {
20
- if (!entry.body) {
21
- return {
22
- html: ""
23
- };
24
- }
25
- const result = await processor.render(entry.body, {
20
+ const result = await processor.render(entry.body ?? "", {
26
21
  frontmatter: entry.data,
27
22
  // @ts-expect-error Internal API
28
23
  fileURL: entry.filePath ? pathToFileURL(entry.filePath) : void 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.1.3",
3
+ "version": "5.1.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",