astro 5.5.2 → 5.5.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.
@@ -117,7 +117,7 @@ async function add(names, { flags }) {
117
117
  const cwd = inlineConfig.root;
118
118
  const logger = createLoggerFromFlags(flags);
119
119
  const integrationNames = names.map((name) => ALIASES.has(name) ? ALIASES.get(name) : name);
120
- const integrations = await validateIntegrations(integrationNames);
120
+ const integrations = await validateIntegrations(integrationNames, flags);
121
121
  let installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logger });
122
122
  const rootPath = resolveRoot(cwd);
123
123
  const root = pathToFileURL(rootPath);
@@ -585,7 +585,7 @@ ${message}`
585
585
  return 2 /* cancelled */;
586
586
  }
587
587
  }
588
- async function validateIntegrations(integrations) {
588
+ async function validateIntegrations(integrations, flags) {
589
589
  const spinner = yoctoSpinner({ text: "Resolving packages..." }).start();
590
590
  try {
591
591
  const integrationEntries = await Promise.all(
@@ -606,13 +606,7 @@ async function validateIntegrations(integrations) {
606
606
  spinner.warning(yellow(firstPartyPkgCheck.message));
607
607
  }
608
608
  spinner.warning(yellow(`${bold(integration)} is not an official Astro package.`));
609
- const response = await prompts({
610
- type: "confirm",
611
- name: "askToContinue",
612
- message: "Continue?",
613
- initial: true
614
- });
615
- if (!response.askToContinue) {
609
+ if (!await askToContinue({ flags })) {
616
610
  throw new Error(
617
611
  `No problem! Find our official integrations at ${cyan(
618
612
  "https://astro.build/integrations"
@@ -243,4 +243,15 @@ export declare class experimental_AstroContainer {
243
243
  * @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
244
244
  */
245
245
  renderToResponse(component: AstroComponentFactory, options?: ContainerRenderOptions): Promise<Response>;
246
+ /**
247
+ * It stores an Astro **page** route. The first argument, `route`, gets associated to the `component`.
248
+ *
249
+ * This function can be useful when you want to render a route via `AstroContainer.renderToString`, where that
250
+ * route eventually renders another route via `Astro.rewrite`.
251
+ *
252
+ * @param {string} route - The URL that will render the component.
253
+ * @param {AstroComponentFactory} component - The component factory to be used for rendering the route.
254
+ * @param {Record<string, string | undefined>} params - An object containing key-value pairs of route parameters.
255
+ */
256
+ insertPageRoute(route: string, component: AstroComponentFactory, params?: Record<string, string | undefined>): void;
246
257
  }
@@ -12,6 +12,7 @@ import { RenderContext } from "../core/render-context.js";
12
12
  import { getParts } from "../core/routing/manifest/parts.js";
13
13
  import { getPattern } from "../core/routing/manifest/pattern.js";
14
14
  import { validateSegment } from "../core/routing/manifest/segment.js";
15
+ import { SlotString } from "../runtime/server/render/slot.js";
15
16
  import { ContainerPipeline } from "./pipeline.js";
16
17
  function createManifest(manifest, renderers, middleware) {
17
18
  function middlewareInstance() {
@@ -234,6 +235,9 @@ class experimental_AstroContainer {
234
235
  * @param {ContainerRenderOptions=} options Possible options to pass when rendering the component.
235
236
  */
236
237
  async renderToString(component, options = {}) {
238
+ if (options.slots) {
239
+ options.slots = markAllSlotsAsSlotString(options.slots);
240
+ }
237
241
  const response = await this.renderToResponse(component, options);
238
242
  return await response.text();
239
243
  }
@@ -285,6 +289,29 @@ class experimental_AstroContainer {
285
289
  }
286
290
  return renderContext.render(componentInstance, slots);
287
291
  }
292
+ /**
293
+ * It stores an Astro **page** route. The first argument, `route`, gets associated to the `component`.
294
+ *
295
+ * This function can be useful when you want to render a route via `AstroContainer.renderToString`, where that
296
+ * route eventually renders another route via `Astro.rewrite`.
297
+ *
298
+ * @param {string} route - The URL that will render the component.
299
+ * @param {AstroComponentFactory} component - The component factory to be used for rendering the route.
300
+ * @param {Record<string, string | undefined>} params - An object containing key-value pairs of route parameters.
301
+ */
302
+ insertPageRoute(route, component, params) {
303
+ const url = new URL(route, "https://example.com/");
304
+ const routeData = this.#createRoute(url, params ?? {}, "page");
305
+ this.#pipeline.manifest.routes.push({
306
+ routeData,
307
+ file: "",
308
+ links: [],
309
+ styles: [],
310
+ scripts: []
311
+ });
312
+ const componentInstance = this.#wrapComponent(component, params);
313
+ this.#pipeline.insertRoute(routeData, componentInstance);
314
+ }
288
315
  #createRoute(url, params, type) {
289
316
  const segments = removeLeadingForwardSlash(url.pathname).split(posix.sep).filter(Boolean).map((s) => {
290
317
  validateSegment(s);
@@ -331,6 +358,13 @@ class experimental_AstroContainer {
331
358
  function isNamedRenderer(renderer) {
332
359
  return !!renderer?.name;
333
360
  }
361
+ function markAllSlotsAsSlotString(slots) {
362
+ const markedSlots = {};
363
+ for (const slotName in slots) {
364
+ markedSlots[slotName] = new SlotString(slots[slotName], null);
365
+ }
366
+ return markedSlots;
367
+ }
334
368
  export {
335
369
  experimental_AstroContainer
336
370
  };
@@ -9,5 +9,5 @@ export declare class ContainerPipeline extends Pipeline {
9
9
  headElements(routeData: RouteData): Promise<HeadElements> | HeadElements;
10
10
  tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
11
11
  insertRoute(route: RouteData, componentInstance: ComponentInstance): void;
12
- getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance>;
12
+ getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
13
13
  }
@@ -71,8 +71,12 @@ class ContainerPipeline extends Pipeline {
71
71
  });
72
72
  }
73
73
  // At the moment it's not used by the container via any public API
74
- // @ts-expect-error It needs to be implemented.
75
- async getComponentByRoute(_routeData) {
74
+ async getComponentByRoute(routeData) {
75
+ const page = this.#componentsInterner.get(routeData);
76
+ if (page) {
77
+ return page.page();
78
+ }
79
+ throw new Error("Couldn't find component for route " + routeData.pathname);
76
80
  }
77
81
  }
78
82
  export {
@@ -153,7 +153,7 @@ ${contentConfig.error.message}`);
153
153
  logger.info("Content config changed");
154
154
  shouldClear = true;
155
155
  }
156
- if (previousAstroVersion && previousAstroVersion !== "5.5.2") {
156
+ if (previousAstroVersion && previousAstroVersion !== "5.5.4") {
157
157
  logger.info("Astro version changed");
158
158
  shouldClear = true;
159
159
  }
@@ -161,8 +161,8 @@ ${contentConfig.error.message}`);
161
161
  logger.info("Clearing content store");
162
162
  this.#store.clearAll();
163
163
  }
164
- if ("5.5.2") {
165
- await this.#store.metaStore().set("astro-version", "5.5.2");
164
+ if ("5.5.4") {
165
+ await this.#store.metaStore().set("astro-version", "5.5.4");
166
166
  }
167
167
  if (currentConfigDigest) {
168
168
  await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -223,11 +223,11 @@ ${contentConfig.error.message}`);
223
223
  );
224
224
  await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
225
225
  await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
226
- await this.#store.writeToDisk();
227
226
  const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
228
227
  await this.#store.writeAssetImports(assetImportsFile);
229
228
  const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir);
230
229
  await this.#store.writeModuleImports(modulesImportsFile);
230
+ await this.#store.waitUntilSaveComplete();
231
231
  logger.info("Synced content");
232
232
  if (this.#settings.config.experimental.contentIntellisense) {
233
233
  await this.regenerateCollectionFileManifest();
@@ -20,6 +20,11 @@ export declare class MutableDataStore extends ImmutableDataStore {
20
20
  * Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
21
21
  */
22
22
  metaStore(collectionName?: string): MetaStore;
23
+ /**
24
+ * Returns a promise that resolves when all pending saves are complete.
25
+ * This includes any in-progress debounced saves for the data store, asset imports, and module imports.
26
+ */
27
+ waitUntilSaveComplete(): Promise<void>;
23
28
  toString(): string;
24
29
  writeToDisk(): Promise<void>;
25
30
  /**
@@ -15,6 +15,8 @@ class MutableDataStore extends ImmutableDataStore {
15
15
  #saveTimeout;
16
16
  #assetsSaveTimeout;
17
17
  #modulesSaveTimeout;
18
+ #savePromise;
19
+ #savePromiseResolve;
18
20
  #dirty = false;
19
21
  #assetsDirty = false;
20
22
  #modulesDirty = false;
@@ -118,15 +120,28 @@ ${lines.join(",\n")}]);
118
120
  }
119
121
  this.#modulesDirty = false;
120
122
  }
123
+ #maybeResolveSavePromise() {
124
+ if (!this.#saveTimeout && !this.#assetsSaveTimeout && !this.#modulesSaveTimeout && this.#savePromiseResolve) {
125
+ this.#savePromiseResolve();
126
+ this.#savePromiseResolve = void 0;
127
+ this.#savePromise = void 0;
128
+ }
129
+ }
121
130
  #writeAssetsImportsDebounced() {
122
131
  this.#assetsDirty = true;
123
132
  if (this.#assetsFile) {
124
133
  if (this.#assetsSaveTimeout) {
125
134
  clearTimeout(this.#assetsSaveTimeout);
126
135
  }
127
- this.#assetsSaveTimeout = setTimeout(() => {
136
+ if (!this.#savePromise) {
137
+ this.#savePromise = new Promise((resolve) => {
138
+ this.#savePromiseResolve = resolve;
139
+ });
140
+ }
141
+ this.#assetsSaveTimeout = setTimeout(async () => {
128
142
  this.#assetsSaveTimeout = void 0;
129
- this.writeAssetImports(this.#assetsFile);
143
+ await this.writeAssetImports(this.#assetsFile);
144
+ this.#maybeResolveSavePromise();
130
145
  }, SAVE_DEBOUNCE_MS);
131
146
  }
132
147
  }
@@ -136,22 +151,45 @@ ${lines.join(",\n")}]);
136
151
  if (this.#modulesSaveTimeout) {
137
152
  clearTimeout(this.#modulesSaveTimeout);
138
153
  }
139
- this.#modulesSaveTimeout = setTimeout(() => {
154
+ if (!this.#savePromise) {
155
+ this.#savePromise = new Promise((resolve) => {
156
+ this.#savePromiseResolve = resolve;
157
+ });
158
+ }
159
+ this.#modulesSaveTimeout = setTimeout(async () => {
140
160
  this.#modulesSaveTimeout = void 0;
141
- this.writeModuleImports(this.#modulesFile);
161
+ await this.writeModuleImports(this.#modulesFile);
162
+ this.#maybeResolveSavePromise();
142
163
  }, SAVE_DEBOUNCE_MS);
143
164
  }
144
165
  }
166
+ // Skips the debounce and writes to disk immediately
167
+ async #saveToDiskNow() {
168
+ if (this.#saveTimeout) {
169
+ clearTimeout(this.#saveTimeout);
170
+ }
171
+ this.#saveTimeout = void 0;
172
+ if (this.#file) {
173
+ await this.writeToDisk();
174
+ }
175
+ this.#maybeResolveSavePromise();
176
+ }
145
177
  #saveToDiskDebounced() {
146
178
  this.#dirty = true;
147
179
  if (this.#saveTimeout) {
148
180
  clearTimeout(this.#saveTimeout);
149
181
  }
150
- this.#saveTimeout = setTimeout(() => {
182
+ if (!this.#savePromise) {
183
+ this.#savePromise = new Promise((resolve) => {
184
+ this.#savePromiseResolve = resolve;
185
+ });
186
+ }
187
+ this.#saveTimeout = setTimeout(async () => {
151
188
  this.#saveTimeout = void 0;
152
189
  if (this.#file) {
153
- this.writeToDisk();
190
+ await this.writeToDisk();
154
191
  }
192
+ this.#maybeResolveSavePromise();
155
193
  }, SAVE_DEBOUNCE_MS);
156
194
  }
157
195
  #writing = /* @__PURE__ */ new Set();
@@ -271,6 +309,17 @@ ${lines.join(",\n")}]);
271
309
  has: (key) => this.has(collectionKey, key)
272
310
  };
273
311
  }
312
+ /**
313
+ * Returns a promise that resolves when all pending saves are complete.
314
+ * This includes any in-progress debounced saves for the data store, asset imports, and module imports.
315
+ */
316
+ async waitUntilSaveComplete() {
317
+ if (!this.#savePromise) {
318
+ return Promise.resolve();
319
+ }
320
+ await this.#saveToDiskNow();
321
+ return this.#savePromise;
322
+ }
274
323
  toString() {
275
324
  return devalue.stringify(this._collections);
276
325
  }
@@ -282,8 +331,8 @@ ${lines.join(",\n")}]);
282
331
  throw new AstroError(AstroErrorData.UnknownFilesystemError);
283
332
  }
284
333
  try {
285
- await this.#writeFileAtomic(this.#file, this.toString());
286
334
  this.#dirty = false;
335
+ await this.#writeFileAtomic(this.#file, this.toString());
287
336
  } catch (err) {
288
337
  throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
289
338
  }
@@ -450,8 +450,6 @@ async function render({
450
450
  }
451
451
  }
452
452
  function createReference({ lookupMap }) {
453
- let store = null;
454
- globalDataStore.get().then((s) => store = s);
455
453
  return function reference(collection) {
456
454
  return z.union([
457
455
  z.string(),
@@ -465,14 +463,6 @@ function createReference({ lookupMap }) {
465
463
  })
466
464
  ]).transform(
467
465
  (lookup, ctx) => {
468
- if (!store) {
469
- ctx.addIssue({
470
- code: ZodIssueCode.custom,
471
- message: `**${ctx.path.join(".")}:** Reference to ${collection} could not be resolved: store not available.
472
- This is an Astro bug, so please file an issue at https://github.com/withastro/astro/issues.`
473
- });
474
- return;
475
- }
476
466
  const flattenedErrorPath = ctx.path.join(".");
477
467
  if (typeof lookup === "object") {
478
468
  if (lookup.collection !== collection) {
@@ -195,12 +195,20 @@ class App {
195
195
  const redirect = this.#redirectTrailingSlash(url.pathname);
196
196
  if (redirect !== url.pathname) {
197
197
  const status = request.method === "GET" ? 301 : 308;
198
- return new Response(redirectTemplate({ status, location: redirect, from: request.url }), {
199
- status,
200
- headers: {
201
- location: redirect + url.search
198
+ return new Response(
199
+ redirectTemplate({
200
+ status,
201
+ relativeLocation: url.pathname,
202
+ absoluteLocation: redirect,
203
+ from: request.url
204
+ }),
205
+ {
206
+ status,
207
+ headers: {
208
+ location: redirect + url.search
209
+ }
202
210
  }
203
- });
211
+ );
204
212
  }
205
213
  addCookieHeader = renderOptions?.addCookieHeader;
206
214
  clientAddress = renderOptions?.clientAddress ?? Reflect.get(request, clientAddressSymbol);
@@ -39,13 +39,20 @@ class NodeApp extends App {
39
39
  return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0];
40
40
  };
41
41
  const forwardedProtocol = getFirstForwardedValue(req.headers["x-forwarded-proto"]);
42
- const protocol = forwardedProtocol ?? (isEncrypted ? "https" : "http");
42
+ const providedProtocol = isEncrypted ? "https" : "http";
43
+ const protocol = forwardedProtocol ?? providedProtocol;
43
44
  const forwardedHostname = getFirstForwardedValue(req.headers["x-forwarded-host"]);
44
- const hostname = forwardedHostname ?? req.headers.host ?? req.headers[":authority"];
45
+ const providedHostname = req.headers.host ?? req.headers[":authority"];
46
+ const hostname = forwardedHostname ?? providedHostname;
45
47
  const port = getFirstForwardedValue(req.headers["x-forwarded-port"]);
46
- const portInHostname = typeof hostname === "string" && /:\d+$/.test(hostname);
47
- const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ""}`;
48
- const url = `${protocol}://${hostnamePort}${req.url}`;
48
+ let url;
49
+ try {
50
+ const hostnamePort = getHostnamePort(hostname, port);
51
+ url = new URL(`${protocol}://${hostnamePort}${req.url}`);
52
+ } catch {
53
+ const hostnamePort = getHostnamePort(providedHostname, port);
54
+ url = new URL(`${providedProtocol}://${hostnamePort}`);
55
+ }
49
56
  const options = {
50
57
  method: req.method || "GET",
51
58
  headers: makeRequestHeaders(req)
@@ -107,6 +114,11 @@ class NodeApp extends App {
107
114
  }
108
115
  }
109
116
  }
117
+ function getHostnamePort(hostname, port) {
118
+ const portInHostname = typeof hostname === "string" && /:\d+$/.test(hostname);
119
+ const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ""}`;
120
+ return hostnamePort;
121
+ }
110
122
  function makeRequestHeaders(req) {
111
123
  const headers = new Headers();
112
124
  for (const [name, value] of Object.entries(req.headers)) {
@@ -327,7 +327,12 @@ async function generatePath(pathname, pipeline, gopts, route) {
327
327
  const siteURL = config.site;
328
328
  const location = siteURL ? new URL(locationSite, siteURL) : locationSite;
329
329
  const fromPath = new URL(request.url).pathname;
330
- body = redirectTemplate({ status: response.status, location, from: fromPath });
330
+ body = redirectTemplate({
331
+ status: response.status,
332
+ absoluteLocation: location,
333
+ relativeLocation: locationSite,
334
+ from: fromPath
335
+ });
331
336
  if (config.compressHTML === true) {
332
337
  body = body.replaceAll("\n", "");
333
338
  }
@@ -26,6 +26,9 @@ function mergeConfigRecursively(defaults, overrides, rootPath) {
26
26
  continue;
27
27
  }
28
28
  }
29
+ if (key === "allowedHosts" && rootPath === "server" && typeof existing === "boolean") {
30
+ continue;
31
+ }
29
32
  if (key === "data" && rootPath === "db") {
30
33
  if (!Array.isArray(existing) && !Array.isArray(value)) {
31
34
  existing = [existing];
@@ -24,6 +24,9 @@ async function loadConfigWithVite({
24
24
  const config = await import(pathToFileURL(configPath).toString() + "?t=" + Date.now());
25
25
  return config.default ?? {};
26
26
  } catch (e) {
27
+ if (e && typeof e === "object" && "code" in e && e.code === "ERR_DLOPEN_DISABLED") {
28
+ throw e;
29
+ }
27
30
  debug("Failed to load config with Node", e);
28
31
  }
29
32
  }
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "5.5.2";
1
+ const ASTRO_VERSION = "5.5.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";
@@ -1,5 +1,5 @@
1
- import type { CookieSerializeOptions } from 'cookie';
2
- export type AstroCookieSetOptions = Pick<CookieSerializeOptions, 'domain' | 'path' | 'expires' | 'maxAge' | 'httpOnly' | 'sameSite' | 'secure' | 'encode'>;
1
+ import type { SerializeOptions } from 'cookie';
2
+ export type AstroCookieSetOptions = Pick<SerializeOptions, 'domain' | 'path' | 'expires' | 'maxAge' | 'httpOnly' | 'sameSite' | 'secure' | 'encode'>;
3
3
  export interface AstroCookieGetOptions {
4
4
  decode?: (value: string) => string;
5
5
  }
@@ -76,7 +76,9 @@ class AstroCookies {
76
76
  const values = this.#ensureParsed(options);
77
77
  if (key in values) {
78
78
  const value = values[key];
79
- return new AstroCookie(value);
79
+ if (value) {
80
+ return new AstroCookie(value);
81
+ }
80
82
  }
81
83
  }
82
84
  /**
@@ -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.5.2";
25
+ const currentVersion = "5.5.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.5.2";
41
+ const version = "5.5.4";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -282,7 +282,7 @@ function printHelp({
282
282
  message.push(
283
283
  linebreak(),
284
284
  ` ${bgGreen(black(` ${commandName} `))} ${green(
285
- `v${"5.5.2"}`
285
+ `v${"5.5.4"}`
286
286
  )} ${headline}`
287
287
  );
288
288
  }
@@ -1,6 +1,7 @@
1
1
  export type RedirectTemplate = {
2
2
  from?: string;
3
- location: string | URL;
3
+ absoluteLocation: string | URL;
4
4
  status: number;
5
+ relativeLocation: string;
5
6
  };
6
- export declare function redirectTemplate({ status, location, from }: RedirectTemplate): string;
7
+ export declare function redirectTemplate({ status, absoluteLocation, relativeLocation, from, }: RedirectTemplate): string;
@@ -1,12 +1,17 @@
1
- function redirectTemplate({ status, location, from }) {
1
+ function redirectTemplate({
2
+ status,
3
+ absoluteLocation,
4
+ relativeLocation,
5
+ from
6
+ }) {
2
7
  const delay = status === 302 ? 2 : 0;
3
8
  return `<!doctype html>
4
- <title>Redirecting to: ${location}</title>
5
- <meta http-equiv="refresh" content="${delay};url=${location}">
9
+ <title>Redirecting to: ${relativeLocation}</title>
10
+ <meta http-equiv="refresh" content="${delay};url=${relativeLocation}">
6
11
  <meta name="robots" content="noindex">
7
- <link rel="canonical" href="${location}">
12
+ <link rel="canonical" href="${absoluteLocation}">
8
13
  <body>
9
- <a href="${location}">Redirecting ${from ? `from <code>${from}</code> ` : ""}to <code>${location}</code></a>
14
+ <a href="${relativeLocation}">Redirecting ${from ? `from <code>${from}</code> ` : ""}to <code>${relativeLocation}</code></a>
10
15
  </body>`;
11
16
  }
12
17
  export {
@@ -51,13 +51,20 @@ async function renderPage(result, componentFactory, props, children, streaming,
51
51
  headers.set("Content-Length", body.byteLength.toString());
52
52
  }
53
53
  let status = init.status;
54
+ let statusText = init.statusText;
54
55
  if (route?.route === "/404") {
55
56
  status = 404;
57
+ if (statusText === "OK") {
58
+ statusText = "Not Found";
59
+ }
56
60
  } else if (route?.route === "/500") {
57
61
  status = 500;
62
+ if (statusText === "OK") {
63
+ statusText = "Internal Server Error";
64
+ }
58
65
  }
59
66
  if (status) {
60
- return new Response(body, { ...init, headers, status });
67
+ return new Response(body, { ...init, headers, status, statusText });
61
68
  } else {
62
69
  return new Response(body, { ...init, headers });
63
70
  }
@@ -70,7 +70,7 @@ function renderServerIsland(result, _displayName, props, slots) {
70
70
  `<link rel="preload" as="fetch" href="${serverIslandUrl}" crossorigin="anonymous">`
71
71
  );
72
72
  }
73
- destination.write(`<script async type="module" data-island-id="${hostId}">
73
+ destination.write(`<script async type="module" data-astro-rerun data-island-id="${hostId}">
74
74
  let script = document.querySelector('script[data-island-id="${hostId}"]');
75
75
 
76
76
  ${useGETRequest ? (
@@ -93,11 +93,11 @@ let response = await fetch('${serverIslandUrl}', {
93
93
  )}
94
94
  if (script) {
95
95
  if(
96
- response.status === 200
97
- && response.headers.has('content-type')
96
+ response.status === 200
97
+ && response.headers.has('content-type')
98
98
  && response.headers.get('content-type').split(";")[0].trim() === 'text/html') {
99
99
  let html = await response.text();
100
-
100
+
101
101
  // Swap!
102
102
  while(script.previousSibling &&
103
103
  script.previousSibling.nodeType !== 8 &&
@@ -105,11 +105,11 @@ if (script) {
105
105
  script.previousSibling.remove();
106
106
  }
107
107
  script.previousSibling?.remove();
108
-
108
+
109
109
  let frag = document.createRange().createContextualFragment(html);
110
110
  script.before(frag);
111
111
  }
112
- script.remove();
112
+ script.remove(); // Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
113
113
  }
114
114
  </script>`);
115
115
  }
@@ -39,7 +39,11 @@ function writeHtmlResponse(res, statusCode, html) {
39
39
  res.end();
40
40
  }
41
41
  function writeRedirectResponse(res, statusCode, location) {
42
- const html = redirectTemplate({ status: statusCode, location });
42
+ const html = redirectTemplate({
43
+ status: statusCode,
44
+ absoluteLocation: location,
45
+ relativeLocation: location
46
+ });
43
47
  res.writeHead(statusCode, {
44
48
  Location: location,
45
49
  "Content-Type": "text/html",
@@ -211,10 +211,12 @@ async function handleRoute({
211
211
  }
212
212
  if (response.status < 400 && response.status >= 300) {
213
213
  if (response.status >= 300 && response.status < 400 && routeIsRedirect(route) && !config.build.redirects && pipeline.settings.buildOutput === "static") {
214
+ const location = response.headers.get("location");
214
215
  response = new Response(
215
216
  redirectTemplate({
216
217
  status: response.status,
217
- location: response.headers.get("location"),
218
+ absoluteLocation: location,
219
+ relativeLocation: location,
218
220
  from: pathname
219
221
  }),
220
222
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "5.5.2",
3
+ "version": "5.5.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",
@@ -106,7 +106,6 @@
106
106
  "@astrojs/compiler": "^2.11.0",
107
107
  "@oslojs/encoding": "^1.1.0",
108
108
  "@rollup/pluginutils": "^5.1.4",
109
- "@types/cookie": "^0.6.0",
110
109
  "acorn": "^8.14.1",
111
110
  "aria-query": "^5.3.2",
112
111
  "axobject-query": "^4.1.0",
@@ -114,7 +113,7 @@
114
113
  "ci-info": "^4.2.0",
115
114
  "clsx": "^2.1.1",
116
115
  "common-ancestor-path": "^1.0.1",
117
- "cookie": "^0.7.2",
116
+ "cookie": "^1.0.2",
118
117
  "cssesc": "^3.0.0",
119
118
  "debug": "^4.4.0",
120
119
  "deterministic-object-hash": "^2.0.2",
@@ -142,7 +141,7 @@
142
141
  "prompts": "^2.4.2",
143
142
  "rehype": "^13.0.2",
144
143
  "semver": "^7.7.1",
145
- "shiki": "^1.29.2",
144
+ "shiki": "^3.0.0",
146
145
  "tinyexec": "^0.3.2",
147
146
  "tinyglobby": "^0.2.12",
148
147
  "tsconfck": "^3.1.5",
@@ -158,8 +157,8 @@
158
157
  "zod": "^3.24.2",
159
158
  "zod-to-json-schema": "^3.24.3",
160
159
  "zod-to-ts": "^1.2.0",
161
- "@astrojs/markdown-remark": "6.3.0",
162
160
  "@astrojs/internal-helpers": "0.6.1",
161
+ "@astrojs/markdown-remark": "6.3.1",
163
162
  "@astrojs/telemetry": "3.2.0"
164
163
  },
165
164
  "optionalDependencies": {