astro 4.0.3 → 4.0.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.
package/astro-jsx.d.ts CHANGED
@@ -517,7 +517,8 @@ declare namespace astroHTML.JSX {
517
517
  | 'search'
518
518
  | 'send'
519
519
  | undefined
520
- | null;
520
+ | null;
521
+ exportparts?: string | undefined | null;
521
522
  hidden?: boolean | string | undefined | null;
522
523
  id?: string | undefined | null;
523
524
  inert?: boolean | string | undefined | null;
@@ -533,18 +534,23 @@ declare namespace astroHTML.JSX {
533
534
  | undefined
534
535
  | null;
535
536
  is?: string | undefined | null;
537
+
538
+ // Microdata API
536
539
  itemid?: string | undefined | null;
537
540
  itemprop?: string | undefined | null;
538
541
  itemref?: string | undefined | null;
539
542
  itemscope?: boolean | string | undefined | null;
540
543
  itemtype?: string | undefined | null;
544
+
541
545
  lang?: string | undefined | null;
546
+ part?: string | undefined | null;
547
+ popover?: boolean | string | undefined | null;
542
548
  slot?: string | undefined | null;
543
549
  spellcheck?: 'true' | 'false' | boolean | undefined | null;
544
550
  style?: string | StyleObject | undefined | null;
545
551
  tabindex?: number | string | undefined | null;
546
552
  title?: string | undefined | null;
547
- translate?: 'yes' | 'no' | undefined | null;
553
+ translate?: 'yes' | 'no' | '' | undefined | null;
548
554
 
549
555
  // <command>, <menuitem>
550
556
  radiogroup?: string | undefined | null;
package/astro.js CHANGED
@@ -67,7 +67,7 @@ Please upgrade Node.js to a supported version: "${engines}"\n`);
67
67
  console.log(
68
68
  `${ci.name} CI Environment Detected!\nAdditional steps may be needed to set your Node.js version:`
69
69
  );
70
- console.log(`Documentation: https://docs.astro.build/guides/deploy`);
70
+ console.log(`Documentation: https://docs.astro.build/en/guides/deploy/`);
71
71
  if (CI_INSTRUCTIONS[platform]) {
72
72
  console.log(`${ci.name} Documentation: ${CI_INSTRUCTIONS[platform]}`);
73
73
  }
@@ -51,6 +51,16 @@ async function getImage(options, imageConfig) {
51
51
  message: AstroErrorData.ExpectedImageOptions.message(JSON.stringify(options))
52
52
  });
53
53
  }
54
+ if (typeof options.src === "undefined") {
55
+ throw new AstroError({
56
+ ...AstroErrorData.ExpectedImage,
57
+ message: AstroErrorData.ExpectedImage.message(
58
+ options.src,
59
+ "undefined",
60
+ JSON.stringify(options)
61
+ )
62
+ });
63
+ }
54
64
  const service = await getConfiguredImageService();
