Add zod schema validation (#1198)

* add zod schema validation

* update pageUrlFormat config name

* add trailing slash support to config
This commit is contained in:
Fred K. Schott 2021-08-23 14:07:03 -07:00 committed by GitHub
parent 010c71e16a
commit f9cd031033
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 377 additions and 360 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix TypeScript "types" reference in package.json

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Improve schema validation using zod

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Preact renderer to support Preact JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Preact renderer to support Preact JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Preact renderer to support Preact JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the lit renderer to support LitHTML components and templates.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-lit'], renderers: ['@astrojs/renderer-lit'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable many renderers to support all different kinds of components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'], renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Preact renderer to support Preact JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the React renderer to support React JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-react'], renderers: ['@astrojs/renderer-react'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Solid renderer to support Solid JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-solid'], renderers: ['@astrojs/renderer-solid'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Svelte renderer to support Svelte components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-svelte'], renderers: ['@astrojs/renderer-svelte'],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Vue renderer to support Vue components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-vue'], renderers: ['@astrojs/renderer-vue'],
}; });

View file

@ -1,3 +1,13 @@
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} */ ({
// Set "renderers" to "[]" to disable all default, builtin component support.
renderers: [], renderers: [],
}; });

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable the Preact renderer to support Preact JSX components.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -1,14 +1,12 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // ...
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js' });
},
};

View file

@ -1,12 +1,14 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
// Enable Custom Markdown options, plugins, etc.
markdownOptions: { markdownOptions: {
remarkPlugins: ['remark-code-titles', 'remark-slug', ['remark-autolink-headings', { behavior: 'prepend' }]], remarkPlugins: ['remark-code-titles', 'remark-slug', ['remark-autolink-headings', { behavior: 'prepend' }]],
rehypePlugins: [ rehypePlugins: [
@ -14,8 +16,4 @@ export default {
['rehype-add-classes', { 'h1,h2,h3': 'title' }], ['rehype-add-classes', { 'h1,h2,h3': 'title' }],
], ],
}, },
devOptions: { });
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
};

View file

@ -1,15 +1,13 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
// sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
devOptions: { export default /** @type {import('astro').AstroUserConfig} */ ({
// port: 3000, // The port to run the dev server on. // Enable many renderers to support all different kinds of components.
// tailwindConfig: './tailwind.config.js', // Path to tailwind.config.js if used, e.g. './tailwind.config.js' renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
}, });
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
};

View file

@ -1,15 +1,17 @@
export default { // Full Astro Configuration API Documentation:
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project. // https://docs.astro.build/reference/configuration-reference
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output // @type-check enabled!
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing. // VSCode and other TypeScript-enabled text editors will provide auto-completion,
buildOptions: { // helpful tooltips, and warnings if your exported object is invalid.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. // You can disable this by removing "@ts-check" and `@type` comments below.
sitemap: true, // Generate sitemap (set to "false" to disable)
}, // @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
// Enable Tailwind by telling Astro where your Tailwind config file lives.
devOptions: { devOptions: {
// port: 3000, // The port to run the dev server on. tailwindConfig: './tailwind.config.js',
tailwindConfig: './tailwind.config.js', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
}, },
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'], renderers: ['@astrojs/renderer-preact'],
}; });

View file

@ -4,7 +4,7 @@
"author": "Skypack", "author": "Skypack",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"types": "./dist/types", "types": "./dist/types/@types/public.d.ts",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/snowpackjs/astro.git", "url": "https://github.com/snowpackjs/astro.git",
@ -97,7 +97,8 @@
"supports-esm": "^1.0.0", "supports-esm": "^1.0.0",
"tiny-glob": "^0.2.8", "tiny-glob": "^0.2.8",
"unified": "^9.2.1", "unified": "^9.2.1",
"yargs-parser": "^20.2.7" "yargs-parser": "^20.2.7",
"zod": "^3.8.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/types": "^7.14.0", "@babel/types": "^7.14.0",

View file

