summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Fred K. Schott <fkschott@gmail.com> 2021-09-03 10:47:12 -0700
committerGravatar GitHub <noreply@github.com> 2021-09-03 17:47:12 +0000
commit3b4bbdc98df0ae12750617ec6ca16107a3ec3950 (patch)
tree028f3ce8c49af62f7988f5dd4e7b7a32f65a4baa
parentac2c00e99b0fb9a85a95442fcc52800dc5201fcb (diff)
downloadastro-3b4bbdc98df0ae12750617ec6ca16107a3ec3950.tar.gz
astro-3b4bbdc98df0ae12750617ec6ca16107a3ec3950.tar.zst
astro-3b4bbdc98df0ae12750617ec6ca16107a3ec3950.zip
Format config errors for humans (#1298)
* format config errors * fix bad root
-rw-r--r--.changeset/cuddly-feet-hug.md5
-rw-r--r--packages/astro/package.json3
-rw-r--r--packages/astro/src/cli.ts15
-rw-r--r--packages/astro/src/config.ts14
-rw-r--r--packages/astro/src/runtime.ts14
-rw-r--r--packages/astro/test/config-validate.test.js42
-rw-r--r--www/astro.config.mjs13
7 files changed, 92 insertions, 14 deletions
diff --git a/.changeset/cuddly-feet-hug.md b/.changeset/cuddly-feet-hug.md
new file mode 100644
index 000000000..6cba0d466
--- /dev/null
+++ b/.changeset/cuddly-feet-hug.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Add human readable config verification errors
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 531f8fdbc..35be76d9b 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -113,7 +113,8 @@
"@types/sass": "^1.16.0",
"@types/yargs-parser": "^20.2.0",
"astro-scripts": "0.0.1",
- "is-windows": "^1.0.2"
+ "is-windows": "^1.0.2",
+ "strip-ansi": "^7.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0",
diff --git a/packages/astro/src/cli.ts b/packages/astro/src/cli.ts
index 17dfea668..348828a26 100644
--- a/packages/astro/src/cli.ts
+++ b/packages/astro/src/cli.ts
@@ -1,12 +1,11 @@
/* eslint-disable no-console */
-import type { AstroConfig } from './@types/astro';
-
-import * as colors from 'kleur/colors';
import { promises as fsPromises } from 'fs';
+import * as colors from 'kleur/colors';
import yargs from 'yargs-parser';
-
-import { loadConfig } from './config.js';
+import { z } from 'zod';
+import type { AstroConfig } from './@types/astro';
import { build } from './build.js';
+import { formatConfigError, loadConfig } from './config.js';
import devServer from './dev.js';
import { preview } from './preview.js';
import { reload } from './reload.js';
@@ -113,7 +112,11 @@ async function runCommand(rawRoot: string, cmd: (a: AstroConfig, opts: any) => P
return cmd(astroConfig, options);
} catch (err) {
- console.error(colors.red(err.toString() || err));
+ if (err instanceof z.ZodError) {
+ console.log(formatConfigError(err));
+ } else {
+ console.error(colors.red(err.toString() || err));
+ }
process.exit(1);
}
}
diff --git a/packages/astro/src/config.ts b/packages/astro/src/config.ts
index 3e20559f8..65bd4e400 100644
--- a/packages/astro/src/config.ts
+++ b/packages/astro/src/config.ts
@@ -1,6 +1,8 @@
import { existsSync } from 'fs';
import getPort from 'get-port';
+import * as colors from 'kleur/colors';
import path from 'path';
+import { pathToFileURL } from 'url';
import { z } from 'zod';
import { AstroConfig, AstroUserConfig } from './@types/astro';
import { addTrailingSlash } from './util.js';
@@ -70,8 +72,8 @@ export const AstroConfigSchema = z.object({
});
/** Turn raw config values into normalized values */
-async function validateConfig(userConfig: any, root: string): Promise<AstroConfig> {
- const fileProtocolRoot = `file://${root}/`;
+export async function validateConfig(userConfig: any, root: string): Promise<AstroConfig> {
+ const fileProtocolRoot = pathToFileURL(root + path.sep);
// We need to extend the global schema to add transforms that are relative to root.
// This is type checked against the global schema to make sure we still match.
const AstroConfigRelativeSchema = AstroConfigSchema.extend({
@@ -109,6 +111,10 @@ export async function loadConfig(rawRoot: string | undefined, configFileName = '
userConfig = (await import(astroConfigPath.href)).default;
}
// normalize, validate, and return
- const config = await validateConfig(userConfig, root);
- return config;
+ return validateConfig(userConfig, root);
+}
+
+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')}`;
}
diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts
index e1770d6e9..c10a3bce4 100644
--- a/packages/astro/src/runtime.ts
+++ b/packages/astro/src/runtime.ts
@@ -14,9 +14,11 @@ import {
startServer as startSnowpackServer,
} from 'snowpack';
import { fileURLToPath } from 'url';
-import type { AstroConfig, RSSFunctionArgs, GetStaticPathsArgs, GetStaticPathsResult, ManifestData, Params, RuntimeMode } from './@types/astro';
+import { z } from 'zod';
+import type { AstroConfig, GetStaticPathsArgs, GetStaticPathsResult, ManifestData, Params, RSSFunctionArgs, RuntimeMode } from './@types/astro';
import { generatePaginateFunction } from './build/paginate.js';
import { canonicalURL, getSrcPath, stopTimer } from './build/util.js';
+import { formatConfigError } from './config.js';
import { ConfigManager } from './config_manager.js';
import snowpackExternals from './external.js';
import { debug, info, LogOptions } from './logger.js';
@@ -48,6 +50,7 @@ type LoadResultSuccess = {
type LoadResultNotFound = { statusCode: 404; error: Error };
type LoadResultError = { statusCode: 500 } & (
| { type: 'parse-error'; error: ICompileError }
+ | { type: 'config-error'; error: z.ZodError }
| { type: 'ssr'; error: Error }
| { type: 'not-found'; error: ICompileError }
| { type: 'unknown'; error: Error }
@@ -173,6 +176,15 @@ async function load(config: AstroRuntimeConfig, rawPathname: string | undefined)
rss: undefined, // TODO: Add back rss support
};
} catch (err) {
+ if (err instanceof z.ZodError) {
+ console.log(formatConfigError(err));
+ return {
+ statusCode: 500,
+ type: 'config-error',
+ error: err,
+ };
+ }
+
if (err.code === 'parse-error' || err instanceof SyntaxError) {
return {
statusCode: 500,
diff --git a/packages/astro/test/config-validate.test.js b/packages/astro/test/config-validate.test.js
new file mode 100644
index 000000000..3c9bdde36
--- /dev/null
+++ b/packages/astro/test/config-validate.test.js
@@ -0,0 +1,42 @@
+import { z } from 'zod';
+import { suite } from 'uvu';
+import * as assert from 'uvu/assert';
+import stripAnsi from 'strip-ansi';
+import { formatConfigError, validateConfig } from '#astro/config';
+
+const ConfigValidate = suite('Config Validation');
+
+ConfigValidate('empty user config is valid', async (context) => {
+ const configError = await validateConfig({}, process.cwd()).catch(err => err);
+ assert.ok(!(configError instanceof Error));
+});
+
+ConfigValidate('Zod errors are returned when invalid config is used', async (context) => {
+ const configError = await validateConfig({buildOptions: {sitemap: 42}}, process.cwd()).catch(err => err);
+ assert.ok(configError instanceof z.ZodError);
+});
+
+ConfigValidate('A validation error can be formatted correctly', async (context) => {
+ const configError = await validateConfig({buildOptions: {sitemap: 42}}, process.cwd()).catch(err => err);
+ assert.ok(configError instanceof z.ZodError);
+ const formattedError = stripAnsi(formatConfigError(configError));
+ assert.equal(formattedError, `[config] Astro found issue(s) with your configuration:
+ ! buildOptions.sitemap Expected boolean, received number.`);
+});
+
+ConfigValidate('Multiple validation errors can be formatted correctly', async (context) => {
+ const veryBadConfig = {
+ renderers: [42],
+ buildOptions: {pageUrlFormat: 'invalid'},
+ pages: {},
+ };
+ const configError = await validateConfig(veryBadConfig, process.cwd()).catch(err => err);
+ assert.ok(configError instanceof z.ZodError);
+ const formattedError = stripAnsi(formatConfigError(configError));
+ assert.equal(formattedError, `[config] Astro found issue(s) with your configuration:
+ ! pages Expected string, received object.
+ ! renderers.0 Expected string, received number.
+ ! buildOptions.pageUrlFormat Invalid input.`);
+});
+
+ConfigValidate.run();
diff --git a/www/astro.config.mjs b/www/astro.config.mjs
index d13e39e6f..34fc3cd74 100644
--- a/www/astro.config.mjs
+++ b/www/astro.config.mjs
@@ -1,6 +1,15 @@
-export default {
+// Full Astro Configuration API Documentation:
+// https://docs.astro.build/reference/configuration-reference
+
+// @type-check enabled!
+// VSCode and other TypeScript-enabled text editors will provide auto-completion,
+// helpful tooltips, and warnings if your exported object is invalid.
+// You can disable this by removing "@ts-check" and `@type` comments below.
+
+// @ts-check
+export default /** @type {import('astro').AstroUserConfig} */ ({
buildOptions: {
sitemap: true,
site: 'https://astro.build/',
},
-};
+});