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 {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the lit renderer to support LitHTML components and templates.
renderers: ['@astrojs/renderer-lit'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable many renderers to support all different kinds of components.
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the React renderer to support React JSX components.
renderers: ['@astrojs/renderer-react'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Solid renderer to support Solid JSX components.
renderers: ['@astrojs/renderer-solid'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Svelte renderer to support Svelte components.
renderers: ['@astrojs/renderer-svelte'],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Vue renderer to support Vue components.
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: [],
};
});

View file

@ -1,15 +1,13 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
// 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} */ ({
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

@ -1,14 +1,12 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
};
// 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} */ ({
// ...
});

View file

@ -1,12 +1,14 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
// 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} */ ({
// Enable Custom Markdown options, plugins, etc.
markdownOptions: {
remarkPlugins: ['remark-code-titles', 'remark-slug', ['remark-autolink-headings', { behavior: 'prepend' }]],
rehypePlugins: [
@ -14,8 +16,4 @@ export default {
['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 {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// 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'],
};
// 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} */ ({
// Enable many renderers to support all different kinds of components.
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
});

View file

@ -1,15 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
// 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} */ ({
// Enable Tailwind by telling Astro where your Tailwind config file lives.
devOptions: {
// port: 3000, // The port to run the dev server on.
tailwindConfig: './tailwind.config.js', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
tailwindConfig: './tailwind.config.js',
},
// Enable the Preact renderer to support Preact JSX components.
renderers: ['@astrojs/renderer-preact'],
};
});

View file

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

View file