55
65
  const resolvedOptions = {
56
66
  ...options,
@@ -5,6 +5,7 @@ import { bold, cyan, dim, green, magenta, red, yellow } from "kleur/colors";
5
5
  import fsMod, { existsSync, promises as fs } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import maxSatisfying from "semver/ranges/max-satisfying.js";
8
9
  import ora from "ora";
9
10
  import preferredPM from "preferred-pm";
10
11
  import prompts from "prompts";
@@ -507,9 +508,7 @@ async function getInstallIntegrationsCommand({
507
508
  logger.debug("add", `package manager: ${JSON.stringify(pm)}`);
508
509
  if (!pm)
509
510
  return null;
510
- let dependencies = integrations.map((i) => [[i.packageName, null], ...i.dependencies]).flat(1).filter((dep, i, arr) => arr.findIndex((d) => d[0] === dep[0]) === i).map(
511
- ([name, version]) => version === null ? name : `${name}@${version.split(/\s*\|\|\s*/).pop()}`
512
- ).sort();
511
+ const dependencies = await convertIntegrationsToInstallSpecifiers(integrations);
513
512
  switch (pm.name) {
514
513
  case "npm":
515
514
  return { pm: "npm", command: "install", flags: [], dependencies };
@@ -523,6 +522,26 @@ async function getInstallIntegrationsCommand({
523
522
  return null;
524
523
  }
525
524
  }
525
+ async function convertIntegrationsToInstallSpecifiers(integrations) {
526
+ const ranges = {};
527
+ for (let { packageName, dependencies } of integrations) {
528
+ ranges[packageName] = "*";
529
+ for (const [name, range] of dependencies) {
530
+ ranges[name] = range;
531
+ }
532
+ }
533
+ return Promise.all(
534
+ Object.entries(ranges).map(([name, range]) => resolveRangeToInstallSpecifier(name, range))
535
+ );
536
+ }
537
+ async function resolveRangeToInstallSpecifier(name, range) {
538
+ const versions = await fetchPackageVersions(name);
539
+ if (versions instanceof Error)
540
+ return name;
541
+ const stableVersions = versions.filter((v) => !v.includes("-"));
542
+ const maxStable = maxSatisfying(stableVersions, range);
543
+ return `${name}@^${maxStable}`;
544
+ }
526
545
  const INHERITED_FLAGS = /* @__PURE__ */ new Set([
527
546
  "P",
528
547
  "save-prod",
@@ -614,6 +633,19 @@ async function fetchPackageJson(scope, name, tag) {
614
633
  return new Error(`Failed to fetch ${registry}/${packageName}/${tag} - GET ${res.status}`);
615
634
  }
616
635
  }
636
+ async function fetchPackageVersions(packageName) {
637
+ const registry = await getRegistry();
638
+ const res = await fetch(`${registry}/${packageName}`, {
639
+ headers: { accept: "application/vnd.npm.install-v1+json" }
640
+ });
641
+ if (res.status >= 200 && res.status < 300) {
642
+ return await res.json().then((data) => Object.keys(data.versions));
643
+ } else if (res.status === 404) {
644
+ return new Error();
645
+ } else {
646
+ return new Error(`Failed to fetch ${registry}/${packageName} - GET ${res.status}`);
647
+ }
648
+ }
617
649
  async function validateIntegrations(integrations) {
618
650
  const spinner = ora("Resolving packages...").start();
619
651
  try {
@@ -4,7 +4,10 @@ import { Pipeline } from "../pipeline.js";
4
4
  import { routeIsFallback, routeIsRedirect } from "../redirects/helpers.js";
5
5
  import { createEnvironment } from "../render/index.js";
6
6
  import { createAssetLink } from "../render/ssr-element.js";
7
- import { ASTRO_PAGE_RESOLVED_MODULE_ID } from "./plugins/plugin-pages.js";
7
+ import {
8
+ ASTRO_PAGE_RESOLVED_MODULE_ID,
9
+ getVirtualModulePageNameFromPath
10
+ } from "./plugins/plugin-pages.js";
8
11
  import { RESOLVED_SPLIT_MODULE_ID } from "./plugins/plugin-ssr.js";
9
12
  import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from "./plugins/util.js";
10
13
  import { i18nHasFallback } from "./util.js";
@@ -131,7 +134,11 @@ class BuildPipeline extends Pipeline {
131
134
  if (routeIsRedirect(pageData.route)) {
132
135
  pages.set(pageData, path);
133
136
  } else if (routeIsFallback(pageData.route) && (i18nHasFallback(this.getConfig()) || routeIsFallback(pageData.route) && pageData.route.route === "/")) {
134
- pages.set(pageData, path);
137
+ const moduleSpecifier = getVirtualModulePageNameFromPath(path);
138
+ const filePath = this.#internals.entrySpecifierToBundleMap.get(moduleSpecifier);
139
+ if (filePath) {
140
+ pages.set(pageData, filePath);
141
+ }
135
142
  }
136
143
  }
137
144
  return pages;
@@ -223,11 +223,7 @@ async function generatePage(pageData, ssrEntry, builtPaths, pipeline) {
223
223
  };
224
224
  for (const route of eachRouteInRouteData(pageData)) {
225
225
  const icon = route.type === "page" || route.type === "redirect" || route.type === "fallback" ? green("\u25B6") : magenta("\u03BB");
226
- if (isRelativePath(route.component)) {
227
- logger.info(null, `${icon} ${route.route}`);
228
- } else {
229
- logger.info(null, `${icon} ${route.component}`);
230
- }
226
+ logger.info(null, `${icon} ${getPrettyRouteName(route)}`);
231
227
  const paths = await getPathsForRoute(route, pageModule, pipeline, builtPaths);
232
228
  let timeStart = performance.now();
233
229
  let prevTimeEnd = timeStart;
@@ -453,6 +449,15 @@ async function generatePath(pathname, pipeline, gopts, route) {
453
449
  await fs.promises.mkdir(outFolder, { recursive: true });
454
450
  await fs.promises.writeFile(outFile, body);
455
451
  }
452
+ function getPrettyRouteName(route) {
453
+ if (isRelativePath(route.component)) {
454
+ return route.route;
455
+ } else if (route.component.includes("node_modules/")) {
456
+ return route.component.match(/.*node_modules\/(.+)/)?.[1] ?? route.component;
457
+ } else {
458
+ return route.component;
459
+ }
460
+ }
456
461
  function createBuildManifest(settings, internals, renderers) {
457
462
  let i18nManifest = void 0;
458
463
  if (settings.config.i18n) {
@@ -132,8 +132,8 @@ class AstroBuilder {
132
132
  teardownCompiler: this.teardownCompiler,
133
133
  viteConfig
134
134
  };
135
- const { internals } = await viteBuild(opts);
136
- await staticBuild(opts, internals);
135
+ const { internals, ssrOutputChunkNames } = await viteBuild(opts);
136
+ await staticBuild(opts, internals, ssrOutputChunkNames);
137
137
  this.timer.assetsStart = performance.now();
138
138
  Object.keys(assets).map((k) => {
139
139
  if (!assets[k])
@@ -1,4 +1,4 @@
1
- import type { Plugin as VitePlugin } from 'vite';
1
+ import type { Plugin as VitePlugin, Rollup } from 'vite';
2
2
  import type { BuildInternals } from './internal.js';
3
3
  import type { StaticBuildOptions, ViteBuildReturn } from './types.js';
4
4
  type RollupOutputArray = Extract<ViteBuildReturn, Array<any>>;
@@ -34,7 +34,7 @@ export declare function createPluginContainer(options: StaticBuildOptions, inter
34
34
  vitePlugins: (VitePlugin<any> | VitePlugin<any>[])[];
35
35
  lastVitePlugins: (VitePlugin<any> | VitePlugin<any>[])[];
36
36
  }>;
37
- runPostHook(ssrReturn: ViteBuildReturn, clientReturn: ViteBuildReturn | null): Promise<Map<string, {
37
+ runPostHook(ssrOutputs: Rollup.RollupOutput[], clientOutputs: Rollup.RollupOutput[]): Promise<Map<string, {
38
38
  targets: BuildTarget[];
39
39
  code: string;
40
40
  }>>;
@@ -33,20 +33,8 @@ function createPluginContainer(options, internals) {
33
33
  lastVitePlugins
34
34
  };
35
35
  },
36
- async runPostHook(ssrReturn, clientReturn) {
36
+ async runPostHook(ssrOutputs, clientOutputs) {
37
37
  const mutations = /* @__PURE__ */ new Map();
38
- const ssrOutputs = [];
39
- const clientOutputs = [];
40
- if (Array.isArray(ssrReturn)) {
41
- ssrOutputs.push(...ssrReturn);
42
- } else if ("output" in ssrReturn) {
43
- ssrOutputs.push(ssrReturn);
44
- }
45
- if (Array.isArray(clientReturn)) {
46
- clientOutputs.push(...clientReturn);
47
- } else if (clientReturn && "output" in clientReturn) {
48
- clientOutputs.push(clientReturn);
49
- }
50
38
  const mutate = (chunk, targets, newCode) => {
51
39
  chunk.code = newCode;
52
40
  mutations.set(chunk.fileName, {
@@ -3,8 +3,9 @@ import { type BuildInternals } from '../../core/build/internal.js';
3
3
  import type { StaticBuildOptions } from './types.js';
4
4
  export declare function viteBuild(opts: StaticBuildOptions): Promise<{
5
5
  internals: BuildInternals;
6
+ ssrOutputChunkNames: string[];
6
7
  }>;
7
- export declare function staticBuild(opts: StaticBuildOptions, internals: BuildInternals): Promise<void>;
8
+ export declare function staticBuild(opts: StaticBuildOptions, internals: BuildInternals, ssrOutputChunkNames: string[]): Promise<void>;
8
9
  export declare function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles?: boolean): Promise<void>;
9
10
  /**
10
11
  * This function takes the virtual module name of any page entrypoint and
@@ -30,7 +30,7 @@ import { ASTRO_PAGE_RESOLVED_MODULE_ID } from "./plugins/plugin-pages.js";
30
30
  import { RESOLVED_RENDERERS_MODULE_ID } from "./plugins/plugin-renderers.js";
31
31
  import { RESOLVED_SPLIT_MODULE_ID, RESOLVED_SSR_VIRTUAL_MODULE_ID } from "./plugins/plugin-ssr.js";
32
32
  import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from "./plugins/util.js";
33
- import { encodeName, getTimeStat } from "./util.js";
33
+ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from "./util.js";
34
34
  async function viteBuild(opts) {
35
35
  const { allPages, settings } = opts;
36
36
  if (isModeServerWithNoAdapter(opts.settings)) {
@@ -70,28 +70,38 @@ async function viteBuild(opts) {
70
70
  clientInput.add(PAGE_SCRIPT_ID);
71
71
  }
72
72
  const clientOutput = await clientBuild(opts, internals, clientInput, container);
73
- await runPostBuildHooks(container, ssrOutput, clientOutput);
73
+ const ssrOutputs = viteBuildReturnToRollupOutputs(ssrOutput);
74
+ const clientOutputs = viteBuildReturnToRollupOutputs(clientOutput ?? []);
75
+ await runPostBuildHooks(container, ssrOutputs, clientOutputs);
74
76
  settings.timer.end("Client build");
75
77
  internals.ssrEntryChunk = void 0;
76
78
  if (opts.teardownCompiler) {
77
79
  teardown();
78
80
  }
79
- return { internals };
81
+ const ssrOutputChunkNames = [];
82
+ for (const output of ssrOutputs) {
83
+ for (const chunk of output.output) {
84
+ if (chunk.type === "chunk") {
85
+ ssrOutputChunkNames.push(chunk.fileName);
86
+ }
87
+ }
88
+ }
89
+ return { internals, ssrOutputChunkNames };
80
90
  }
81
- async function staticBuild(opts, internals) {
91
+ async function staticBuild(opts, internals, ssrOutputChunkNames) {
82
92
  const { settings } = opts;
83
93
  switch (true) {
84
94
  case settings.config.output === "static": {
85
95
  settings.timer.start("Static generate");
86
96
  await generatePages(opts, internals);
87
- await cleanServerOutput(opts);
97
+ await cleanServerOutput(opts, ssrOutputChunkNames);
88
98
  settings.timer.end("Static generate");
89
99
  return;
90
100
  }
91
101
  case isServerLikeOutput(settings.config): {
92
102
  settings.timer.start("Server generate");
93
103
  await generatePages(opts, internals);
94
- await cleanStaticOutput(opts, internals);
104
+ await cleanStaticOutput(opts, internals, ssrOutputChunkNames);
95
105
  opts.logger.info(null, `
96
106
  ${bgMagenta(black(" finalizing server assets "))}
97
107
  `);
@@ -254,19 +264,19 @@ ${bgGreen(black(" building client (vite) "))}`);
254
264
  const buildResult = await vite.build(viteBuildConfig);
255
265
  return buildResult;
256
266
  }
257
- async function runPostBuildHooks(container, ssrReturn, clientReturn) {
258
- const mutations = await container.runPostHook(ssrReturn, clientReturn);
267
+ async function runPostBuildHooks(container, ssrOutputs, clientOutputs) {
268
+ const mutations = await container.runPostHook(ssrOutputs, clientOutputs);
259
269
  const config = container.options.settings.config;
260
270
  const build = container.options.settings.config.build;
261
271
  for (const [fileName, mutation] of mutations) {
262
- const root = isServerLikeOutput(config) ? mutation.targets.includes("server") ? build.server : build.client : config.outDir;
272
+ const root = isServerLikeOutput(config) ? mutation.targets.includes("server") ? build.server : build.client : getOutDirWithinCwd(config.outDir);
263
273
  const fullPath = path.join(fileURLToPath(root), fileName);
264
274
  const fileURL = pathToFileURL(fullPath);
265
275
  await fs.promises.mkdir(new URL("./", fileURL), { recursive: true });
266
276
  await fs.promises.writeFile(fileURL, mutation.code, "utf-8");
267
277
  }
268
278
  }
269
- async function cleanStaticOutput(opts, internals) {
279
+ async function cleanStaticOutput(opts, internals, ssrOutputChunkNames) {
270
280
  const allStaticFiles = /* @__PURE__ */ new Set();
271
281
  for (const pageData of eachPageData(internals)) {
272
282
  if (pageData.route.prerender) {
@@ -278,9 +288,7 @@ async function cleanStaticOutput(opts, internals) {
278
288
  }
279
289
  const ssr = isServerLikeOutput(opts.settings.config);
280
290
  const out = ssr ? opts.settings.config.build.server : getOutDirWithinCwd(opts.settings.config.outDir);
281
- const files = await glob("**/*.mjs", {
282
- cwd: fileURLToPath(out)
283
- });
291
+ const files = ssrOutputChunkNames.filter((f) => f.endsWith(".mjs"));
284
292
  if (files.length) {
285
293
  await eslexer.init;
286
294
  await Promise.all(
@@ -306,13 +314,9 @@ export const ${e.n} = noop;`;
306
314
  removeEmptyDirs(out);
307
315
  }
308
316
  }
309
- async function cleanServerOutput(opts) {
317
+ async function cleanServerOutput(opts, ssrOutputChunkNames) {
310
318
  const out = getOutDirWithinCwd(opts.settings.config.outDir);
311
- const files = await glob("**/*.mjs", {
312
- cwd: fileURLToPath(out),
313
- // Important! Also cleanup dotfiles like `node_modules/.pnpm/**`
314
- dot: true
315
- });
319
+ const files = ssrOutputChunkNames.filter((f) => f.endsWith(".mjs"));
316
320
  if (files.length) {
317
321
  await Promise.all(
318
322
  files.map(async (filename) => {
@@ -1,4 +1,6 @@
1
+ import type { Rollup } from 'vite';
1
2
  import type { AstroConfig } from '../../@types/astro.js';
3
+ import type { ViteBuildReturn } from './types.js';
2
4
  export declare function getTimeStat(timeStart: number, timeEnd: number): string;
3
5
  /**
4
6
  * Given the Astro configuration, it tells if a slash should be appended or not
@@ -6,3 +8,4 @@ export declare function getTimeStat(timeStart: number, timeEnd: number): string;
6
8
  export declare function shouldAppendForwardSlash(trailingSlash: AstroConfig['trailingSlash'], buildFormat: AstroConfig['build']['format']): boolean;
7
9
  export declare function i18nHasFallback(config: AstroConfig): boolean;
8
10
  export declare function encodeName(name: string): string;
11
+ export declare function viteBuildReturnToRollupOutputs(viteBuildReturn: ViteBuildReturn): Rollup.RollupOutput[];
@@ -35,9 +35,19 @@ function encodeName(name) {
35
35
  }
36
36
  return name;
37
37
  }
38
+ function viteBuildReturnToRollupOutputs(viteBuildReturn) {
39
+ const result = [];
40
+ if (Array.isArray(viteBuildReturn)) {
41
+ result.push(...viteBuildReturn);
42
+ } else if ("output" in viteBuildReturn) {
43
+ result.push(viteBuildReturn);
44
+ }
45
+ return result;
46
+ }
38
47
  export {
39
48
  encodeName,
40
49
  getTimeStat,
41
50
  i18nHasFallback,
42
- shouldAppendForwardSlash
51
+ shouldAppendForwardSlash,
52
+ viteBuildReturnToRollupOutputs
43
53
  };
@@ -57,12 +57,12 @@ export declare const AstroConfigSchema: z.ZodObject<{
57
57
  redirects: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
58
58
  inlineStylesheets: z.ZodDefault<z.ZodOptional<z.ZodEnum<["always", "auto", "never"]>>>;
59
59
  }, "strip", z.ZodTypeAny, {
60
+ redirects: boolean;
60
61
  format: "file" | "directory";
61
62
  client: URL;
62
63
  server: URL;
63
64
  assets: string;
64
65
  serverEntry: string;
65
- redirects: boolean;
66
66
  inlineStylesheets: "always" | "never" | "auto";
67
67
  assetsPrefix?: string | undefined;
68
68
  }, {
@@ -308,12 +308,12 @@ export declare const AstroConfigSchema: z.ZodObject<{
308
308
  legacy: z.ZodDefault<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
309
309
  }, "strip", z.ZodTypeAny, {
310
310
  build: {
311
+ redirects: boolean;
311
312
  format: "file" | "directory";
312
313
  client: URL;
313
314
  server: URL;
314
315
  assets: string;
315
316
  serverEntry: string;
316
- redirects: boolean;
317
317
  inlineStylesheets: "always" | "never" | "auto";
318
318
  assetsPrefix?: string | undefined;
319
319
  };
@@ -332,6 +332,10 @@ export declare const AstroConfigSchema: z.ZodObject<{
332
332
  smartypants: boolean;
333
333
  };
334
334
  vite: ViteUserConfig;
335
+ redirects: Record<string, string | {
336
+ status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
337
+ destination: string;
338
+ }>;
335
339
  output: "server" | "static" | "hybrid";
336
340
  server: {
337
341
  host: string | boolean;
@@ -339,10 +343,6 @@ export declare const AstroConfigSchema: z.ZodObject<{
339
343
  open: boolean;
340
344
  headers?: OutgoingHttpHeaders | undefined;
341
345
  };
342
- redirects: Record<string, string | {
343
- status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
344
- destination: string;
345
- }>;
346
346
  root: URL;
347
347
  srcDir: URL;
348
348
  publicDir: URL;
@@ -539,7 +539,6 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
539
539
  smartypants?: boolean | undefined;
540
540
  }>>;
541
541
  vite: z.ZodDefault<z.ZodType<ViteUserConfig, z.ZodTypeDef, ViteUserConfig>>;
542
- output: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"static">, z.ZodLiteral<"server">, z.ZodLiteral<"hybrid">]>>>;
543
542
  redirects: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodObject<{
544
543
  status: z.ZodUnion<[z.ZodLiteral<300>, z.ZodLiteral<301>, z.ZodLiteral<302>, z.ZodLiteral<303>, z.ZodLiteral<304>, z.ZodLiteral<307>, z.ZodLiteral<308>]>;
545
544
  destination: z.ZodString;
@@ -550,6 +549,7 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
550
549
  status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
551
550
  destination: string;
552
551
  }>]>>>;
552
+ output: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"static">, z.ZodLiteral<"server">, z.ZodLiteral<"hybrid">]>>>;
553
553
  site: z.ZodOptional<z.ZodString>;
554
554
  base: z.ZodDefault<z.ZodOptional<z.ZodString>>;
555
555
  trailingSlash: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"always">, z.ZodLiteral<"never">, z.ZodLiteral<"ignore">]>>>;
@@ -748,12 +748,12 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
748
748
  redirects: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
749
749
  inlineStylesheets: z.ZodDefault<z.ZodOptional<z.ZodEnum<["always", "auto", "never"]>>>;
750
750
  }, "strip", z.ZodTypeAny, {
751
+ redirects: boolean;
751
752
  format: "file" | "directory";
752
753
  client: import("url").URL;
753
754
  server: import("url").URL;
754
755
  assets: string;
755
756
  serverEntry: string;
756
- redirects: boolean;
757
757
  inlineStylesheets: "always" | "never" | "auto";
758
758
  assetsPrefix?: string | undefined;
759
759
  }, {
@@ -793,12 +793,12 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
793
793
  }, unknown>;
794
794
  }, "strip", z.ZodTypeAny, {
795
795
  build: {
796
+ redirects: boolean;
796
797
  format: "file" | "directory";
797
798
  client: import("url").URL;
798
799
  server: import("url").URL;
799
800
  assets: string;
800
801
  serverEntry: string;
801
- redirects: boolean;
802
802
  inlineStylesheets: "always" | "never" | "auto";
803
803
  assetsPrefix?: string | undefined;
804
804
  };
@@ -817,6 +817,10 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
817
817
  smartypants: boolean;
818
818
  };
819
819
  vite: ViteUserConfig;
820
+ redirects: Record<string, string | {
821
+ status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
822
+ destination: string;
823
+ }>;
820
824
  output: "server" | "static" | "hybrid";
821
825
  server: {
822
826
  host: string | boolean;
@@ -825,10 +829,6 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
825
829
  streaming: boolean;
826
830
  headers?: OutgoingHttpHeaders | undefined;
827
831
  };
828
- redirects: Record<string, string | {
829
- status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
830
- destination: string;
831
- }>;
832
832
  root: import("url").URL;
833
833
  srcDir: import("url").URL;
834
834
  publicDir: import("url").URL;
@@ -902,11 +902,11 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
902
902
  smartypants?: boolean | undefined;
903
903
  } | undefined;
904
904
  vite?: ViteUserConfig | undefined;
905
- output?: "server" | "static" | "hybrid" | undefined;
906
905
  redirects?: Record<string, string | {
907
906
  status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
908
907
  destination: string;
909
908
  }> | undefined;
909
+ output?: "server" | "static" | "hybrid" | undefined;
910
910
  site?: string | undefined;
911
911
  base?: string | undefined;
912
912
  trailingSlash?: "ignore" | "always" | "never" | undefined;
@@ -973,12 +973,12 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
973
973
  server?: unknown;
974
974
  }>, {
975
975
  build: {
976
+ redirects: boolean;
976
977
  format: "file" | "directory";
977
978
  client: import("url").URL;
978
979
  server: import("url").URL;
979
980
  assets: string;
980
981
  serverEntry: string;
981
- redirects: boolean;
982
982
  inlineStylesheets: "always" | "never" | "auto";
983
983
  assetsPrefix?: string | undefined;
984
984
  };
@@ -997,6 +997,10 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
997
997
  smartypants: boolean;
998
998
  };
999
999
  vite: ViteUserConfig;
1000
+ redirects: Record<string, string | {
1001
+ status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1002
+ destination: string;
1003
+ }>;
1000
1004
  output: "server" | "static" | "hybrid";
1001
1005
  server: {
1002
1006
  host: string | boolean;
@@ -1005,10 +1009,6 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1005
1009
  streaming: boolean;
1006
1010
  headers?: OutgoingHttpHeaders | undefined;
1007
1011
  };
1008
- redirects: Record<string, string | {
1009
- status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1010
- destination: string;
1011
- }>;
1012
1012
  root: import("url").URL;
1013
1013
  srcDir: import("url").URL;
1014
1014
  publicDir: import("url").URL;
@@ -1082,11 +1082,11 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1082
1082
  smartypants?: boolean | undefined;
1083
1083
  } | undefined;
1084
1084
  vite?: ViteUserConfig | undefined;
1085
- output?: "server" | "static" | "hybrid" | undefined;
1086
1085
  redirects?: Record<string, string | {
1087
1086
  status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1088
1087
  destination: string;
1089
1088
  }> | undefined;
1089
+ output?: "server" | "static" | "hybrid" | undefined;
1090
1090
  site?: string | undefined;
1091
1091
  base?: string | undefined;
1092
1092
  trailingSlash?: "ignore" | "always" | "never" | undefined;
@@ -1153,12 +1153,12 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1153
1153
  server?: unknown;
1154
1154
  }>, {
1155
1155
  build: {
1156
+ redirects: boolean;
1156
1157
  format: "file" | "directory";
1157
1158
  client: import("url").URL;
1158
1159
  server: import("url").URL;
1159
1160
  assets: string;
1160
1161
  serverEntry: string;
1161
- redirects: boolean;
1162
1162
  inlineStylesheets: "always" | "never" | "auto";
1163
1163
  assetsPrefix?: string | undefined;
1164
1164
  };
@@ -1177,6 +1177,10 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1177
1177
  smartypants: boolean;
1178
1178
  };
1179
1179
  vite: ViteUserConfig;
1180
+ redirects: Record<string, string | {
1181
+ status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1182
+ destination: string;
1183
+ }>;
1180
1184
  output: "server" | "static" | "hybrid";
1181
1185
  server: {
1182
1186
  host: string | boolean;
@@ -1185,10 +1189,6 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1185
1189
  streaming: boolean;
1186
1190
  headers?: OutgoingHttpHeaders | undefined;
1187
1191
  };
1188
- redirects: Record<string, string | {
1189
- status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1190
- destination: string;
1191
- }>;
1192
1192
  root: import("url").URL;
1193
1193
  srcDir: import("url").URL;
1194
1194
  publicDir: import("url").URL;
@@ -1262,11 +1262,11 @@ export declare function createRelativeSchema(cmd: string, fileProtocolRoot: stri
1262
1262
  smartypants?: boolean | undefined;
1263
1263
  } | undefined;
1264
1264
  vite?: ViteUserConfig | undefined;
1265
- output?: "server" | "static" | "hybrid" | undefined;
1266
1265
  redirects?: Record<string, string | {
1267
1266
  status: 300 | 301 | 302 | 303 | 304 | 307 | 308;
1268
1267
  destination: string;
1269
1268
  }> | undefined;
1269
+ output?: "server" | "static" | "hybrid" | undefined;
1270
1270
  site?: string | undefined;
1271
1271
  base?: string | undefined;
1272
1272
  trailingSlash?: "ignore" | "always" | "never" | undefined;
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.0.3";
1
+ const ASTRO_VERSION = "4.0.5";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -31,6 +31,7 @@ import { vitePluginSSRManifest } from "../vite-plugin-ssr-manifest/index.js";
31
31
  import { createViteLogger } from "./logger/vite.js";
32
32
  import { vitePluginMiddleware } from "./middleware/vite-plugin.js";
33
33
  import { joinPaths } from "./path.js";
34
+ import vitePluginFileURL from "../vite-plugin-fileurl/index.js";
34
35
  const ALWAYS_NOEXTERNAL = [
35
36
  // This is only because Vite's native ESM doesn't resolve "exports" correctly.
36
37
  "astro",
@@ -113,6 +114,7 @@ async function createVite(commandConfig, { settings, logger, mode, command, fs =
113
114
  astroPrefetch({ settings }),
114
115
  astroTransitions({ settings }),
115
116
  astroDevOverlay({ settings, logger }),
117
+ vitePluginFileURL({}),
116
118
  !!settings.config.i18n && astroInternationalization({ settings })
117
119
  ],
118
120
  publicDir: fileURLToPath(settings.config.publicDir),
@@ -21,7 +21,7 @@ async function dev(inlineConfig) {
21
21
  base: restart.container.settings.config.base
22
22
  })
23
23
  );
24
- const currentVersion = "4.0.3";
24
+ const currentVersion = "4.0.5";
25
25
  if (currentVersion.includes("-")) {
26
26
  logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
27
27
  }
@@ -1155,3 +1155,9 @@ export declare const UnknownError: {
1155
1155
  name: string;
1156
1156
  title: string;
1157
1157
  };
1158
+ export declare const UnhandledRejection: {
1159
+ name: string;
1160
+ title: string;
1161
+ message: (stack: string) => string;
1162
+ hint: string;
1163
+ };
@@ -440,6 +440,15 @@ const CantRenderPage = {
440
440
  hint: "If you expect to find a route here, this may be an Astro bug. Please file an issue/restart the dev server"
441
441
  };
442
442
  const UnknownError = { name: "UnknownError", title: "Unknown Error." };
443
+ const UnhandledRejection = {
444
+ name: "UnhandledRejection",
445
+ title: "Unhandled rejection",
446
+ message: (stack) => {
447
+ return `Astro detected an unhandled rejection. Here's the stack trace:
448
+ ${stack}`;
449
+ },
450
+ hint: "Make sure your promises all have an `await` or a `.catch()` handler."
451
+ };
443
452
  export {
444
453
  AstroGlobNoMatch,
445
454
  AstroGlobUsedOutside,
@@ -503,6 +512,7 @@ export {
503
512
  ResponseSentError,
504
513
  StaticClientAddressNotAvailable,
505
514
  StaticRedirectNotAvailable,
515
+ UnhandledRejection,
506
516
  UnknownCLIError,
507
517
  UnknownCSSError,
508
518
  UnknownCompilerError,
@@ -7,7 +7,7 @@ export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
7
7
  * rather than specific to a single command, function, use, etc. The label will be
8
8
  * shown in the log message to the user, so it should be relevant.
9
9
  */
10
- export type LoggerLabel = 'add' | 'build' | 'check' | 'config' | 'content' | 'deprecated' | 'markdown' | 'router' | 'types' | 'vite' | 'watch' | 'middleware' | 'preferences' | 'SKIP_FORMAT';
10
+ export type LoggerLabel = 'add' | 'build' | 'check' | 'config' | 'content' | 'deprecated' | 'markdown' | 'router' | 'types' | 'vite' | 'watch' | 'middleware' | 'preferences' | 'redirects' | 'SKIP_FORMAT';
11
11
  export interface LogOptions {
12
12
  dest: LogWritable<LogMessage>;
13
13
  level: LoggerLevel;
@@ -36,7 +36,7 @@ function serverStart({
36
36
  host,
37
37
  base
38
38
  }) {
39
- const version = "4.0.3";
39
+ const version = "4.0.5";
40
40
  const localPrefix = `${dim("\u2503")} Local `;
41
41
  const networkPrefix = `${dim("\u2503")} Network `;
42
42
  const emptyPrefix = " ".repeat(11);
@@ -258,7 +258,7 @@ function printHelp({
258
258
  message.push(
259
259
  linebreak(),
260
260
  ` ${bgGreen(black(` ${commandName} `))} ${green(
261
- `v${"4.0.3"}`
261
+ `v${"4.0.5"}`
262
262
  )} ${headline}`
263
263
  );
264
264
  }
@@ -308,6 +308,20 @@ This route collides with: "${collision.component}".`
308
308
  const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
309
309
  const params = segments.flat().filter((p) => p.dynamic).map((p) => p.content);
310
310
  const route = `/${segments.map(([{ dynamic, content }]) => dynamic ? `[${content}]` : content).join("/")}`.toLowerCase();
311
+ {
312
+ let destination;
313
+ if (typeof to === "string") {
314
+ destination = to;
315
+ } else {
316
+ destination = to.destination;
317
+ }
318
+ if (/^https?:\/\//.test(destination)) {
319
+ logger.warn(
320
+ "redirects",
321
+ `Redirecting to an external URL is not officially supported: ${from} -> ${to}`
322
+ );
323
+ }
324
+ }
311
325
  const routeData = {
312
326
  type: "redirect",
313
327
  route,
@@ -43,9 +43,9 @@ function validateSupportKind(supportKind, adapterName, logger, featureName, hasC
43
43
  if (supportKind === STABLE) {
44
44
  return true;
45
45
  } else if (supportKind === DEPRECATED) {
46
- featureIsDeprecated(adapterName, logger);
46
+ featureIsDeprecated(adapterName, logger, featureName);
47
47
  } else if (supportKind === EXPERIMENTAL) {
48
- featureIsExperimental(adapterName, logger);
48
+ featureIsExperimental(adapterName, logger, featureName);
49
49
  }
50
50
  if (hasCorrectConfig() && supportKind === UNSUPPORTED) {
51
51
  featureIsUnsupported(adapterName, logger, featureName);
@@ -55,18 +55,18 @@ function validateSupportKind(supportKind, adapterName, logger, featureName, hasC
55
55
  }
56
56
  }
57
57
  function featureIsUnsupported(adapterName, logger, featureName) {
58
- logger.error("config", `The feature ${featureName} is not supported (used by ${adapterName}).`);
58
+ logger.error("config", `The feature "${featureName}" is not supported (used by ${adapterName}).`);
59
59
  }
60
- function featureIsExperimental(adapterName, logger) {
60
+ function featureIsExperimental(adapterName, logger, featureName) {
61
61
  logger.warn(
62
62
  "config",
63
- `The feature is experimental and subject to change (used by ${adapterName}).`
63
+ `The feature "${featureName}" is experimental and subject to change (used by ${adapterName}).`
64
64
  );
65
65
  }
66
- function featureIsDeprecated(adapterName, logger) {
66
+ function featureIsDeprecated(adapterName, logger, featureName) {
67
67
  logger.warn(
68
68
  "config",
69
- `The feature is deprecated and will be removed in the future (used by ${adapterName}).`
69
+ `The feature "${featureName}" is deprecated and will be removed in the future (used by ${adapterName}).`
70
70
  );
71
71
  }
72
72
  const SHARP_SERVICE = "astro/assets/services/sharp";
@@ -0,0 +1,7 @@
1
+ import type { ModuleLoader } from '../core/module-loader/index.js';
2
+ import type { AstroConfig } from '../@types/astro.js';
3
+ import type DevPipeline from './devPipeline.js';
4
+ export declare function recordServerError(loader: ModuleLoader, config: AstroConfig, pipeline: DevPipeline, _err: unknown): {
5
+ error: Error;
6
+ errorWithMetadata: import("../core/errors/errors.js").ErrorWithMetadata;
7
+ };
@@ -0,0 +1,24 @@
1
+ import { collectErrorMetadata } from "../core/errors/dev/index.js";
2
+ import { createSafeError } from "../core/errors/index.js";
3
+ import { formatErrorMessage } from "../core/messages.js";
4
+ import { eventError, telemetry } from "../events/index.js";
5
+ function recordServerError(loader, config, pipeline, _err) {
6
+ const err = createSafeError(_err);
7
+ try {
8
+ loader.fixStacktrace(err);
9
+ } catch {
10
+ }
11
+ const errorWithMetadata = collectErrorMetadata(err, config.root);
12
+ telemetry.record(eventError({ cmd: "dev", err: errorWithMetadata, isFatal: false }));
13
+ pipeline.logger.error(
14
+ null,
15
+ formatErrorMessage(errorWithMetadata, pipeline.logger.level() === "debug")
16
+ );
17
+ return {
18
+ error: err,
19
+ errorWithMetadata
20
+ };
21
+ }
22
+ export {
23
+ recordServerError
24
+ };
@@ -5,6 +5,12 @@ import { baseMiddleware } from "./base.js";
5
5
  import { createController } from "./controller.js";
6
6
  import DevPipeline from "./devPipeline.js";
7
7
  import { handleRequest } from "./request.js";
8
+ import { AstroError, AstroErrorData } from "../core/errors/index.js";
9
+ import { getViteErrorPayload } from "../core/errors/dev/index.js";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ import { IncomingMessage } from "node:http";
12
+ import { setRouteError } from "./server-state.js";
13
+ import { recordServerError } from "./error.js";
8
14
  function createVitePluginAstroServer({
9
15
  settings,
10
16
  logger,
@@ -18,6 +24,7 @@ function createVitePluginAstroServer({
18
24
  const pipeline = new DevPipeline({ logger, manifest, settings, loader });
19
25
  let manifestData = createRouteManifest({ settings, fsMod }, logger);
20
26
  const controller = createController({ loader });
27
+ const localStorage = new AsyncLocalStorage();
21
28
  function rebuildManifest(needsManifestRebuild) {
22
29
  pipeline.clearRouteCache();
23
30
  if (needsManifestRebuild) {
@@ -27,6 +34,23 @@ function createVitePluginAstroServer({
27
34
  viteServer.watcher.on("add", rebuildManifest.bind(null, true));
28
35
  viteServer.watcher.on("unlink", rebuildManifest.bind(null, true));
29
36
  viteServer.watcher.on("change", rebuildManifest.bind(null, false));
37
+ function handleUnhandledRejection(rejection) {
38
+ const error = new AstroError({
39
+ ...AstroErrorData.UnhandledRejection,
40
+ message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection)
41
+ });
42
+ const store = localStorage.getStore();
43
+ if (store instanceof IncomingMessage) {
44
+ const request = store;
45
+ setRouteError(controller.state, request.url, error);
46
+ }
47
+ const { errorWithMetadata } = recordServerError(loader, settings.config, pipeline, error);
48
+ setTimeout(
49
+ async () => loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)),
50
+ 200
51
+ );
52
+ }
53
+ process.on("unhandledRejection", handleUnhandledRejection);
30
54
  return () => {
31
55
  viteServer.middlewares.stack.unshift({
32
56
  route: "",
@@ -38,13 +62,15 @@ function createVitePluginAstroServer({
38
62
  response.end();
39
63
  return;
40
64
  }
41
- handleRequest({
42
- pipeline,
43
- manifestData,
44
- controller,
45
- incomingRequest: request,
46
- incomingResponse: response,
47
- manifest
65
+ localStorage.run(request, () => {
66
+ handleRequest({
67
+ pipeline,
68
+ manifestData,
69
+ controller,
70
+ incomingRequest: request,
71
+ incomingResponse: response,
72
+ manifest
73
+ });
48
74
  });
49
75
  });
50
76
  };
@@ -7,6 +7,7 @@ import { isServerLikeOutput } from "../prerender/utils.js";
7
7
  import { runWithErrorHandling } from "./controller.js";
8
8
  import { handle500Response } from "./response.js";
9
9
  import { handleRoute, matchRoute } from "./route.js";
10
+ import { recordServerError } from "./error.js";
10
11
  async function handleRequest({
11
12
  pipeline,
12
13
  manifestData,
@@ -64,19 +65,9 @@ async function handleRequest({
64
65
  });
65
66
  },
66
67
  onError(_err) {
67
- const err = createSafeError(_err);
68
- try {
69
- moduleLoader.fixStacktrace(err);
70
- } catch {
71
- }
72
- const errorWithMetadata = collectErrorMetadata(err, config.root);
73
- telemetry.record(eventError({ cmd: "dev", err: errorWithMetadata, isFatal: false }));
74
- pipeline.logger.error(
75
- null,
76
- formatErrorMessage(errorWithMetadata, pipeline.logger.level() === "debug")
77
- );
68
+ const { error, errorWithMetadata } = recordServerError(moduleLoader, config, pipeline, _err);
78
69
  handle500Response(moduleLoader, incomingResponse, errorWithMetadata);
79
- return err;
70
+ return error;
80
71
  }
81
72
  });
82
73
  }
@@ -0,0 +1,2 @@
1
+ import type { Plugin as VitePlugin } from 'vite';
2
+ export default function vitePluginFileURL({}: {}): VitePlugin;
@@ -0,0 +1,14 @@
1
+ function vitePluginFileURL({}) {
2
+ return {
3
+ name: "astro:vite-plugin-file-url",
4
+ resolveId(source, importer) {
5
+ if (source.startsWith("file://")) {
6
+ const rest = source.slice(7);
7
+ return this.resolve(rest, importer);
8
+ }
9
+ }
10
+ };
11
+ }
12
+ export {
13
+ vitePluginFileURL as default
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.0.3",
3
+ "version": "4.0.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",
@@ -157,9 +157,9 @@
157
157
  "which-pm": "^2.1.1",
158
158
  "yargs-parser": "^21.1.1",
159
159
  "zod": "^3.22.4",
160
- "@astrojs/markdown-remark": "4.0.0",
161
- "@astrojs/telemetry": "3.0.4",
162
- "@astrojs/internal-helpers": "0.2.1"
160
+ "@astrojs/internal-helpers": "0.2.1",
161
+ "@astrojs/markdown-remark": "4.0.1",
162
+ "@astrojs/telemetry": "3.0.4"
163
163
  },
164
164
  "optionalDependencies": {
165
165
  "sharp": "^0.32.5"
@@ -187,6 +187,7 @@
187
187
  "@types/probe-image-size": "^7.2.3",
188
188
  "@types/prompts": "^2.4.8",
189
189
  "@types/resolve": "^1.20.5",
190
+ "@types/semver": "^7.5.2",
190
191
  "@types/send": "^0.17.4",
191
192
  "@types/server-destroy": "^1.0.3",
192
193
  "@types/unist": "^3.0.2",