@ -1,7 +1,7 @@
import type { ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier } from '@babel/types'; import type { ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier } from '@babel/types';
import type { AstroMarkdownOptions } from '@astrojs/markdown-support'; import type { AstroUserConfig, AstroConfig } from './config';
import type { AstroConfig } from './config';
export { AstroUserConfig, AstroConfig };
export interface RouteData { export interface RouteData {
type: 'page'; type: 'page';
pattern: RegExp; pattern: RegExp;
@ -14,28 +14,7 @@ export interface RouteData {
export interface ManifestData { export interface ManifestData {
routes: RouteData[]; routes: RouteData[];
} }
export interface AstroConfigRaw {
dist: string;
projectRoot: string;
src: string;
pages: string;
public: string;
jsx?: string;
}
export { AstroMarkdownOptions, AstroConfig };
export type AstroUserConfig = Omit<AstroConfig, 'buildOptions' | 'devOptions'> & {
buildOptions: {
sitemap: boolean;
};
devOptions: {
hostname?: string;
port?: number;
projectRoot?: string;
tailwindConfig?: string;
};
};
export interface JsxItem { export interface JsxItem {
name: string; name: string;

View file

@ -1,32 +1,39 @@
import type { AstroMarkdownOptions } from '@astrojs/markdown-support'; import type { AstroMarkdownOptions } from '@astrojs/markdown-support';
export interface AstroConfig { import type { AstroConfigSchema } from '../config';
import type { z } from 'zod';
/**
* The Astro User Config Format:
* This is the type interface for your astro.config.mjs default export.
*/
export interface AstroUserConfig {
/** /**
* Where to resolve all URLs relative to. Useful if you have a monorepo project. * Where to resolve all URLs relative to. Useful if you have a monorepo project.
* Default: '.' (current working directory) * Default: '.' (current working directory)
*/ */
projectRoot: URL; projectRoot?: string;
/** /**
* Path to the `astro build` output. * Path to the `astro build` output.
* Default: './dist' * Default: './dist'
*/ */
dist: string; dist?: string;
/** /**
* Path to all of your Astro components, pages, and data. * Path to all of your Astro components, pages, and data.
* Default: './src' * Default: './src'
*/ */
src: URL; src?: string;
/** /**
* Path to your Astro/Markdown pages. Each file in this directory * Path to your Astro/Markdown pages. Each file in this directory
* becomes a page in your final build. * becomes a page in your final build.
* Default: './src/pages' * Default: './src/pages'
*/ */
pages: URL; pages?: string;
/** /**
* Path to your public files. These are copied over into your build directory, untouched. * Path to your public files. These are copied over into your build directory, untouched.
* Useful for favicons, images, and other files that don't need processing. * Useful for favicons, images, and other files that don't need processing.
* Default: './public' * Default: './public'
*/ */
public: URL; public?: string;
/** /**
* Framework component renderers enable UI framework rendering (static and dynamic). * Framework component renderers enable UI framework rendering (static and dynamic).
* When you define this in your configuration, all other defaults are disabled. * When you define this in your configuration, all other defaults are disabled.
@ -41,26 +48,26 @@ export interface AstroConfig {
/** Options for rendering markdown content */ /** Options for rendering markdown content */
markdownOptions?: Partial<AstroMarkdownOptions>; markdownOptions?: Partial<AstroMarkdownOptions>;
/** Options specific to `astro build` */ /** Options specific to `astro build` */
buildOptions: { buildOptions?: {
/** Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. */ /** Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. */
site?: string; site?: string;
/** Generate an automatically-generated sitemap for your build. /** Generate an automatically-generated sitemap for your build.
* Default: true * Default: true
*/ */
sitemap: boolean; sitemap?: boolean;
/** /**
* Control the output file/URL format of each page. * Control the output file URL format of each page.
* If true, Astro will generate a directory with a nested index.html (ex: "/foo/index.html") for each page. * If 'file', Astro will generate a matching HTML file (ex: "/foo.html") instead of a directory.
* If false, Astro will generate a matching HTML file (ex: "/foo.html") instead of a directory. * If 'directory', Astro will generate a directory with a nested index.html (ex: "/foo/index.html") for each page.
* Default: true * Default: 'directory'
*/ */
pageDirectoryUrl: boolean; pageUrlFormat?: 'file' | 'directory';
}; };
/** Options for the development server run with `astro dev`. */ /** Options for the development server run with `astro dev`. */
devOptions: { devOptions?: {
hostname?: string; hostname?: string;
/** The port to run the dev server on. */ /** The port to run the dev server on. */
port: number; port?: number;
/** Path to tailwind.config.js, if used */ /** Path to tailwind.config.js, if used */
tailwindConfig?: string; tailwindConfig?: string;
/** /**
@ -70,6 +77,20 @@ export interface AstroConfig {
* 'ignore' - Match URLs regardless of whether a trailing "/" exists * 'ignore' - Match URLs regardless of whether a trailing "/" exists
* Default: 'always' * Default: 'always'
*/ */
trailingSlash: 'always' | 'never' | 'ignore'; trailingSlash?: 'always' | 'never' | 'ignore';
}; };
} }
// NOTE(fks): We choose to keep our hand-generated AstroUserConfig interface so that
// we can add JSDoc-style documentation and link to the definition file in our repo.
// However, Zod comes with the ability to auto-generate AstroConfig from the schema
// above. If we ever get to the point where we no longer need the dedicated
// @types/config.ts file, consider replacing it with the following lines:
//
// export interface AstroUserConfig extends z.input<typeof AstroConfigSchema> {
// markdownOptions?: Partial<AstroMarkdownOptions>;
// }
export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
markdownOptions: Partial<AstroMarkdownOptions>;
}

View file

@ -0,0 +1 @@
export {AstroConfig, AstroUserConfig} from './config';

View file

@ -32,7 +32,6 @@ function isRemoteOrEmbedded(url: string) {
/** The primary build action */ /** The primary build action */
export async function build(astroConfig: AstroConfig, logging: LogOptions = defaultLogging): Promise<0 | 1> { export async function build(astroConfig: AstroConfig, logging: LogOptions = defaultLogging): Promise<0 | 1> {
const { projectRoot } = astroConfig; const { projectRoot } = astroConfig;
const dist = new URL(astroConfig.dist + '/', projectRoot);
const buildState: BuildOutput = {}; const buildState: BuildOutput = {};
const depTree: BundleMap = {}; const depTree: BundleMap = {};
const timer: Record<string, number> = {}; const timer: Record<string, number> = {};
@ -54,7 +53,7 @@ export async function build(astroConfig: AstroConfig, logging: LogOptions = defa
try { try {
// 0. erase build directory // 0. erase build directory
await del(fileURLToPath(dist)); await del(fileURLToPath(astroConfig.dist));
/** /**
* 1. Build Pages * 1. Build Pages
@ -197,7 +196,7 @@ ${stack}
timer.sitemap = performance.now(); timer.sitemap = performance.now();
info(logging, 'build', yellow('! creating sitemap...')); info(logging, 'build', yellow('! creating sitemap...'));
const sitemap = generateSitemap(buildState, astroConfig.buildOptions.site); const sitemap = generateSitemap(buildState, astroConfig.buildOptions.site);
const sitemapPath = new URL('sitemap.xml', dist); const sitemapPath = new URL('sitemap.xml', astroConfig.dist);
await fs.promises.mkdir(path.dirname(fileURLToPath(sitemapPath)), { recursive: true }); await fs.promises.mkdir(path.dirname(fileURLToPath(sitemapPath)), { recursive: true });
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8'); await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
info(logging, 'build', green('✔'), 'sitemap built.'); info(logging, 'build', green('✔'), 'sitemap built.');
@ -208,7 +207,7 @@ ${stack}
timer.write = performance.now(); timer.write = performance.now();
await Promise.all( await Promise.all(
Object.keys(buildState).map(async (id) => { Object.keys(buildState).map(async (id) => {
const outPath = new URL(`.${id}`, dist); const outPath = new URL(`.${id}`, astroConfig.dist);
const parentDir = path.dirname(fileURLToPath(outPath)); const parentDir = path.dirname(fileURLToPath(outPath));
await fs.promises.mkdir(parentDir, { recursive: true }); await fs.promises.mkdir(parentDir, { recursive: true });
await fs.promises.writeFile(outPath, buildState[id].contents, buildState[id].encoding); await fs.promises.writeFile(outPath, buildState[id].contents, buildState[id].encoding);
@ -229,7 +228,7 @@ ${stack}
await Promise.all( await Promise.all(
publicFiles.map(async (filepath) => { publicFiles.map(async (filepath) => {
const srcPath = new URL(filepath, astroConfig.public); const srcPath = new URL(filepath, astroConfig.public);
const distPath = new URL(filepath, dist); const distPath = new URL(filepath, astroConfig.dist);
await fs.promises.mkdir(path.dirname(fileURLToPath(distPath)), { recursive: true }); await fs.promises.mkdir(path.dirname(fileURLToPath(distPath)), { recursive: true });
await fs.promises.copyFile(srcPath, distPath); await fs.promises.copyFile(srcPath, distPath);
}) })
@ -249,7 +248,7 @@ ${stack}
info(logging, 'build', yellow(`! bundling...`)); info(logging, 'build', yellow(`! bundling...`));
if (jsImports.size > 0) { if (jsImports.size > 0) {
timer.bundleJS = performance.now(); timer.bundleJS = performance.now();
const jsStats = await bundleJS(jsImports, { dist: new URL(dist + '/', projectRoot), astroRuntime }); const jsStats = await bundleJS(jsImports, { dist: astroConfig.dist, astroRuntime });
mapBundleStatsToURLStats({ urlStats, depTree, bundleStats: jsStats }); mapBundleStatsToURLStats({ urlStats, depTree, bundleStats: jsStats });
debug(logging, 'build', `bundled JS [${stopTimer(timer.bundleJS)}]`); debug(logging, 'build', `bundled JS [${stopTimer(timer.bundleJS)}]`);
info(logging, 'build', green(``), 'bundling complete.'); info(logging, 'build', green(``), 'bundling complete.');

View file

@ -45,14 +45,14 @@ export async function getStaticPathsForPage({
}; };
} }
function formatOutFile(path: string, pageDirectoryUrl: boolean) { function formatOutFile(path: string, pageUrlFormat: AstroConfig['buildOptions']['pageUrlFormat']) {
if (path === '/404') { if (path === '/404') {
return '/404.html'; return '/404.html';
} }
if (path === '/') { if (path === '/') {
return '/index.html'; return '/index.html';
} }
if (pageDirectoryUrl) { if (pageUrlFormat === 'directory') {
return _path.posix.join(path, '/index.html'); return _path.posix.join(path, '/index.html');
} }
return `${path}.html`; return `${path}.html`;
@ -68,7 +68,7 @@ export async function buildStaticPage({ astroConfig, buildState, path, route, as
err.filename = fileURLToPath(location.fileURL); err.filename = fileURLToPath(location.fileURL);
throw err; throw err;
} }
buildState[formatOutFile(path, astroConfig.buildOptions.pageDirectoryUrl)] = { buildState[formatOutFile(path, astroConfig.buildOptions.pageUrlFormat)] = {
srcPath: location.fileURL, srcPath: location.fileURL,
contents: result.contents, contents: result.contents,
contentType: 'text/html', contentType: 'text/html',

View file

@ -1,6 +1,6 @@
import type { Ast, Script, Style, TemplateNode, Expression } from '@astrojs/parser'; import type { Ast, Script, Style, TemplateNode, Expression } from '@astrojs/parser';
import type { CompileOptions } from '../../@types/compiler'; import type { CompileOptions } from '../../@types/compiler';
import type { AstroConfig, AstroMarkdownOptions, TransformResult, ComponentInfo, Components } from '../../@types/astro'; import type { AstroConfig, TransformResult, ComponentInfo, Components } from '../../@types/astro';
import type { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier, ImportDefaultSpecifier } from '@babel/types'; import type { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier, ImportDefaultSpecifier } from '@babel/types';
import type { Attribute } from './interfaces'; import type { Attribute } from './interfaces';
import eslexer from 'es-module-lexer'; import eslexer from 'es-module-lexer';
@ -566,7 +566,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
} }
const { $scope: scopedClassName } = state.markers.insideMarkdown as Record<'$scope', any>; const { $scope: scopedClassName } = state.markers.insideMarkdown as Record<'$scope', any>;
let { content: rendered } = await renderMarkdown(dedent(md), { let { content: rendered } = await renderMarkdown(dedent(md), {
...(markdownOptions as AstroMarkdownOptions), ...markdownOptions,
$: { scopedClassName: scopedClassName && scopedClassName.slice(1, -1) }, $: { scopedClassName: scopedClassName && scopedClassName.slice(1, -1) },
}); });

View file

@ -1,108 +1,114 @@
import type { AstroConfig } from './@types/astro';
import path from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import getPort from 'get-port'; import getPort from 'get-port';
import path from 'path';
import { z } from 'zod';
import { AstroConfig, AstroUserConfig } from './@types/astro';
import { addTrailingSlash } from './util.js';
/** Type util */ export const AstroConfigSchema = z.object({
const type = (thing: any): string => (Array.isArray(thing) ? 'Array' : typeof thing); projectRoot: z
.string()
/** Throws error if a user provided an invalid config. Manually-implemented to avoid a heavy validation library. */ .optional()
function validateConfig(config: any): void { .default('.')
// basic .transform((val) => new URL(val)),
if (config === undefined || config === null) throw new Error(`[config] Config empty!`); src: z
if (typeof config !== 'object') throw new Error(`[config] Expected object, received ${typeof config}`); .string()
.optional()
// strings .default('./src')
for (const key of ['projectRoot', 'pages', 'dist', 'public']) { .transform((val) => new URL(val)),
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'string') { pages: z
throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected string, received ${type(config[key])}.`); .string()
} .optional()
} .default('./src/pages')
.transform((val) => new URL(val)),
// booleans public: z
for (const key of ['sitemap']) { .string()
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'boolean') { .optional()
throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected boolean, received ${type(config[key])}.`); .default('./public')
} .transform((val) => new URL(val)),
} dist: z
.string()
// buildOptions .optional()
if (config.buildOptions) { .default('./dist')
// buildOptions.site .transform((val) => new URL(val)),
if (config.buildOptions.site !== undefined) { renderers: z.array(z.string()).optional().default(['@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-react', '@astrojs/renderer-preact']),
if (typeof config.buildOptions.site !== 'string') throw new Error(`[config] buildOptions.site is not a string`); markdownOptions: z
try { .object({
new URL(config.buildOptions.site); footnotes: z.boolean().optional(),
} catch (err) { gfm: z.boolean().optional(),
throw new Error('[config] buildOptions.site must be a valid URL'); remarkPlugins: z.array(z.any()).optional(),
} rehypePlugins: z.array(z.any()).optional(),
} })
} .optional()
.default({}),
// devOptions buildOptions: z
if (typeof config.devOptions?.port !== 'number') { .object({
throw new Error(`[config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`); site: z.string().optional(),
} sitemap: z.boolean().optional().default(true),
if (typeof config.devOptions?.hostname !== 'string') { pageUrlFormat: z
throw new Error(`[config] devOptions.hostname: Expected string, received ${type(config.devOptions?.hostname)}`); .union([z.literal('file'), z.literal('directory')])
} .optional()
if (config.devOptions?.tailwindConfig !== undefined && typeof config.devOptions?.tailwindConfig !== 'string') { .default('directory'),
throw new Error(`[config] devOptions.tailwindConfig: Expected string, received ${type(config.devOptions?.tailwindConfig)}`); })
} .optional()
} .default({}),
devOptions: z
/** Set default config values */ .object({
async function configDefaults(userConfig?: any): Promise<any> { hostname: z.string().optional().default('localhost'),
const config: any = { ...(userConfig || {}) }; port: z
.number()
if (config.projectRoot === undefined) config.projectRoot = '.'; .optional()
if (config.src === undefined) config.src = './src'; .transform((val) => val || getPort({ port: getPort.makeRange(3000, 3050) })),
if (config.pages === undefined) config.pages = './src/pages'; tailwindConfig: z.string().optional(),
if (config.dist === undefined) config.dist = './dist'; trailingSlash: z
if (config.public === undefined) config.public = './public'; .union([z.literal('always'), z.literal('never'), z.literal('ignore')])
if (config.devOptions === undefined) config.devOptions = {}; .optional()
if (config.devOptions.port === undefined) config.devOptions.port = await getPort({ port: getPort.makeRange(3000, 3050) }); .default('ignore'),
if (config.devOptions.hostname === undefined) config.devOptions.hostname = 'localhost'; })
if (config.devOptions.trailingSlash === undefined) config.devOptions.trailingSlash = 'ignore'; .optional()
if (config.buildOptions === undefined) config.buildOptions = {}; .default({}),
if (config.buildOptions.pageDirectoryUrl === undefined) config.buildOptions.pageDirectoryUrl = true; });
if (config.markdownOptions === undefined) config.markdownOptions = {};
if (config.buildOptions.sitemap === undefined) config.buildOptions.sitemap = true;
return config;
}
/** Turn raw config values into normalized values */ /** Turn raw config values into normalized values */
function normalizeConfig(userConfig: any, root: string): AstroConfig { async function validateConfig(userConfig: any, root: string): Promise<AstroConfig> {
const config: any = { ...(userConfig || {}) };
const fileProtocolRoot = `file://${root}/`; const fileProtocolRoot = `file://${root}/`;
config.projectRoot = new URL(config.projectRoot + '/', fileProtocolRoot); // We need to extend the global schema to add transforms that are relative to root.
config.src = new URL(config.src + '/', fileProtocolRoot); // This is type checked against the global schema to make sure we still match.
config.pages = new URL(config.pages + '/', fileProtocolRoot); const AstroConfigRelativeSchema = AstroConfigSchema.extend({
config.public = new URL(config.public + '/', fileProtocolRoot); projectRoot: z
.string()
return config as AstroConfig; .default('.')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
src: z
.string()
.default('./src')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
pages: z
.string()
.default('./src/pages')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
public: z
.string()
.default('./public')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
dist: z
.string()
.default('./dist')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
});
return AstroConfigRelativeSchema.parseAsync(userConfig);
} }
/** Attempt to load an `astro.config.mjs` file */ /** Attempt to load an `astro.config.mjs` file */
export async function loadConfig(rawRoot: string | undefined, configFileName = 'astro.config.mjs'): Promise<AstroConfig> { export async function loadConfig(rawRoot: string | undefined, configFileName = 'astro.config.mjs'): Promise<AstroConfig> {
const root = rawRoot ? path.resolve(rawRoot) : process.cwd(); const root = rawRoot ? path.resolve(rawRoot) : process.cwd();
const astroConfigPath = new URL(`./${configFileName}`, `file://${root}/`); const astroConfigPath = new URL(`./${configFileName}`, `file://${root}/`);
let userConfig: AstroUserConfig = {};
// load // Load a user-config, if one exists and is provided
let config: any;
if (existsSync(astroConfigPath)) { if (existsSync(astroConfigPath)) {
config = await configDefaults((await import(astroConfigPath.href)).default); userConfig = (await import(astroConfigPath.href)).default;
} else {
config = await configDefaults();
} }
// normalize, validate, and return
// validate const config = await validateConfig(userConfig, root);
validateConfig(config); return config;
// normalize
config = normalizeConfig(config, root);
return config as AstroConfig;
} }

View file

@ -351,7 +351,7 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
}, },
buildOptions: { buildOptions: {
baseUrl: astroConfig.buildOptions.site || '/', // note: Snowpack needs this fallback baseUrl: astroConfig.buildOptions.site || '/', // note: Snowpack needs this fallback
out: astroConfig.dist, out: fileURLToPath(astroConfig.dist),
}, },
packageOptions: { packageOptions: {
knownEntrypoints, knownEntrypoints,

View file

@ -43,3 +43,14 @@ export function validateGetStaticPathsResult(result: GetStaticPathsResult, loggi
} }
}); });
} }
/** Add / to beginning of string (but dont double-up) */
export function addLeadingSlash(path: string) {
return path.replace(/^\/?/, '/');
}
/** Add / to the end of string (but dont double-up) */
export function addTrailingSlash(path: string) {
return path.replace(/\/?$/, '/');
}

View file

@ -2,7 +2,7 @@ import { suite } from 'uvu';
import * as assert from 'uvu/assert'; import * as assert from 'uvu/assert';
import { setupBuild } from './helpers.js'; import { setupBuild } from './helpers.js';
const PageDirectoryUrl = suite('pageDirectoryUrl'); const PageDirectoryUrl = suite('pageUrlFormat');
setupBuild(PageDirectoryUrl, './fixtures/astro-page-directory-url'); setupBuild(PageDirectoryUrl, './fixtures/astro-page-directory-url');

View file

@ -1,5 +1,5 @@
export default { export default {
buildOptions: { buildOptions: {
pageDirectoryUrl: false pageUrlFormat: 'file'
} }
}; };

View file

@ -85,7 +85,7 @@ export function setupBuild(Suite, fixturePath) {
context.build = () => astroBuild(astroConfig, { level: 'error' }); context.build = () => astroBuild(astroConfig, { level: 'error' });
context.readFile = async (path) => { context.readFile = async (path) => {
const resolved = fileURLToPath(new URL(`${fixturePath}/${astroConfig.dist}${path}`, import.meta.url)); const resolved = fileURLToPath(new URL(path.replace(/^\//, ''), astroConfig.dist));
return readFileSync(resolved, { encoding: 'utf8' }); return readFileSync(resolved, { encoding: 'utf8' });
}; };

View file

@ -10989,6 +10989,11 @@ zepto@^1.2.0:
resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98" resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"
integrity sha1-4Se9nmb9hGvl6rSME5SIL3wOT5g= integrity sha1-4Se9nmb9hGvl6rSME5SIL3wOT5g=
zod@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.8.1.tgz#b1173c3b4ac2a9e06d302ff580e3b41902766b9f"
integrity sha512-u4Uodl7dLh8nXZwqXL1SM5FAl5b4lXYHOxMUVb9lqhlEAZhA2znX+0oW480m0emGFMxpoRHzUncAqRkc4h8ZJA==
zwitch@^1.0.0: zwitch@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"