@ -1,7 +1,7 @@
import type { ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier } from '@babel/types';
import type { AstroMarkdownOptions } from '@astrojs/markdown-support';
import type { AstroConfig } from './config';
import type { AstroUserConfig, AstroConfig } from './config';
export { AstroUserConfig, AstroConfig };
export interface RouteData {
type: 'page';
pattern: RegExp;
@ -14,28 +14,7 @@ export interface RouteData {
export interface ManifestData {
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 {
name: string;

View file

@ -1,32 +1,39 @@
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.
* Default: '.' (current working directory)
*/
projectRoot: URL;
projectRoot?: string;
/**
* Path to the `astro build` output.
* Default: './dist'
*/
dist: string;
dist?: string;
/**
* Path to all of your Astro components, pages, and data.
* Default: './src'
*/
src: URL;
src?: string;
/**
* Path to your Astro/Markdown pages. Each file in this directory
* becomes a page in your final build.
* Default: './src/pages'
*/
pages: URL;
pages?: string;
/**
* 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.
* Default: './public'
*/
public: URL;
public?: string;
/**
* Framework component renderers enable UI framework rendering (static and dynamic).
* When you define this in your configuration, all other defaults are disabled.
@ -41,26 +48,26 @@ export interface AstroConfig {
/** Options for rendering markdown content */
markdownOptions?: Partial<AstroMarkdownOptions>;
/** Options specific to `astro build` */
buildOptions: {
buildOptions?: {
/** Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. */
site?: string;
/** Generate an automatically-generated sitemap for your build.
* Default: true
*/
sitemap: boolean;
sitemap?: boolean;
/**
* 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 false, Astro will generate a matching HTML file (ex: "/foo.html") instead of a directory.
* Default: true
* Control the output file URL format of each page.
* If 'file', 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: 'directory'
*/
pageDirectoryUrl: boolean;
pageUrlFormat?: 'file' | 'directory';
};
/** Options for the development server run with `astro dev`. */
devOptions: {
devOptions?: {
hostname?: string;
/** The port to run the dev server on. */
port: number;
port?: number;
/** Path to tailwind.config.js, if used */
tailwindConfig?: string;
/**
@ -70,6 +77,20 @@ export interface AstroConfig {
* 'ignore' - Match URLs regardless of whether a trailing "/" exists
* 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 */
export async function build(astroConfig: AstroConfig, logging: LogOptions = defaultLogging): Promise<0 | 1> {
const { projectRoot } = astroConfig;
const dist = new URL(astroConfig.dist + '/', projectRoot);
const buildState: BuildOutput = {};
const depTree: BundleMap = {};
const timer: Record<string, number> = {};
@ -54,7 +53,7 @@ export async function build(astroConfig: AstroConfig, logging: LogOptions = defa
try {
// 0. erase build directory
await del(fileURLToPath(dist));
await del(fileURLToPath(astroConfig.dist));
/**
* 1. Build Pages
@ -197,7 +196,7 @@ ${stack}
timer.sitemap = performance.now();
info(logging, 'build', yellow('! creating sitemap...'));
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.writeFile(sitemapPath, sitemap, 'utf8');
info(logging, 'build', green('✔'), 'sitemap built.');
@ -208,7 +207,7 @@ ${stack}
timer.write = performance.now();
await Promise.all(
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));
await fs.promises.mkdir(parentDir, { recursive: true });
await fs.promises.writeFile(outPath, buildState[id].contents, buildState[id].encoding);
@ -229,7 +228,7 @@ ${stack}
await Promise.all(
publicFiles.map(async (filepath) => {
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.copyFile(srcPath, distPath);
})
@ -249,7 +248,7 @@ ${stack}
info(logging, 'build', yellow(`! bundling...`));
if (jsImports.size > 0) {
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 });
debug(logging, 'build', `bundled JS [${stopTimer(timer.bundleJS)}]`);
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') {
return '/404.html';
}
if (path === '/') {
return '/index.html';
}
if (pageDirectoryUrl) {
if (pageUrlFormat === 'directory') {
return _path.posix.join(path, '/index.html');
}
return `${path}.html`;
@ -68,7 +68,7 @@ export async function buildStaticPage({ astroConfig, buildState, path, route, as
err.filename = fileURLToPath(location.fileURL);
throw err;
}
buildState[formatOutFile(path, astroConfig.buildOptions.pageDirectoryUrl)] = {
buildState[formatOutFile(path, astroConfig.buildOptions.pageUrlFormat)] = {
srcPath: location.fileURL,
contents: result.contents,
contentType: 'text/html',

View file

@ -1,6 +1,6 @@
import type { Ast, Script, Style, TemplateNode, Expression } from '@astrojs/parser';
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 { Attribute } from './interfaces';
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>;
let { content: rendered } = await renderMarkdown(dedent(md), {
...(markdownOptions as AstroMarkdownOptions),
...markdownOptions,
$: { 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 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 */
const type = (thing: any): string => (Array.isArray(thing) ? 'Array' : typeof thing);
/** Throws error if a user provided an invalid config. Manually-implemented to avoid a heavy validation library. */
function validateConfig(config: any): void {
// basic
if (config === undefined || config === null) throw new Error(`[config] Config empty!`);
if (typeof config !== 'object') throw new Error(`[config] Expected object, received ${typeof config}`);
// strings
for (const key of ['projectRoot', 'pages', 'dist', 'public']) {
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'string') {
throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected string, received ${type(config[key])}.`);
}
}
// booleans
for (const key of ['sitemap']) {
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'boolean') {
throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected boolean, received ${type(config[key])}.`);
}
}
// buildOptions
if (config.buildOptions) {
// buildOptions.site
if (config.buildOptions.site !== undefined) {
if (typeof config.buildOptions.site !== 'string') throw new Error(`[config] buildOptions.site is not a string`);
try {
new URL(config.buildOptions.site);
} catch (err) {
throw new Error('[config] buildOptions.site must be a valid URL');
}
}
}
// devOptions
if (typeof config.devOptions?.port !== 'number') {
throw new Error(`[config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`);
}
if (typeof config.devOptions?.hostname !== 'string') {
throw new Error(`[config] devOptions.hostname: Expected string, received ${type(config.devOptions?.hostname)}`);
}
if (config.devOptions?.tailwindConfig !== undefined && typeof config.devOptions?.tailwindConfig !== 'string') {
throw new Error(`[config] devOptions.tailwindConfig: Expected string, received ${type(config.devOptions?.tailwindConfig)}`);
}
}
/** Set default config values */
async function configDefaults(userConfig?: any): Promise<any> {
const config: any = { ...(userConfig || {}) };
if (config.projectRoot === undefined) config.projectRoot = '.';
if (config.src === undefined) config.src = './src';
if (config.pages === undefined) config.pages = './src/pages';
if (config.dist === undefined) config.dist = './dist';
if (config.public === undefined) config.public = './public';
if (config.devOptions === undefined) config.devOptions = {};
if (config.devOptions.port === undefined) config.devOptions.port = await getPort({ port: getPort.makeRange(3000, 3050) });
if (config.devOptions.hostname === undefined) config.devOptions.hostname = 'localhost';
if (config.devOptions.trailingSlash === undefined) config.devOptions.trailingSlash = 'ignore';
if (config.buildOptions === undefined) config.buildOptions = {};
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;
}
export const AstroConfigSchema = z.object({
projectRoot: z
.string()
.optional()
.default('.')
.transform((val) => new URL(val)),
src: z
.string()
.optional()
.default('./src')
.transform((val) => new URL(val)),
pages: z
.string()
.optional()
.default('./src/pages')
.transform((val) => new URL(val)),
public: z
.string()
.optional()
.default('./public')
.transform((val) => new URL(val)),
dist: z
.string()
.optional()
.default('./dist')
.transform((val) => new URL(val)),
renderers: z.array(z.string()).optional().default(['@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-react', '@astrojs/renderer-preact']),
markdownOptions: z
.object({
footnotes: z.boolean().optional(),
gfm: z.boolean().optional(),
remarkPlugins: z.array(z.any()).optional(),
rehypePlugins: z.array(z.any()).optional(),
})
.optional()
.default({}),
buildOptions: z
.object({
site: z.string().optional(),
sitemap: z.boolean().optional().default(true),
pageUrlFormat: z
.union([z.literal('file'), z.literal('directory')])
.optional()
.default('directory'),
})
.optional()
.default({}),
devOptions: z
.object({
hostname: z.string().optional().default('localhost'),
port: z
.number()
.optional()
.transform((val) => val || getPort({ port: getPort.makeRange(3000, 3050) })),
tailwindConfig: z.string().optional(),
trailingSlash: z
.union([z.literal('always'), z.literal('never'), z.literal('ignore')])
.optional()
.default('ignore'),
})
.optional()
.default({}),
});
/** Turn raw config values into normalized values */
function normalizeConfig(userConfig: any, root: string): AstroConfig {
const config: any = { ...(userConfig || {}) };
async function validateConfig(userConfig: any, root: string): Promise<AstroConfig> {
const fileProtocolRoot = `file://${root}/`;
config.projectRoot = new URL(config.projectRoot + '/', fileProtocolRoot);
config.src = new URL(config.src + '/', fileProtocolRoot);
config.pages = new URL(config.pages + '/', fileProtocolRoot);
config.public = new URL(config.public + '/', fileProtocolRoot);
return config as AstroConfig;
// 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({
projectRoot: z
.string()
.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 */
export async function loadConfig(rawRoot: string | undefined, configFileName = 'astro.config.mjs'): Promise<AstroConfig> {
const root = rawRoot ? path.resolve(rawRoot) : process.cwd();
const astroConfigPath = new URL(`./${configFileName}`, `file://${root}/`);
// load
let config: any;
let userConfig: AstroUserConfig = {};
// Load a user-config, if one exists and is provided
if (existsSync(astroConfigPath)) {
config = await configDefaults((await import(astroConfigPath.href)).default);
} else {
config = await configDefaults();
userConfig = (await import(astroConfigPath.href)).default;
}
// validate
validateConfig(config);
// normalize
config = normalizeConfig(config, root);
return config as AstroConfig;
// normalize, validate, and return
const config = await validateConfig(userConfig, root);
return config;
}

View file

@ -351,7 +351,7 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
},
buildOptions: {
baseUrl: astroConfig.buildOptions.site || '/', // note: Snowpack needs this fallback
out: astroConfig.dist,
out: fileURLToPath(astroConfig.dist),
},
packageOptions: {
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 { setupBuild } from './helpers.js';
const PageDirectoryUrl = suite('pageDirectoryUrl');
const PageDirectoryUrl = suite('pageUrlFormat');
setupBuild(PageDirectoryUrl, './fixtures/astro-page-directory-url');

View file

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

View file

@ -85,7 +85,7 @@ export function setupBuild(Suite, fixturePath) {
context.build = () => astroBuild(astroConfig, { level: 'error' });
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' });
};

View file

@ -10989,6 +10989,11 @@ zepto@^1.2.0:
resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"
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:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"