diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 805317dfcf0a..abe62b13a89e 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -19,6 +19,18 @@ export interface AstroComponentMetadata { componentExport?: { value: string; namespace?: boolean }; } +/** The flags supported by the Astro CLI */ +export interface CLIFlags { + projectRoot?: string; + site?: string; + sitemap?: boolean; + hostname?: string; + port?: number; + config?: string; + experimentalStaticBuild?: boolean; + drafts?: boolean; +} + /** * Astro.* available in all components * Docs: https://docs.astro.build/reference/api-reference/#astro-global @@ -115,6 +127,11 @@ export interface AstroUserConfig { * Default: false */ drafts?: boolean; + /** + * Experimental: Enables "static build mode" for faster builds. + * Default: false + */ + experimentalStaticBuild?: boolean; }; /** Options for the development server run with `astro dev`. */ devOptions?: { diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 0031a08aefed..e764e96b7df2 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -15,54 +15,7 @@ import { check } from './check.js'; import { formatConfigError, loadConfig } from '../core/config.js'; type Arguments = yargs.Arguments; -type cliCommand = 'help' | 'version' | 'dev' | 'build' | 'preview' | 'reload' | 'check'; -interface CLIState { - cmd: cliCommand; - options: { - projectRoot?: string; - site?: string; - sitemap?: boolean; - hostname?: string; - port?: number; - config?: string; - experimentalStaticBuild?: boolean; - drafts?: boolean; - }; -} - -/** Determine which action the user requested */ -function resolveArgs(flags: Arguments): CLIState { - const options: CLIState['options'] = { - projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot : undefined, - site: typeof flags.site === 'string' ? flags.site : undefined, - sitemap: typeof flags.sitemap === 'boolean' ? flags.sitemap : undefined, - port: typeof flags.port === 'number' ? flags.port : undefined, - config: typeof flags.config === 'string' ? flags.config : undefined, - hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined, - experimentalStaticBuild: typeof flags.experimentalStaticBuild === 'boolean' ? flags.experimentalStaticBuild : false, - drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false, - }; - - if (flags.version) { - return { cmd: 'version', options }; - } else if (flags.help) { - return { cmd: 'help', options }; - } - - const cmd = flags._[2]; - switch (cmd) { - case 'dev': - return { cmd: 'dev', options }; - case 'build': - return { cmd: 'build', options }; - case 'preview': - return { cmd: 'preview', options }; - case 'check': - return { cmd: 'check', options }; - default: - return { cmd: 'help', options }; - } -} +type CLICommand = 'help' | 'version' | 'dev' | 'build' | 'preview' | 'reload' | 'check'; /** Display --help flag */ function printHelp() { @@ -95,24 +48,28 @@ async function printVersion() { console.log(pkgVersion); } -/** Merge CLI flags & config options (CLI flags take priority) */ -function mergeCLIFlags(astroConfig: AstroConfig, flags: CLIState['options']) { - if (typeof flags.sitemap === 'boolean') astroConfig.buildOptions.sitemap = flags.sitemap; - if (typeof flags.site === 'string') astroConfig.buildOptions.site = flags.site; - if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port; - if (typeof flags.hostname === 'string') astroConfig.devOptions.hostname = flags.hostname; - if (typeof flags.experimentalStaticBuild === 'boolean') astroConfig.buildOptions.experimentalStaticBuild = flags.experimentalStaticBuild; - if (typeof flags.drafts === 'boolean') astroConfig.buildOptions.drafts = flags.drafts; +/** Determine which command the user requested */ +function resolveCommand(flags: Arguments): CLICommand { + if (flags.version) { + return 'version'; + } else if (flags.help) { + return 'help'; + } + const cmd = flags._[2]; + const supportedCommands = new Set(['dev', 'build', 'preview', 'check']); + if (supportedCommands.has(cmd)) { + return cmd as 'dev' | 'build' | 'preview' | 'check'; + } + return 'help'; } /** The primary CLI action */ export async function cli(args: string[]) { const flags = yargs(args); - const state = resolveArgs(flags); - const options = { ...state.options }; - const projectRoot = options.projectRoot || flags._[3]; + const cmd = resolveCommand(flags); + const projectRoot = flags.projectRoot || flags._[3]; - switch (state.cmd) { + switch (cmd) { case 'help': printHelp(); return process.exit(0); @@ -131,8 +88,7 @@ export async function cli(args: string[]) { if (flags.silent) logging.level = 'silent'; let config: AstroConfig; try { - config = await loadConfig({ cwd: projectRoot, filename: options.config }); - mergeCLIFlags(config, options); + config = await loadConfig({ cwd: projectRoot, flags }); } catch (err) { if (err instanceof z.ZodError) { console.error(formatConfigError(err)); @@ -142,7 +98,7 @@ export async function cli(args: string[]) { process.exit(1); } - switch (state.cmd) { + switch (cmd) { case 'dev': { try { await devServer(config, { logging }); @@ -154,6 +110,7 @@ export async function cli(args: string[]) { return; } + case 'build': { try { await build(config, { logging }); @@ -163,10 +120,12 @@ export async function cli(args: string[]) { } return; } + case 'check': { const ret = await check(config); return process.exit(ret); } + case 'preview': { try { await preview(config, { logging }); // this will keep running @@ -175,8 +134,9 @@ export async function cli(args: string[]) { } return; } + default: { - throw new Error(`Error running ${state.cmd}`); + throw new Error(`Error running ${cmd}`); } } } diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 6d5fd9a72692..4c0b75449023 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -1,4 +1,5 @@ -import type { AstroConfig, AstroUserConfig } from '../@types/astro'; +import type { AstroConfig, AstroUserConfig, CLIFlags } from '../@types/astro'; +import type { Arguments as Flags } from 'yargs-parser'; import * as colors from 'kleur/colors'; import path from 'path'; @@ -116,19 +117,47 @@ function addTrailingSlash(str: string): string { return str.replace(/\/*$/, '/'); } +/** Convert the generic "yargs" flag object into our own, custom TypeScript object. */ +function resolveFlags(flags: Partial): CLIFlags { + return { + projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot : undefined, + site: typeof flags.site === 'string' ? flags.site : undefined, + sitemap: typeof flags.sitemap === 'boolean' ? flags.sitemap : undefined, + port: typeof flags.port === 'number' ? flags.port : undefined, + config: typeof flags.config === 'string' ? flags.config : undefined, + hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined, + experimentalStaticBuild: typeof flags.experimentalStaticBuild === 'boolean' ? flags.experimentalStaticBuild : false, + drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false, + }; +} + +/** Merge CLI flags & user config object (CLI flags take priority) */ +function mergeCLIFlags(astroConfig: AstroUserConfig, flags: CLIFlags) { + astroConfig.buildOptions = astroConfig.buildOptions || {}; + astroConfig.devOptions = astroConfig.devOptions || {}; + if (typeof flags.sitemap === 'boolean') astroConfig.buildOptions.sitemap = flags.sitemap; + if (typeof flags.site === 'string') astroConfig.buildOptions.site = flags.site; + if (typeof flags.port === 'number') astroConfig.devOptions.port = flags.port; + if (typeof flags.hostname === 'string') astroConfig.devOptions.hostname = flags.hostname; + if (typeof flags.experimentalStaticBuild === 'boolean') astroConfig.buildOptions.experimentalStaticBuild = flags.experimentalStaticBuild; + if (typeof flags.drafts === 'boolean') astroConfig.buildOptions.drafts = flags.drafts; + return astroConfig; +} + interface LoadConfigOptions { cwd?: string; - filename?: string; + flags?: Flags; } /** Attempt to load an `astro.config.mjs` file */ -export async function loadConfig(options: LoadConfigOptions): Promise { - const root = options.cwd ? path.resolve(options.cwd) : process.cwd(); +export async function loadConfig(configOptions: LoadConfigOptions): Promise { + const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd(); + const flags = resolveFlags(configOptions.flags || {}); let userConfig: AstroUserConfig = {}; let userConfigPath: string | undefined; - if (options.filename) { - userConfigPath = /^\.*\//.test(options.filename) ? options.filename : `./${options.filename}`; + if (flags?.config) { + userConfigPath = /^\.*\//.test(flags.config) ? flags.config : `./${flags.config}`; userConfigPath = fileURLToPath(new URL(userConfigPath, `file://${root}/`)); } // Automatically load config file using Proload @@ -138,7 +167,9 @@ export async function loadConfig(options: LoadConfigOptions): Promise