summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/small-radios-remain.md5
-rw-r--r--packages/astro/src/cli/check.ts6
-rw-r--r--packages/astro/src/cli/index.ts40
-rw-r--r--packages/astro/src/core/add/index.ts18
-rw-r--r--packages/astro/src/core/build/common.ts2
-rw-r--r--packages/astro/src/core/build/generate.ts159
-rw-r--r--packages/astro/src/core/build/index.ts88
-rw-r--r--packages/astro/src/core/build/static-build.ts8
-rw-r--r--packages/astro/src/core/config.ts5
-rw-r--r--packages/astro/src/core/dev/index.ts2
-rw-r--r--packages/astro/src/core/dev/util.ts50
-rw-r--r--packages/astro/src/core/endpoint/dev/index.ts19
-rw-r--r--packages/astro/src/core/errors.ts78
-rw-r--r--packages/astro/src/core/messages.ts67
-rw-r--r--packages/astro/src/core/render/dev/error.ts44
-rw-r--r--packages/astro/src/core/render/dev/index.ts10
-rw-r--r--packages/astro/src/core/util.ts53
-rw-r--r--packages/astro/src/vite-plugin-astro-server/index.ts12
-rw-r--r--packages/astro/src/vite-plugin-build-html/index.ts39
-rw-r--r--packages/astro/test/cli.test.js2
-rw-r--r--packages/astro/test/config-validate.test.js7
21 files changed, 368 insertions, 346 deletions
diff --git a/.changeset/small-radios-remain.md b/.changeset/small-radios-remain.md
new file mode 100644
index 000000000..d0747a700
--- /dev/null
+++ b/.changeset/small-radios-remain.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Update CLI error format and style
diff --git a/packages/astro/src/cli/check.ts b/packages/astro/src/cli/check.ts
index 499ac9afd..ffdc246df 100644
--- a/packages/astro/src/cli/check.ts
+++ b/packages/astro/src/cli/check.ts
@@ -57,7 +57,7 @@ function offsetAt({ line, character }: { line: number; character: number }, text
return i;
}
-function pad(str: string, len: number) {
+function generateString(str: string, len: number) {
return Array.from({ length: len }, () => str).join('');
}
@@ -86,8 +86,8 @@ export async function check(astroConfig: AstroConfig) {
const lineNumStr = d.range.start.line.toString();
const lineNumLen = lineNumStr.length;
console.error(`${bgWhite(black(lineNumStr))} ${str}`);
- let tildes = pad('~', d.range.end.character - d.range.start.character);
- let spaces = pad(' ', d.range.start.character + lineNumLen - 1);
+ let tildes = generateString('~', d.range.end.character - d.range.start.character);
+ let spaces = generateString(' ', d.range.start.character + lineNumLen - 1);
console.error(` ${spaces}${bold(red(tildes))}\n`);
result.errors++;
break;
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index 4108d4984..5b4f2abbc 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -12,8 +12,9 @@ import add from '../core/add/index.js';
import devServer from '../core/dev/index.js';
import preview from '../core/preview/index.js';
import { check } from './check.js';
-import { formatConfigError, loadConfig } from '../core/config.js';
-import { printHelp } from '../core/messages.js';
+import { loadConfig } from '../core/config.js';
+import { printHelp, formatErrorMessage, formatConfigErrorMessage } from '../core/messages.js';
+import { createSafeError } from '../core/util.js';
type Arguments = yargs.Arguments;
type CLICommand = 'help' | 'version' | 'add' | 'dev' | 'build' | 'preview' | 'reload' | 'check';
@@ -102,40 +103,33 @@ export async function cli(args: string[]) {
// For now, `add` has to resolve the config again internally
config = await loadConfig({ cwd: projectRoot, flags });
} catch (err) {
- throwAndExit(err);
- return;
+ return throwAndExit(err);
}
switch (cmd) {
case 'add': {
try {
const packages = flags._.slice(3) as string[];
- await add(packages, { cwd: projectRoot, flags, logging });
- process.exit(0);
+ return await add(packages, { cwd: projectRoot, flags, logging });
} catch (err) {
- throwAndExit(err);
+ return throwAndExit(err);
}
- return;
}
case 'dev': {
try {
await devServer(config, { logging });
-
- await new Promise(() => {}); // don’t close dev server
+ return await new Promise(() => {}); // lives forever
} catch (err) {
- throwAndExit(err);
+ return throwAndExit(err);
}
- return;
}
case 'build': {
try {
- await build(config, { logging });
- process.exit(0);
+ return await build(config, { logging });
} catch (err) {
- throwAndExit(err);
+ return throwAndExit(err);
}
- return;
}
case 'check': {
@@ -145,11 +139,10 @@ export async function cli(args: string[]) {
case 'preview': {
try {
- await preview(config, { logging }); // this will keep running
+ return await preview(config, { logging }); // this will keep running
} catch (err) {
- throwAndExit(err);
+ return throwAndExit(err);
}
- return;
}
default: {
@@ -159,14 +152,11 @@ export async function cli(args: string[]) {
}
/** Display error and exit */
-function throwAndExit(err: any) {
+function throwAndExit(err: unknown) {
if (err instanceof z.ZodError) {
- console.error(formatConfigError(err));
- } else if (err.stack) {
- const [mainMsg, ...stackMsg] = err.stack.split('\n');
- console.error(colors.red(mainMsg) + '\n' + colors.dim(stackMsg.join('\n')));
+ console.error(formatConfigErrorMessage(err));
} else {
- console.error(colors.red(err.toString() || err));
+ console.error(formatErrorMessage(createSafeError(err)));
}
process.exit(1);
}
diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts
index 74045cc6e..d1f63a9cf 100644
--- a/packages/astro/src/core/add/index.ts
+++ b/packages/astro/src/core/add/index.ts
@@ -54,6 +54,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
}
applyPolyfill();
+ // If no integrations were given, prompt the user for some popular ones.
if (names.length === 0) {
const response = await prompts([
{
@@ -72,16 +73,13 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO
},
]);
- if (!response.frameworks && !response.addons) {
- info(logging, null, msg.cancelled(`Integrations skipped.`, `You can always run ${cyan('astro add')} later!`));
- return;
- }
- const selected = [response.frameworks ?? [], response.addons ?? []].flat(1);
- if (selected.length === 0) {
- error(logging, null, `\n${red('No integrations specified!')}\n${dim('Try running')} astro add again.`);
- return;
- }
- names = selected;
+ names = [...(response.frameworks ?? []), ...(response.addons ?? [])];
+ }
+
+ // If still empty after prompting, exit gracefully.
+ if (names.length === 0) {
+ error(logging, null, `No integrations specified.`);
+ return;
}
// Some packages might have a common alias! We normalize those here.
diff --git a/packages/astro/src/core/build/common.ts b/packages/astro/src/core/build/common.ts
index 5407f66fd..a29aa356c 100644
--- a/packages/astro/src/core/build/common.ts
+++ b/packages/astro/src/core/build/common.ts
@@ -4,7 +4,7 @@ import { appendForwardSlash } from '../../core/path.js';
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
-export function getOutRoot(astroConfig: AstroConfig): URL {
+function getOutRoot(astroConfig: AstroConfig): URL {
return new URL('./', astroConfig.dist);
}
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 119274e76..84d9dd7e2 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -1,21 +1,21 @@
+import fs from 'fs';
+import { bgGreen, bgMagenta, black, cyan, dim, green, magenta } from 'kleur/colors';
+import npath from 'path';
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
+import { fileURLToPath } from 'url';
import type { AstroConfig, ComponentInstance, EndpointHandler, SSRLoadedRenderer } from '../../@types/astro';
-import type { PageBuildData, StaticBuildOptions, SingleFileBuiltModule } from './types';
import type { BuildInternals } from '../../core/build/internal.js';
+import { debug, info } from '../../core/logger.js';
+import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
import type { RenderOptions } from '../../core/render/core';
-
-import fs from 'fs';
-import npath from 'path';
-import { fileURLToPath } from 'url';
-import { debug, error, info } from '../../core/logger.js';
-import { prependForwardSlash } from '../../core/path.js';
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { render } from '../render/core.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
-import { getOutFile, getOutRoot, getOutFolder } from './common.js';
-import { getPageDataByComponent, eachPageData } from './internal.js';
-import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors';
+import { getOutputFilename } from '../util.js';
+import { getOutFile, getOutFolder } from './common.js';
+import { eachPageData, getPageDataByComponent } from './internal.js';
+import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
// Render is usually compute, which Node.js can't parallelize well.
@@ -67,7 +67,8 @@ export function chunkIsPage(astroConfig: AstroConfig, output: OutputAsset | Outp
}
export async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
- info(opts.logging, null, `\n${bgMagenta(black(' generating static routes '))}\n`);
+ const timer = performance.now();
+ info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`);
const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint;
const serverEntry = opts.buildConfig.serverEntry;
@@ -78,6 +79,7 @@ export async function generatePages(result: RollupOutput, opts: StaticBuildOptio
for (const pageData of eachPageData(internals)) {
await generatePage(opts, internals, pageData, ssrEntry);
}
+ info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
}
async function generatePage(
@@ -109,31 +111,18 @@ async function generatePage(
renderers,
};
- const icon = pageData.route.type === 'page' ? cyan('</>') : magenta('{-}');
+ const icon = pageData.route.type === 'page' ? green('▶') : magenta('λ');
info(opts.logging, null, `${icon} ${pageData.route.component}`);
- // Throttle the paths to avoid overloading the CPU with too many tasks.
- const renderPromises = [];
- for (const paths of throttle(MAX_CONCURRENT_RENDERS, pageData.paths)) {
- for (const path of paths) {
- renderPromises.push(generatePath(path, opts, generationOptions));
- }
- // This blocks generating more paths until these 10 complete.
- await Promise.all(renderPromises);
+ for (let i = 0; i < pageData.paths.length; i++) {
+ const path = pageData.paths[i];
+ await generatePath(path, opts, generationOptions);
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
- let shouldLogTimeChange = !getTimeStat(timeStart, timeEnd).startsWith('0');
- for (const path of paths) {
- const timeIncrease = shouldLogTimeChange ? ` ${dim(`+${timeChange}`)}` : '';
- info(opts.logging, null, ` ${dim('┃')} ${path}${timeIncrease}`);
- // Should only log build time on the first generated path
- // Logging for all generated paths adds extra noise
- shouldLogTimeChange = false;
- }
- // Reset timeStart for the next batch of rendered paths
- timeStart = performance.now();
- // This empties the array without allocating a new one.
- renderPromises.length = 0;
+ const timeIncrease = `(+${timeChange})`;
+ const filePath = getOutputFilename(opts.astroConfig, path);
+ const lineIcon = i === pageData.paths.length - 1 ? '└─' : '├─';
+ info(opts.logging, null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
}
}
@@ -175,65 +164,61 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
}
}
- try {
- const options: RenderOptions = {
- legacyBuild: false,
- links,
- logging,
- markdownRender: astroConfig.markdownOptions.render,
- mod,
- origin,
- pathname,
- scripts,
- renderers,
- async resolve(specifier: string) {
- const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
- if (typeof hashedFilePath !== 'string') {
- // If no "astro:scripts/before-hydration.js" script exists in the build,
- // then we can assume that no before-hydration scripts are needed.
- // Return this as placeholder, which will be ignored by the browser.
- // TODO: In the future, we hope to run this entire script through Vite,
- // removing the need to maintain our own custom Vite-mimic resolve logic.
- if (specifier === BEFORE_HYDRATION_SCRIPT_ID) {
- return 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
- }
- throw new Error(`Cannot find the built path for ${specifier}`);
+ const options: RenderOptions = {
+ legacyBuild: false,
+ links,
+ logging,
+ markdownRender: astroConfig.markdownOptions.render,
+ mod,
+ origin,
+ pathname,
+ scripts,
+ renderers,
+ async resolve(specifier: string) {
+ const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
+ if (typeof hashedFilePath !== 'string') {
+ // If no "astro:scripts/before-hydration.js" script exists in the build,
+ // then we can assume that no before-hydration scripts are needed.
+ // Return this as placeholder, which will be ignored by the browser.
+ // TODO: In the future, we hope to run this entire script through Vite,
+ // removing the need to maintain our own custom Vite-mimic resolve logic.
+ if (specifier === BEFORE_HYDRATION_SCRIPT_ID) {
+ return 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
}
- const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
- const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
- return fullyRelativePath;
- },
- method: 'GET',
- headers: new Headers(),
- route: pageData.route,
- routeCache,
- site: astroConfig.buildOptions.site,
- ssr: opts.astroConfig.buildOptions.experimentalSsr,
- };
-
- let body: string;
- if (pageData.route.type === 'endpoint') {
- const result = await callEndpoint(mod as unknown as EndpointHandler, options);
-
- if (result.type === 'response') {
- throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
+ throw new Error(`Cannot find the built path for ${specifier}`);
}
- body = result.body;
- } else {
- const result = await render(options);
+ const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
+ const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
+ return fullyRelativePath;
+ },
+ method: 'GET',
+ headers: new Headers(),
+ route: pageData.route,
+ routeCache,
+ site: astroConfig.buildOptions.site,
+ ssr: opts.astroConfig.buildOptions.experimentalSsr,
+ };
- // If there's a redirect or something, just do nothing.
- if (result.type !== 'html') {
- return;
- }
- body = result.html;
+ let body: string;
+ if (pageData.route.type === 'endpoint') {
+ const result = await callEndpoint(mod as unknown as EndpointHandler, options);
+
+ if (result.type === 'response') {
+ throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
}
+ body = result.body;
+ } else {
+ const result = await render(options);
- const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type);
- const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
- await fs.promises.mkdir(outFolder, { recursive: true });
- await fs.promises.writeFile(outFile, body, 'utf-8');
- } catch (err) {
- error(opts.logging, 'build', `Error rendering:`, err);
+ // If there's a redirect or something, just do nothing.
+ if (result.type !== 'html') {
+ return;
+ }
+ body = result.html;
}
+
+ const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type);
+ const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
+ await fs.promises.mkdir(outFolder, { recursive: true });
+ await fs.promises.writeFile(outFile, body, 'utf-8');
}
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index 7a360b01e..4962ce1c7 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -16,6 +16,8 @@ import { staticBuild } from './static-build.js';
import { RouteCache } from '../render/route-cache.js';
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
import { getTimeStat } from './util.js';
+import { createSafeError } from '../util.js';
+import { fixViteErrorMessage } from '../errors.js';
export interface BuildOptions {
mode?: string;
@@ -24,8 +26,9 @@ export interface BuildOptions {
/** `astro build` */
export default async function build(config: AstroConfig, options: BuildOptions = { logging: defaultLogOptions }): Promise<void> {
+ applyPolyfill();
const builder = new AstroBuilder(config, options);
- await builder.build();
+ await builder.run();
}
class AstroBuilder {
@@ -35,32 +38,30 @@ class AstroBuilder {
private origin: string;
private routeCache: RouteCache;
private manifest: ManifestData;
- private viteServer?: vite.ViteDevServer;
- private viteConfig?: ViteConfigWithSSR;
+ private timer: Record<string, number>;
constructor(config: AstroConfig, options: BuildOptions) {
- applyPolyfill();
-
if (!config.buildOptions.site && config.buildOptions.sitemap !== false) {
warn(options.logging, 'config', `Set "buildOptions.site" to generate correct canonical URLs and sitemap`);
}
-
- if (options.mode) this.mode = options.mode;
+ if (options.mode) {
+ this.mode = options.mode;
+ }
this.config = config;
const port = config.devOptions.port; // no need to save this (don’t rely on port in builder)
this.logging = options.logging;
this.routeCache = new RouteCache(this.logging);
this.origin = config.buildOptions.site ? new URL(config.buildOptions.site).origin : `http://localhost:${port}`;
this.manifest = createRouteManifest({ config }, this.logging);
+ this.timer = {};
}
- async build() {
- info(this.logging, 'build', 'Initial setup...');
-
- const { logging, origin } = this;
- const timer: Record<string, number> = {};
- timer.init = performance.now();
- timer.viteStart = performance.now();
+ /** Setup Vite and run any async setup logic that couldn't run inside of the constructor. */
+ private async setup() {
+ debug('build', 'Initial setup...');
+ const { logging } = this;
+ this.timer.init = performance.now();
+ this.timer.viteStart = performance.now();
this.config = await runHookConfigSetup({ config: this.config, command: 'build' });
const viteConfig = await createVite(
{
@@ -74,10 +75,14 @@ class AstroBuilder {
);
await runHookConfigDone({ config: this.config });
warnIfUsingExperimentalSSR(logging, this.config);
- this.viteConfig = viteConfig;
const viteServer = await vite.createServer(viteConfig);
- this.viteServer = viteServer;
- debug('build', timerMessage('Vite started', timer.viteStart));
+ debug('build', timerMessage('Vite started', this.timer.viteStart));
+ return { viteConfig, viteServer };
+ }
+
+ /** Run the build logic. build() is marked private because usage should go through ".run()" */
+ private async build({ viteConfig, viteServer }: { viteConfig: ViteConfigWithSSR; viteServer: vite.ViteDevServer }) {
+ const { origin } = this;
const buildConfig: BuildConfig = {
client: new URL('./client/', this.config.dist),
server: new URL('./server/', this.config.dist),
@@ -86,15 +91,15 @@ class AstroBuilder {
};
await runHookBuildStart({ config: this.config, buildConfig });
- info(this.logging, 'build', 'Collecting page data...');
- timer.loadStart = performance.now();
+ info(this.logging, 'build', 'Collecting build information...');
+ this.timer.loadStart = performance.now();
const { assets, allPages } = await collectPagesData({
astroConfig: this.config,
logging: this.logging,
manifest: this.manifest,
origin,
routeCache: this.routeCache,
- viteServer: this.viteServer,
+ viteServer,
ssr: this.config.buildOptions.experimentalSsr,
});
@@ -104,21 +109,21 @@ class AstroBuilder {
// TODO: add better type inference to data.preload[1]
const frontmatter = (data.preload[1] as any).frontmatter;
if (Boolean(frontmatter.draft) && !this.config.buildOptions.drafts) {
- debug('build', timerMessage(`Skipping draft page ${page}`, timer.loadStart));
+ debug('build', timerMessage(`Skipping draft page ${page}`, this.timer.loadStart));
delete allPages[page];
}
}
});
- debug('build', timerMessage('All pages loaded', timer.loadStart));
+ debug('build', timerMessage('All pages loaded', this.timer.loadStart));
// The names of each pages
const pageNames: string[] = [];
// Bundle the assets in your final build: This currently takes the HTML output
// of every page (stored in memory) and bundles the assets pointed to on those pages.
- timer.buildStart = performance.now();
- info(this.logging, 'build', colors.dim(`Completed in ${getTimeStat(timer.init, performance.now())}`));
+ this.timer.buildStart = performance.now();
+ info(this.logging, 'build', colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`));
// Use the new faster static based build.
if (!this.config.buildOptions.legacyBuild) {
@@ -130,7 +135,7 @@ class AstroBuilder {
origin: this.origin,
pageNames,
routeCache: this.routeCache,
- viteConfig: this.viteConfig,
+ viteConfig,
buildConfig,
});
} else {
@@ -141,13 +146,13 @@ class AstroBuilder {
origin: this.origin,
pageNames,
routeCache: this.routeCache,
- viteConfig: this.viteConfig,
- viteServer: this.viteServer,
+ viteConfig,
+ viteServer,
});
}
// Write any additionally generated assets to disk.
- timer.assetsStart = performance.now();
+ this.timer.assetsStart = performance.now();
Object.keys(assets).map((k) => {
if (!assets[k]) return;
const filePath = new URL(`file://${k}`);
@@ -155,11 +160,11 @@ class AstroBuilder {
fs.writeFileSync(filePath, assets[k], 'utf8');
delete assets[k]; // free up memory
});
- debug('build', timerMessage('Additional assets copied', timer.assetsStart));
+ debug('build', timerMessage('Additional assets copied', this.timer.assetsStart));
// Build your final sitemap.
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
- timer.sitemapStart = performance.now();
+ this.timer.sitemapStart = performance.now();
const sitemapFilter = this.config.buildOptions.sitemapFilter ? (this.config.buildOptions.sitemapFilter as (page: string) => boolean) : undefined;
const sitemap = generateSitemap(
pageNames.map((pageName) => new URL(pageName, this.config.buildOptions.site).href),
@@ -168,16 +173,27 @@ class AstroBuilder {
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
- debug('build', timerMessage('Sitemap built', timer.sitemapStart));
+ debug('build', timerMessage('Sitemap built', this.timer.sitemapStart));
}
// You're done! Time to clean up.
await viteServer.close();
await runHookBuildDone({ config: this.config, pages: pageNames, routes: Object.values(allPages).map((pd) => pd.route) });
- if (logging.level && levels[logging.level] <= levels['info']) {
+ if (this.logging.level && levels[this.logging.level] <= levels['info']) {
const buildMode = this.config.buildOptions.experimentalSsr ? 'ssr' : 'static';
- await this.printStats({ logging, timeStart: timer.init, pageCount: pageNames.length, buildMode });
+ await this.printStats({ logging: this.logging, timeStart: this.timer.init, pageCount: pageNames.length, buildMode });
+ }
+ }
+
+ /** Build the given Astro project. */
+ async run() {
+ const setupData = await this.setup();
+ try {
+ await this.build(setupData);
+ } catch (_err) {
+ debugger;
+ throw fixViteErrorMessage(createSafeError(_err), setupData.viteServer);
}
}
@@ -188,14 +204,12 @@ class AstroBuilder {
let messages: string[] = [];
if (buildMode === 'static') {
- const timePerPage = Math.round(buildTime / pageCount);
- const perPageMsg = colors.dim(`(${colors.bold(`${timePerPage}ms`)} avg per page + resources)`);
- messages = [`${pageCount} pages built in`, colors.bold(total), perPageMsg];
+ messages = [`${pageCount} page(s) built in`, colors.bold(total)];
} else {
messages = ['Server built in', colors.bold(total)];
}
info(logging, 'build', messages.join(' '));
- info(logging, 'build', `🚀 ${colors.cyan(colors.bold('Done'))}`);
+ info(logging, 'build', `${colors.bold('Complete!')}`);
}
}
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index f5e54b7bf..8e1559e97 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -41,7 +41,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
const timer: Record<string, number> = {};
timer.buildStart = performance.now();
- info(opts.logging, 'build', 'Discovering entrypoints...');
for (const [component, pageData] of Object.entries(allPages)) {
const astroModuleURL = new URL('./' + component, astroConfig.projectRoot);
@@ -97,7 +96,7 @@ export async function staticBuild(opts: StaticBuildOptions) {
timer.ssr = performance.now();
info(opts.logging, 'build', 'Building for SSR...');
const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput;
- info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}`));
+ info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`));
timer.generate = performance.now();
if (opts.buildConfig.staticMode) {
@@ -107,7 +106,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
await ssrMoveAssets(opts);
}
- info(opts.logging, null, dim(`Completed in ${getTimeStat(timer.generate, performance.now())}\n`));
}
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
@@ -171,7 +169,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
}
// TODO: use vite.mergeConfig() here?
- info(opts.logging, null, `\n${bgGreen(black(' building resources '))}\n`);
+ info(opts.logging, null, `\n${bgGreen(black(' building client '))}`);
const out = isBuildingToSSR(astroConfig) ? opts.buildConfig.client : astroConfig.dist;
@@ -210,7 +208,7 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
server: viteConfig.server,
base: appendForwardSlash(astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/'),
});
- info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}\n`));
+ info(opts.logging, null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
return buildResult;
}
diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts
index 39fa44174..394c1c42d 100644
--- a/packages/astro/src/core/config.ts
+++ b/packages/astro/src/core/config.ts
@@ -327,11 +327,6 @@ export async function resolveConfig(userConfig: AstroUserConfig, root: string, f
return validatedConfig;
}
-export function formatConfigError(err: z.ZodError) {
- const errorList = err.issues.map((issue) => ` ! ${colors.bold(issue.path.join('.'))} ${colors.red(issue.message + '.')}`);
- return `${colors.red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
-}
-
function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record<string, any>, rootPath: string) {
const merged: Record<string, any> = { ...defaults };
for (const key in overrides) {
diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts
index 166009cda..cc1e7bde2 100644
--- a/packages/astro/src/core/dev/index.ts
+++ b/packages/astro/src/core/dev/index.ts
@@ -7,7 +7,7 @@ import { createVite } from '../create-vite.js';
import { defaultLogOptions, info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger.js';
import * as msg from '../messages.js';
import { apply as applyPolyfill } from '../polyfill.js';
-import { getResolvedHostForVite } from './util.js';
+import { getResolvedHostForVite } from '../util.js';
export interface DevOptions {
logging: LogOptions;
diff --git a/packages/astro/src/core/dev/util.ts b/packages/astro/src/core/dev/util.ts
deleted file mode 100644
index 9b0c974fd..000000000
--- a/packages/astro/src/core/dev/util.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { AstroConfig } from '../../@types/astro';
-
-export const localIps = new Set(['localhost', '127.0.0.1']);
-
-/** Pad string () */
-export function pad(input: string, minLength: number, dir?: 'left' | 'right'): string {
- let output = input;
- while (output.length < minLength) {
- output = dir === 'left' ? ' ' + output : output + ' ';
- }
- return output;
-}
-
-export function emoji(char: string, fallback: string) {
- return process.platform !== 'win32' ? char : fallback;
-}
-
-// TODO: remove once --hostname is baselined
-export function getResolvedHostForVite(config: AstroConfig) {
- if (config.devOptions.host === false && config.devOptions.hostname !== 'localhost') {
- return config.devOptions.hostname;
- } else {
- return config.devOptions.host;
- }
-}
-
-export function getLocalAddress(serverAddress: string, config: AstroConfig): string {
- // TODO: remove once --hostname is baselined
- const host = getResolvedHostForVite(config);
- if (typeof host === 'boolean' || host === 'localhost') {
- return 'localhost';
- } else {
- return serverAddress;
- }
-}
-
-export type NetworkLogging = 'none' | 'host-to-expose' | 'visible';
-
-export function getNetworkLogging(config: AstroConfig): NetworkLogging {
- // TODO: remove once --hostname is baselined
- const host = getResolvedHostForVite(config);
-
- if (host === false) {
- return 'host-to-expose';
- } else if (typeof host === 'string' && localIps.has(host)) {
- return 'none';
- } else {
- return 'visible';
- }
-}
diff --git a/packages/astro/src/core/endpoint/dev/index.ts b/packages/astro/src/core/endpoint/dev/index.ts
index 3190a500f..da3671bc0 100644
--- a/packages/astro/src/core/endpoint/dev/index.ts
+++ b/packages/astro/src/core/endpoint/dev/index.ts
@@ -1,21 +1,12 @@
import type { EndpointHandler } from '../../../@types/astro';
import type { SSROptions } from '../../render/dev';
-
import { preload } from '../../render/dev/index.js';
-import { errorHandler } from '../../render/dev/error.js';
import { call as callEndpoint } from '../index.js';
-import { getParamsAndProps, GetParamsAndPropsError } from '../../render/core.js';
-import { createRequest } from '../../render/request.js';
export async function call(ssrOpts: SSROptions) {
- try {
- const [, mod] = await preload(ssrOpts);
- return await callEndpoint(mod as unknown as EndpointHandler, {
- ...ssrOpts,
- ssr: ssrOpts.astroConfig.buildOptions.experimentalSsr,
- });
- } catch (e: unknown) {
- await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
- throw e;
- }
+ const [, mod] = await preload(ssrOpts);
+ return await callEndpoint(mod as unknown as EndpointHandler, {
+ ...ssrOpts,
+ ssr: ssrOpts.astroConfig.buildOptions.experimentalSsr,
+ });
}
diff --git a/packages/astro/src/core/errors.ts b/packages/astro/src/core/errors.ts
new file mode 100644
index 000000000..0150978dc
--- /dev/null
+++ b/packages/astro/src/core/errors.ts
@@ -0,0 +1,78 @@
+import type { BuildResult } from 'esbuild';
+import type { ViteDevServer } from 'vite';
+import type { SSRError } from '../@types/astro';
+import eol from 'eol';
+import fs from 'fs';
+import { codeFrame, createSafeError } from './util.js';
+
+export interface ErrorWithMetadata {
+ [name: string]: any;
+ message: string;
+ stack: string;
+ id?: string;
+ frame?: string;
+ plugin?: string;
+ pluginCode?: string;
+ loc?: {
+ file?: string;
+ line: number;
+ column: number;
+ };
+}
+
+export function cleanErrorStack(stack: string) {
+ return stack
+ .split(/\n/g)
+ .filter((l) => /^\s*at/.test(l))
+ .join('\n');
+}
+
+/** Update the error message to correct any vite-isms that we don't want to expose to the user. */
+export function fixViteErrorMessage(_err: unknown, server: ViteDevServer) {
+ const err = createSafeError(_err);
+ // Vite will give you better stacktraces, using sourcemaps.
+ server.ssrFixStacktrace(err);
+ // Fix: Astro.glob() compiles to import.meta.glob() by the time Vite sees it,
+ // so we need to update this error message in case it originally came from Astro.glob().
+ if (err.message === 'import.meta.glob() can only accept string literals.') {
+ err.message = 'Astro.glob() and import.meta.glob() can only accept string literals.';
+ }
+ return err;
+}
+
+/**
+ * Takes any error-like object and returns a standardized Error + metadata object.
+ * Useful for consistent reporting regardless of where the error surfaced from.
+ */
+export function collectErrorMetadata(e: any): ErrorWithMetadata {
+ // normalize error stack line-endings to \n
+ if ((e as any).stack) {
+ (e as any).stack = eol.lf((e as any).stack);
+ }
+
+ // Astro error (thrown by esbuild so it needs to be formatted for Vite)
+ if (Array.isArray((e as any).errors)) {
+ const { location, pluginName, text } = (e as BuildResult).errors[0];
+ const err = e as SSRError;
+ if (location) {
+ err.loc = { file: location.file, line: location.line, column: location.column };
+ err.id = err.id || location?.file;
+ }
+ const possibleFilePath = err.pluginCode || err.id || location?.file;
+ if (possibleFilePath && !err.frame) {
+ try {
+ const fileContents = fs.readFileSync(possibleFilePath, 'utf8');
+ err.frame = codeFrame(fileContents, err.loc);
+ } catch {
+ // do nothing, code frame isn't that big a deal
+ }
+ }
+ if (pluginName) {
+ err.plugin = pluginName;
+ }
+ return err;
+ }
+
+ // Generic error (probably from Vite, and already formatted)
+ return e;
+}
diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts
index e3a7741c4..888e6fcf4 100644
--- a/packages/astro/src/core/messages.ts
+++ b/packages/astro/src/core/messages.ts
@@ -2,12 +2,13 @@
* Dev server messages (organized here to prevent clutter)
*/
-import stripAnsi from 'strip-ansi';
import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed, bgWhite } from 'kleur/colors';
-import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js';
import os from 'os';
import type { AddressInfo } from 'net';
import type { AstroConfig } from '../@types/astro';
+import { collectErrorMetadata, cleanErrorStack } from './errors.js';
+import { ZodError } from 'zod';
+import { emoji, getLocalAddress, getResolvedHostForVite, padMultilineString } from './util.js';
const PREFIX_PADDING = 6;
@@ -18,15 +19,15 @@ export function req({ url, statusCode, reqTime }: { url: string; statusCode: num
else if (statusCode >= 400) color = yellow;
else if (statusCode >= 300) color = dim;
else if (statusCode >= 200) color = green;
- return `${bold(color(pad(`${statusCode}`, PREFIX_PADDING)))} ${pad(url, 40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
+ return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
}
export function reload({ file }: { file: string }): string {
- return `${green(pad('reload', PREFIX_PADDING))} ${file}`;
+ return `${green('reload'.padStart(PREFIX_PADDING))} ${file}`;
}
export function hmr({ file }: { file: string }): string {
- return `${green(pad('update', PREFIX_PADDING))} ${file}`;
+ return `${green('update'.padStart(PREFIX_PADDING))} ${file}`;
}
/** Display dev server host and startup time */
@@ -91,7 +92,7 @@ export function success(message: string, tip?: string) {
const badge = bgGreen(black(` success `));
const headline = green(message);
const footer = tip ? `\n ▶ ${tip}` : undefined;
- return ['', badge, headline, footer]
+ return ['', `${badge} ${headline}`, footer]
.filter((v) => v !== undefined)
.map((msg) => ` ${msg}`)
.join('\n');
@@ -101,7 +102,7 @@ export function failure(message: string, tip?: string) {
const badge = bgRed(black(` error `));
const headline = red(message);
const footer = tip ? `\n ▶ ${tip}` : undefined;
- return ['', badge, headline, footer]
+ return ['', `${badge} ${headline}`, footer]
.filter((v) => v !== undefined)
.map((msg) => ` ${msg}`)
.join('\n');
@@ -111,7 +112,7 @@ export function cancelled(message: string, tip?: string) {
const badge = bgYellow(black(` cancelled `));
const headline = yellow(message);
const footer = tip ? `\n ▶ ${tip}` : undefined;
- return ['', badge, headline, footer]
+ return ['', `${badge} ${headline}`, footer]
.filter((v) => v !== undefined)
.map((msg) => ` ${msg}`)
.join('\n');
@@ -122,15 +123,45 @@ export function portInUse({ port }: { port: number }): string {
return `Port ${port} in use. Trying a new one…`;
}
-/** Pretty-print errors */
-export function err(error: Error): string {
- if (!error.stack) return stripAnsi(error.message);
- let message = stripAnsi(error.message);
- let stack = stripAnsi(error.stack);
- const split = stack.indexOf(message) + message.length;
- message = stack.slice(0, split);
- stack = stack.slice(split).replace(/^\n+/, '');
- return `${message}\n${dim(stack)}`;
+const LOCAL_IP_HOSTS = new Set(['localhost', '127.0.0.1']);
+
+export function getNetworkLogging(config: AstroConfig): 'none' | 'host-to-expose' | 'visible' {
+ // TODO: remove once --hostname is baselined
+ const host = getResolvedHostForVite(config);
+
+ if (host === false) {
+ return 'host-to-expose';
+ } else if (typeof host === 'string' && LOCAL_IP_HOSTS.has(host)) {
+ return 'none';
+ } else {
+ return 'visible';
+ }
+}
+
+export function formatConfigErrorMessage(err: ZodError) {
+ const errorList = err.issues.map((issue) => ` ! ${bold(issue.path.join('.'))} ${red(issue.message + '.')}`);
+ return `${red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
+}
+
+export function formatErrorMessage(_err: Error, args: string[] = []): string {
+ const err = collectErrorMetadata(_err);
+ args.push(`${bgRed(black(` error `))}${red(bold(padMultilineString(err.message)))}`);
+ if (err.id) {
+ args.push(` ${bold('File:')}`);
+ args.push(red(` ${err.id}`));
+ }
+ if (err.frame) {
+ args.push(` ${bold('Code:')}`);
+ args.push(red(padMultilineString(err.frame, 4)));
+ }
+ if (args.length === 1 && err.stack) {
+ args.push(dim(cleanErrorStack(err.stack)));
+ } else if (err.stack) {
+ args.push(` ${bold('Stacktrace:')}`);
+ args.push(dim(cleanErrorStack(err.stack)));
+ args.push(``);
+ }
+ return args.join('\n');
}
export function printHelp({
@@ -157,7 +188,7 @@ export function printHelp({
let raw = '';
for (const row of rows) {
- raw += `${opts.prefix}${bold(pad(`${row[0]}`, opts.padding - opts.prefix.length))}`;
+ raw += `${opts.prefix}${bold(`${row[0]}`.padStart(opts.padding - opts.prefix.length))}`;
if (split) raw += '\n ';
raw += dim(row[1]) + '\n';
}
diff --git a/packages/astro/src/core/render/dev/error.ts b/packages/astro/src/core/render/dev/error.ts
deleted file mode 100644
index 352211b1f..000000000
--- a/packages/astro/src/core/render/dev/error.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { BuildResult } from 'esbuild';
-import type * as vite from 'vite';
-import type { SSRError } from '../../../@types/astro';
-
-import eol from 'eol';
-import fs from 'fs';
-import { codeFrame } from '../../util.js';
-
-interface ErrorHandlerOptions {
- filePath: URL;
- viteServer: vite.ViteDevServer;
-}
-
-export async function errorHandler(e: unknown, { viteServer, filePath }: ErrorHandlerOptions) {
- // normalize error stack line-endings to \n
- if ((e as any).stack) {
- (e as any).stack = eol.lf((e as any).stack);
- }
-
- // fix stack trace with Vite (this searches its module graph for matches)
- if (e instanceof Error) {
- viteServer.ssrFixStacktrace(e);
- }
-
- // Astro error (thrown by esbuild so it needs to be formatted for Vite)
- if (Array.isArray((e as any).errors)) {
- const { location, pluginName, text } = (e as BuildResult).errors[0];
- const err = e as SSRError;
- if (location) err.loc = { file: location.file, line: location.line, column: location.column };
- let src = err.pluginCode;
- if (!src && err.id && fs.existsSync(err.id)) src = await fs.promises.readFile(err.id, 'utf8');
- if (!src) src = await fs.promises.readFile(filePath, 'utf8');
- err.frame = codeFrame(src, err.loc);
- err.id = location?.file;
- err.message = `${location?.file}: ${text}
-${err.frame}
-`;
- if (pluginName) err.plugin = pluginName;
- throw err;
- }
-
- // Generic error (probably from Vite, and already formatted)
- throw e;
-}
diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts
index d16cb8923..af92aca85 100644
--- a/packages/astro/src/core/render/dev/index.ts
+++ b/packages/astro/src/core/render/dev/index.ts
@@ -7,7 +7,6 @@ import { prependForwardSlash } from '../../../core/path.js';
import { RouteCache } from '../route-cache.js';
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
import { getStylesForURL } from './css.js';
-import { errorHandler } from './error.js';
import { getHmrScript } from './hmr.js';
import { injectTags } from './html.js';
export interface SSROptions {
@@ -216,11 +215,6 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta
}
export async function ssr(preloadedComponent: ComponentPreload, ssrOpts: SSROptions): Promise<RenderResponse> {
- try {
- const [renderers, mod] = preloadedComponent;
- return await render(renderers, mod, ssrOpts); // note(drew): without "await", errors won’t get caught by errorHandler()
- } catch (e: unknown) {
- await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
- throw e;
- }
+ const [renderers, mod] = preloadedComponent;
+ return await render(renderers, mod, ssrOpts); // NOTE: without "await", errors won’t get caught below
}
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 6787f177b..373d6cff1 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -1,11 +1,12 @@
-import type { AstroConfig } from '../@types/astro';
-import type { ErrorPayload } from 'vite';
import eol from 'eol';
+import fs from 'fs';
import path from 'path';
+import resolve from 'resolve';
import slash from 'slash';
-import fs from 'fs';
import { fileURLToPath, pathToFileURL } from 'url';
-import resolve from 'resolve';
+import type { ErrorPayload } from 'vite';
+import type { AstroConfig } from '../@types/astro';
+import { removeEndingForwardSlash } from './path.js';
/** Normalize URL to its canonical form */
export function canonicalURL(url: string, base?: string): URL {
@@ -35,6 +36,28 @@ export function arraify<T>(target: T | T[]): T[] {
return Array.isArray(target) ? target : [target];
}
+export function padMultilineString(source: string, n = 2) {
+ const lines = source.split(/\r?\n/);
+ return lines.map((l) => ` `.repeat(n) + l).join(`\n`);
+}
+
+const STATUS_CODE_REGEXP = /^\/?[0-9]{3}$/;
+
+/**
+ * Get the correct output filename for a route, based on your config.
+ * Handles both "/foo" and "foo" `name` formats.
+ * Handles `/404` and `/` correctly.
+ */
+export function getOutputFilename(astroConfig: AstroConfig, name: string) {
+ if (name === '/' || name === '') {
+ return path.posix.join(name, 'index.html');
+ }
+ if (astroConfig.buildOptions.pageUrlFormat === 'directory' && !STATUS_CODE_REGEXP.test(name)) {
+ return path.posix.join(name, 'index.html');
+ }
+ return `${removeEndingForwardSlash(name || 'index')}.html`;
+}
+
/** is a specifier an npm package? */
export function parseNpmName(spec: string): { scope?: string; name: string; subpath?: string } | undefined {
// not an npm package
@@ -137,6 +160,28 @@ export function isBuildingToSSR(config: AstroConfig): boolean {
return !!config._ctx.adapter?.serverEntrypoint;
}
+export function emoji(char: string, fallback: string) {
+ return process.platform !== 'win32' ? char : fallback;
+}
+
+// TODO: remove once --hostname is baselined
+export function getResolvedHostForVite(config: AstroConfig) {
+ if (config.devOptions.host === false && config.devOptions.hostname !== 'localhost') {
+ return config.devOptions.hostname;
+ } else {
+ return config.devOptions.host;
+ }
+}
+
+export function getLocalAddress(serverAddress: string, config: AstroConfig): string {
+ const host = getResolvedHostForVite(config);
+ if (typeof host === 'boolean' || host === 'localhost') {
+ return 'localhost';
+ } else {
+ return serverAddress;
+ }
+}
+
// Vendored from https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl
/** Basic stylesheet for RSS feeds */
export const PRETTY_FEED_V3 = `<?xml version="1.0" encoding="utf-8"?>
diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts
index bc45596da..5b4ae41b8 100644
--- a/packages/astro/src/vite-plugin-astro-server/index.ts
+++ b/packages/astro/src/vite-plugin-astro-server/index.ts
@@ -2,7 +2,7 @@ import type * as vite from 'vite';
import type http from 'http';
import type { AstroConfig, ManifestData } from '../@types/astro';
import type { RenderResponse, SSROptions } from '../core/render/dev/index';
-import { info, warn, error, LogOptions } from '../core/logger.js';
+import { debug, info, warn, error, LogOptions } from '../core/logger.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js';
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
import stripAnsi from 'strip-ansi';
@@ -10,11 +10,10 @@ import { createSafeError } from '../core/util.js';
import { ssr, preload } from '../core/render/dev/index.js';
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
import * as msg from '../core/messages.js';
-
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
import serverErrorTemplate from '../template/5xx.js';
import { RouteCache } from '../core/render/route-cache.js';
-import { AstroRequest } from '../core/render/request.js';
+import { fixViteErrorMessage } from '../core/errors.js';
interface AstroPluginOptions {
config: AstroConfig;
@@ -207,11 +206,10 @@ async function handleRequest(
const result = await ssr(preloadedComponent, options);
return await writeSSRResult(result, res, statusCode);
}
- } catch (_err: any) {
+ } catch (_err) {
debugger;
- info(logging, 'serve', msg.req({ url: pathname, statusCode: 500 }));
- const err = createSafeError(_err);
- error(logging, 'error', msg.err(err));
+ const err = fixViteErrorMessage(createSafeError(_err), viteServer);
+ error(logging, null, msg.formatErrorMessage(err));
handle500Response(viteServer, origin, req, res, err);
}
}
diff --git a/packages/astro/src/vite-plugin-build-html/index.ts b/packages/astro/src/vite-plugin-build-html/index.ts
index 1dc6e3392..80dd480a1 100644
--- a/packages/astro/src/vite-plugin-build-html/index.ts
+++ b/packages/astro/src/vite-plugin-build-html/index.ts
@@ -1,21 +1,22 @@
-import type { AstroConfig } from '../@types/astro';
-import type { LogOptions } from '../core/logger.js';
-import type { ViteDevServer, Plugin as VitePlugin } from 'vite';
-import type { OutputChunk, PreRenderedChunk, PluginContext } from 'rollup';
-import type { AllPagesData } from '../core/build/types';
-import type { BuildInternals } from '../core/build/internal';
+import { createElement, createScript, getAttribute, hasAttribute, insertBefore, remove, setAttribute } from '@web/parse5-utils';
+import { promises as fs } from 'fs';
import parse5 from 'parse5';
-import srcsetParse from 'srcset-parse';
import * as npath from 'path';
-import { promises as fs } from 'fs';
-import { getAttribute, hasAttribute, insertBefore, remove, createScript, createElement, setAttribute } from '@web/parse5-utils';
-import { addRollupInput } from './add-rollup-input.js';
-import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getTextContent, getAttributes } from './extract-assets.js';
-import { isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory, hasSrcSet } from './util.js';
+import type { OutputChunk, PluginContext, PreRenderedChunk } from 'rollup';
+import srcsetParse from 'srcset-parse';
+import type { Plugin as VitePlugin, ViteDevServer } from 'vite';
+import type { AstroConfig } from '../@types/astro';
+import type { BuildInternals } from '../core/build/internal';
+import type { AllPagesData } from '../core/build/types';
+import type { LogOptions } from '../core/logger.js';
+import { prependDotSlash } from '../core/path.js';
import { render as ssrRender } from '../core/render/dev/index.js';
-import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
-import { prependDotSlash, removeEndingForwardSlash } from '../core/path.js';
import { RouteCache } from '../core/render/route-cache.js';
+import { getOutputFilename } from '../core/util.js';
+import { getAstroPageStyleId, getAstroStyleId } from '../vite-plugin-build-css/index.js';
+import { addRollupInput } from './add-rollup-input.js';
+import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getAttributes, getTextContent } from './extract-assets.js';
+import { hasSrcSet, isBuildableImage, isBuildableLink, isHoistedScript, isInSrcDirectory } from './util.js';
// This package isn't real ESM, so have to coerce it
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
@@ -25,7 +26,6 @@ const ASTRO_PAGE_PREFIX = '@astro-page';
const ASTRO_SCRIPT_PREFIX = '@astro-script';
const ASTRO_EMPTY = '@astro-empty';
-const STATUS_CODE_REGEXP = /^[0-9]{3}$/;
interface PluginOptions {
astroConfig: AstroConfig;
@@ -487,14 +487,7 @@ export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
const outHTML = parse5.serialize(document);
const name = pathname.substr(1);
- let outPath: string;
-
- // Output directly to 404.html rather than 404/index.html
- if (astroConfig.buildOptions.pageUrlFormat === 'file' || STATUS_CODE_REGEXP.test(name)) {
- outPath = `${removeEndingForwardSlash(name || 'index')}.html`;
- } else {
- outPath = npath.posix.join(name, 'index.html');
- }
+ const outPath = getOutputFilename(astroConfig, name);
this.emitFile({
fileName: outPath,
diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js
index d84ca56e4..c23fca34f 100644
--- a/packages/astro/test/cli.test.js
+++ b/packages/astro/test/cli.test.js
@@ -28,7 +28,7 @@ describe('astro cli', () => {
it('astro build', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const proc = await cli('build', '--project-root', fileURLToPath(projectRootURL));
- expect(proc.stdout).to.include('Done');
+ expect(proc.stdout).to.include('Complete');
});
it('astro dev welcome', async () => {
diff --git a/packages/astro/test/config-validate.test.js b/packages/astro/test/config-validate.test.js
index b1079b6f4..c543a013c 100644
--- a/packages/astro/test/config-validate.test.js
+++ b/packages/astro/test/config-validate.test.js
@@ -1,7 +1,8 @@
import { expect } from 'chai';
import { z } from 'zod';
import stripAnsi from 'strip-ansi';
-import { formatConfigError, validateConfig } from '../dist/core/config.js';
+import { formatConfigErrorMessage } from '../dist/core/messages.js';
+import { validateConfig } from '../dist/core/config.js';
describe('Config Validation', () => {
it('empty user config is valid', async () => {
@@ -22,7 +23,7 @@ describe('Config Validation', () => {
it('A validation error can be formatted correctly', async () => {
const configError = await validateConfig({ buildOptions: { sitemap: 42 } }, process.cwd()).catch((err) => err);
expect(configError instanceof z.ZodError).to.equal(true);
- const formattedError = stripAnsi(formatConfigError(configError));
+ const formattedError = stripAnsi(formatConfigErrorMessage(configError));
expect(formattedError).to.equal(
`[config] Astro found issue(s) with your configuration:
! buildOptions.sitemap Expected boolean, received number.`
@@ -37,7 +38,7 @@ describe('Config Validation', () => {
};
const configError = await validateConfig(veryBadConfig, process.cwd()).catch((err) => err);
expect(configError instanceof z.ZodError).to.equal(true);
- const formattedError = stripAnsi(formatConfigError(configError));
+ const formattedError = stripAnsi(formatConfigErrorMessage(configError));
expect(formattedError).to.equal(
`[config] Astro found issue(s) with your configuration:
! pages Expected string, received object.