summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/twenty-oranges-poke.md5
-rw-r--r--packages/astro/e2e/errors.test.js7
-rw-r--r--packages/astro/e2e/test-utils.js3
-rw-r--r--packages/astro/src/@types/astro.ts12
-rw-r--r--packages/astro/src/cli/add/index.ts16
-rw-r--r--packages/astro/src/cli/build/index.ts30
-rw-r--r--packages/astro/src/cli/check/index.ts46
-rw-r--r--packages/astro/src/cli/dev/index.ts46
-rw-r--r--packages/astro/src/cli/flags.ts49
-rw-r--r--packages/astro/src/cli/index.ts23
-rw-r--r--packages/astro/src/cli/info/index.ts10
-rw-r--r--packages/astro/src/cli/load-settings.ts47
-rw-r--r--packages/astro/src/cli/preview/index.ts30
-rw-r--r--packages/astro/src/cli/sync/index.ts27
-rw-r--r--packages/astro/src/cli/throw-and-exit.ts4
-rw-r--r--packages/astro/src/config/index.ts4
-rw-r--r--packages/astro/src/core/build/index.ts60
-rw-r--r--packages/astro/src/core/config/config.ts176
-rw-r--r--packages/astro/src/core/config/index.ts12
-rw-r--r--packages/astro/src/core/config/logging.ts13
-rw-r--r--packages/astro/src/core/config/settings.ts14
-rw-r--r--packages/astro/src/core/dev/container.ts60
-rw-r--r--packages/astro/src/core/dev/dev.ts69
-rw-r--r--packages/astro/src/core/dev/index.ts2
-rw-r--r--packages/astro/src/core/dev/restart.ts116
-rw-r--r--packages/astro/src/core/errors/errors.ts18
-rw-r--r--packages/astro/src/core/preview/index.ts44
-rw-r--r--packages/astro/src/core/sync/index.ts63
-rw-r--r--packages/astro/test/astro-markdown-url.test.js12
-rw-r--r--packages/astro/test/astro-sync.test.js6
-rw-r--r--packages/astro/test/client-address.test.js5
-rw-r--r--packages/astro/test/dev-routing.test.js6
-rw-r--r--packages/astro/test/dynamic-endpoint-collision.test.js6
-rw-r--r--packages/astro/test/error-bad-js.test.js6
-rw-r--r--packages/astro/test/error-non-error.test.js6
-rw-r--r--packages/astro/test/preview-routing.test.js24
-rw-r--r--packages/astro/test/react-component.test.js6
-rw-r--r--packages/astro/test/test-utils.js116
-rw-r--r--packages/astro/test/units/config/config-server.test.js27
-rw-r--r--packages/astro/test/units/config/config-validate.test.js2
-rw-r--r--packages/astro/test/units/config/format.test.js17
-rw-r--r--packages/astro/test/units/content-collections/frontmatter.test.js5
-rw-r--r--packages/astro/test/units/dev/base.test.js21
-rw-r--r--packages/astro/test/units/dev/collections-mixed-content-errors.test.js10
-rw-r--r--packages/astro/test/units/dev/collections-renderentry.test.js26
-rw-r--r--packages/astro/test/units/dev/dev.test.js28
-rw-r--r--packages/astro/test/units/dev/head-injection.test.js13
-rw-r--r--packages/astro/test/units/dev/hydration.test.js15
-rw-r--r--packages/astro/test/units/dev/restart.test.js57
-rw-r--r--packages/astro/test/units/render/components.test.js11
-rw-r--r--packages/astro/test/units/routing/manifest.test.js50
-rw-r--r--packages/astro/test/units/routing/route-matching.test.js23
-rw-r--r--packages/astro/test/units/shiki/shiki.test.js5
-rw-r--r--packages/astro/test/units/test-utils.js42
-rw-r--r--packages/astro/test/units/vite-plugin-astro-server/request.test.js4
-rw-r--r--packages/integrations/mdx/test/mdx-get-static-paths.test.js2
-rw-r--r--packages/integrations/mdx/test/mdx-plugins.test.js2
57 files changed, 737 insertions, 822 deletions
diff --git a/.changeset/twenty-oranges-poke.md b/.changeset/twenty-oranges-poke.md
new file mode 100644
index 000000000..a36c175dc
--- /dev/null
+++ b/.changeset/twenty-oranges-poke.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Refactor and improve Astro config loading flow
diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js
index 9276370f5..e23b22c65 100644
--- a/packages/astro/e2e/errors.test.js
+++ b/packages/astro/e2e/errors.test.js
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { getErrorOverlayContent, silentLogging, testFactory } from './test-utils.js';
+import { getErrorOverlayContent, testFactory } from './test-utils.js';
const test = testFactory({
root: './fixtures/errors/',
@@ -12,10 +12,7 @@ const test = testFactory({
let devServer;
test.beforeAll(async ({ astro }) => {
- devServer = await astro.startDevServer({
- // Only test the error overlay, don't print to console
- logging: silentLogging,
- });
+ devServer = await astro.startDevServer();
});
test.afterAll(async ({ astro }) => {
diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js
index 92ae9dc67..0768bff81 100644
--- a/packages/astro/e2e/test-utils.js
+++ b/packages/astro/e2e/test-utils.js
@@ -1,6 +1,7 @@
import { expect, test as testBase } from '@playwright/test';
import fs from 'node:fs/promises';
import path from 'node:path';
+import { fileURLToPath } from 'node:url';
import { loadFixture as baseLoadFixture } from '../test/test-utils.js';
export const isWindows = process.platform === 'win32';
@@ -24,7 +25,7 @@ export function loadFixture(inlineConfig) {
// without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
return baseLoadFixture({
...inlineConfig,
- root: new URL(inlineConfig.root, import.meta.url).toString(),
+ root: fileURLToPath(new URL(inlineConfig.root, import.meta.url)),
server: {
port: testFileToPort.get(path.basename(inlineConfig.root)),
},
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index e30ee3b9e..59e295eba 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -19,7 +19,7 @@ import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config';
import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies';
-import type { LogOptions } from '../core/logger/core';
+import type { LogOptions, LoggerLevel } from '../core/logger/core';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server';
import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
export type {
@@ -1331,6 +1331,16 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
// TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[];
}
+export interface AstroInlineConfig extends AstroUserConfig, AstroInlineOnlyConfig {}
+export interface AstroInlineOnlyConfig {
+ configFile?: string | false;
+ mode?: RuntimeMode;
+ logLevel?: LoggerLevel;
+ /**
+ * @internal for testing only
+ */
+ logging?: LogOptions;
+}
export type ContentEntryModule = {
id: string;
diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts
index 821b2dee2..6ef96561d 100644
--- a/packages/astro/src/cli/add/index.ts
+++ b/packages/astro/src/cli/add/index.ts
@@ -9,7 +9,7 @@ import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts';
import type yargs from 'yargs-parser';
-import { loadTSConfig, resolveConfigPath } from '../../core/config/index.js';
+import { loadTSConfig, resolveConfigPath, resolveRoot } from '../../core/config/index.js';
import {
defaultTSConfig,
presets,
@@ -23,12 +23,12 @@ import { appendForwardSlash } from '../../core/path.js';
import { apply as applyPolyfill } from '../../core/polyfill.js';
import { parseNpmName } from '../../core/util.js';
import { eventCliSession, telemetry } from '../../events/index.js';
+import { createLoggingFromFlags } from '../flags.js';
import { generate, parse, t, visit } from './babel.js';
import { ensureImport } from './imports.js';
import { wrapDefaultExport } from './wrapper.js';
interface AddOptions {
- logging: LogOptions;
flags: yargs.Arguments;
}
@@ -86,7 +86,7 @@ async function getRegistry(): Promise<string> {
}
}
-export async function add(names: string[], { flags, logging }: AddOptions) {
+export async function add(names: string[], { flags }: AddOptions) {
telemetry.record(eventCliSession('add'));
applyPolyfill();
if (flags.help || names.length === 0) {
@@ -130,10 +130,12 @@ export async function add(names: string[], { flags, logging }: AddOptions) {
// Some packages might have a common alias! We normalize those here.
const cwd = flags.root;
+ const logging = createLoggingFromFlags(flags);
const integrationNames = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name));
const integrations = await validateIntegrations(integrationNames);
let installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logging });
- const root = pathToFileURL(cwd ? path.resolve(cwd) : process.cwd());
+ const rootPath = resolveRoot(cwd);
+ const root = pathToFileURL(rootPath);
// Append forward slash to compute relative paths
root.href = appendForwardSlash(root.href);
@@ -199,7 +201,11 @@ export async function add(names: string[], { flags, logging }: AddOptions) {
}
}
- const rawConfigPath = await resolveConfigPath({ cwd, flags, fs: fsMod });
+ const rawConfigPath = await resolveConfigPath({
+ root: rootPath,
+ configFile: flags.config,
+ fs: fsMod,
+ });
let configURL = rawConfigPath ? pathToFileURL(rawConfigPath) : undefined;
if (configURL) {
diff --git a/packages/astro/src/cli/build/index.ts b/packages/astro/src/cli/build/index.ts
index ab3765731..9e26108a2 100644
--- a/packages/astro/src/cli/build/index.ts
+++ b/packages/astro/src/cli/build/index.ts
@@ -1,21 +1,31 @@
import type yargs from 'yargs-parser';
import _build from '../../core/build/index.js';
-import type { LogOptions } from '../../core/logger/core.js';
-import { loadSettings } from '../load-settings.js';
+import { printHelp } from '../../core/messages.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
interface BuildOptions {
flags: yargs.Arguments;
- logging: LogOptions;
}
-export async function build({ flags, logging }: BuildOptions) {
- const settings = await loadSettings({ cmd: 'build', flags, logging });
- if (!settings) return;
+export async function build({ flags }: BuildOptions) {
+ if (flags?.help || flags?.h) {
+ printHelp({
+ commandName: 'astro build',
+ usage: '[...flags]',
+ tables: {
+ Flags: [
+ ['--drafts', `Include Markdown draft pages in the build.`],
+ ['--help (-h)', 'See all available flags.'],
+ ],
+ },
+ description: `Builds your site for deployment.`,
+ });
+ return;
+ }
- await _build(settings, {
- flags,
- logging,
+ const inlineConfig = flagsToAstroInlineConfig(flags);
+
+ await _build(inlineConfig, {
teardownCompiler: true,
- mode: flags.mode,
});
}
diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts
index 09d45ee56..96bee308d 100644
--- a/packages/astro/src/cli/check/index.ts
+++ b/packages/astro/src/cli/check/index.ts
@@ -13,11 +13,16 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
import ora from 'ora';
import type { Arguments as Flags } from 'yargs-parser';
import type { AstroSettings } from '../../@types/astro';
+import { resolveConfig } from '../../core/config/config.js';
+import { createNodeLogging } from '../../core/config/logging.js';
+import { createSettings } from '../../core/config/settings.js';
import type { LogOptions } from '../../core/logger/core.js';
import { debug, info } from '../../core/logger/core.js';
import { printHelp } from '../../core/messages.js';
-import type { ProcessExit, SyncOptions } from '../../core/sync';
-import { loadSettings } from '../load-settings.js';
+import type { syncInternal } from '../../core/sync';
+import { eventCliSession, telemetry } from '../../events/index.js';
+import { runHookConfigSetup } from '../../integrations/index.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
import { printDiagnostic } from './print.js';
type DiagnosticResult = {
@@ -31,11 +36,6 @@ export type CheckPayload = {
* Flags passed via CLI
*/
flags: Flags;
-
- /**
- * Logging options
- */
- logging: LogOptions;
};
type CheckFlags = {
@@ -77,9 +77,8 @@ const ASTRO_GLOB_PATTERN = '**/*.astro';
*
* @param {CheckPayload} options Options passed {@link AstroChecker}
* @param {Flags} options.flags Flags coming from the CLI
- * @param {LogOptions} options.logging Logging options
*/
-export async function check({ logging, flags }: CheckPayload): Promise<AstroChecker | undefined> {
+export async function check({ flags }: CheckPayload): Promise<AstroChecker | undefined> {
if (flags.help || flags.h) {
printHelp({
commandName: 'astro check',
@@ -95,8 +94,12 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
return;
}
- const settings = await loadSettings({ cmd: 'check', flags, logging });
- if (!settings) return;
+ // Load settings
+ const inlineConfig = flagsToAstroInlineConfig(flags);
+ const logging = createNodeLogging(inlineConfig);
+ const { userConfig, astroConfig } = await resolveConfig(inlineConfig, 'check');
+ telemetry.record(eventCliSession('check', userConfig, flags));
+ const settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
const checkFlags = parseFlags(flags);
if (checkFlags.watch) {
@@ -105,7 +108,7 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
info(logging, 'check', 'Checking files');
}
- const { syncCli } = await import('../../core/sync/index.js');
+ const { syncInternal } = await import('../../core/sync/index.js');
const root = settings.config.root;
const require = createRequire(import.meta.url);
const diagnosticChecker = new AstroCheck(
@@ -116,7 +119,7 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
);
return new AstroChecker({
- syncCli,
+ syncInternal,
settings,
fileSystem: fs,
logging,
@@ -130,7 +133,7 @@ type CheckerConstructor = {
isWatchMode: boolean;
- syncCli: (settings: AstroSettings, options: SyncOptions) => Promise<ProcessExit>;
+ syncInternal: typeof syncInternal;
settings: Readonly<AstroSettings>;
@@ -148,7 +151,7 @@ type CheckerConstructor = {
export class AstroChecker {
readonly #diagnosticsChecker: AstroCheck;
readonly #shouldWatch: boolean;
- readonly #syncCli: (settings: AstroSettings, opts: SyncOptions) => Promise<ProcessExit>;
+ readonly #syncInternal: CheckerConstructor['syncInternal'];
readonly #settings: AstroSettings;
@@ -162,14 +165,14 @@ export class AstroChecker {
constructor({
diagnosticChecker,
isWatchMode,
- syncCli,
+ syncInternal,
settings,
fileSystem,
logging,
}: CheckerConstructor) {
this.#diagnosticsChecker = diagnosticChecker;
this.#shouldWatch = isWatchMode;
- this.#syncCli = syncCli;
+ this.#syncInternal = syncInternal;
this.#logging = logging;
this.#settings = settings;
this.#fs = fileSystem;
@@ -223,7 +226,14 @@ export class AstroChecker {
* @param openDocuments Whether the operation should open all `.astro` files
*/
async #checkAllFiles(openDocuments: boolean): Promise<CheckResult> {
- const processExit = await this.#syncCli(this.#settings, {
+ // Run `astro:config:setup` before syncing to initialize integrations.
+ // We do this manually as we're calling `syncInternal` directly.
+ const syncSettings = await runHookConfigSetup({
+ settings: this.#settings,
+ logging: this.#logging,
+ command: 'build',
+ });
+ const processExit = await this.#syncInternal(syncSettings, {
logging: this.#logging,
fs: this.#fs,
});
diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts
index d3230a05b..e55496c4a 100644
--- a/packages/astro/src/cli/dev/index.ts
+++ b/packages/astro/src/cli/dev/index.ts
@@ -1,31 +1,35 @@
-import fs from 'node:fs';
+import { cyan } from 'kleur/colors';
import type yargs from 'yargs-parser';
-import { resolveConfigPath, resolveFlags } from '../../core/config/index.js';
import devServer from '../../core/dev/index.js';
-import { info, type LogOptions } from '../../core/logger/core.js';
-import { handleConfigError, loadSettings } from '../load-settings.js';
+import { printHelp } from '../../core/messages.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
interface DevOptions {
flags: yargs.Arguments;
- logging: LogOptions;
}
-export async function dev({ flags, logging }: DevOptions) {
- const settings = await loadSettings({ cmd: 'dev', flags, logging });
- if (!settings) return;
+export async function dev({ flags }: DevOptions) {
+ if (flags.help || flags.h) {
+ printHelp({
+ commandName: 'astro dev',
+ usage: '[...flags]',
+ tables: {
+ Flags: [
+ ['--port', `Specify which port to run on. Defaults to 3000.`],
+ ['--host', `Listen on all addresses, including LAN and public addresses.`],
+ ['--host <custom-address>', `Expose on a network IP address at <custom-address>`],
+ ['--open', 'Automatically open the app in the browser on server start'],
+ ['--help (-h)', 'See all available flags.'],
+ ],
+ },
+ description: `Check ${cyan(
+ 'https://docs.astro.build/en/reference/cli-reference/#astro-dev'
+ )} for more information.`,
+ });
+ return;
+ }
- const root = flags.root;
- const configFlag = resolveFlags(flags).config;
- const configFlagPath = configFlag ? await resolveConfigPath({ cwd: root, flags, fs }) : undefined;
+ const inlineConfig = flagsToAstroInlineConfig(flags);
- return await devServer(settings, {
- configFlag,
- configFlagPath,
- flags,
- logging,
- handleConfigError(e) {
- handleConfigError(e, { cmd: 'dev', cwd: root, flags, logging });
- info(logging, 'astro', 'Continuing with previous valid configuration\n');
- },
- });
+ return await devServer(inlineConfig);
}
diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts
new file mode 100644
index 000000000..703422d50
--- /dev/null
+++ b/packages/astro/src/cli/flags.ts
@@ -0,0 +1,49 @@
+import type { Arguments as Flags } from 'yargs-parser';
+import type { AstroInlineConfig } from '../@types/astro.js';
+import type { LogOptions } from '../core/logger/core.js';
+import { nodeLogDestination } from '../core/logger/node.js';
+
+export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig {
+ return {
+ // Inline-only configs
+ configFile: typeof flags.config === 'string' ? flags.config : undefined,
+ mode: typeof flags.mode === 'string' ? (flags.mode as AstroInlineConfig['mode']) : undefined,
+ logLevel: flags.verbose ? 'debug' : flags.silent ? 'silent' : undefined,
+
+ // Astro user configs
+ root: typeof flags.root === 'string' ? flags.root : undefined,
+ site: typeof flags.site === 'string' ? flags.site : undefined,
+ base: typeof flags.base === 'string' ? flags.base : undefined,
+ markdown: {
+ drafts: typeof flags.drafts === 'boolean' ? flags.drafts : undefined,
+ },
+ server: {
+ port: typeof flags.port === 'number' ? flags.port : undefined,
+ host:
+ typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
+ open: typeof flags.open === 'boolean' ? flags.open : undefined,
+ },
+ experimental: {
+ assets: typeof flags.experimentalAssets === 'boolean' ? flags.experimentalAssets : undefined,
+ },
+ };
+}
+
+/**
+ * The `logging` is usually created from an `AstroInlineConfig`, but some flows like `add`
+ * doesn't read the AstroConfig directly, so we create a `logging` object from the CLI flags instead.
+ */
+export function createLoggingFromFlags(flags: Flags): LogOptions {
+ const logging: LogOptions = {
+ dest: nodeLogDestination,
+ level: 'info',
+ };
+
+ if (flags.verbose) {
+ logging.level = 'debug';
+ } else if (flags.silent) {
+ logging.level = 'silent';
+ }
+
+ return logging;
+}
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index 7c26b2d01..d16ea91e2 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -2,7 +2,6 @@
import * as colors from 'kleur/colors';
import yargs from 'yargs-parser';
import { ASTRO_VERSION } from '../core/constants.js';
-import type { LogOptions } from '../core/logger/core.js';
type CLICommand =
| 'help'
@@ -112,16 +111,10 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
}
}
- const { enableVerboseLogging, nodeLogDestination } = await import('../core/logger/node.js');
- const logging: LogOptions = {
- dest: nodeLogDestination,
- level: 'info',
- };
+ // In verbose/debug mode, we log the debug logs asap before any potential errors could appear
if (flags.verbose) {
- logging.level = 'debug';
+ const { enableVerboseLogging } = await import('../core/logger/node.js');
enableVerboseLogging();
- } else if (flags.silent) {
- logging.level = 'silent';
}
// Start with a default NODE_ENV so Vite doesn't set an incorrect default when loading the Astro config
@@ -135,12 +128,12 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
case 'add': {
const { add } = await import('./add/index.js');
const packages = flags._.slice(3) as string[];
- await add(packages, { flags, logging });
+ await add(packages, { flags });
return;
}
case 'dev': {
const { dev } = await import('./dev/index.js');
- const server = await dev({ flags, logging });
+ const server = await dev({ flags });
if (server) {
return await new Promise(() => {}); // lives forever
}
@@ -148,12 +141,12 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
}
case 'build': {
const { build } = await import('./build/index.js');
- await build({ flags, logging });
+ await build({ flags });
return;
}
case 'preview': {
const { preview } = await import('./preview/index.js');
- const server = await preview({ flags, logging });
+ const server = await preview({ flags });
if (server) {
return await server.closed(); // keep alive until the server is closed
}
@@ -162,7 +155,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
case 'check': {
const { check } = await import('./check/index.js');
// We create a server to start doing our operations
- const checkServer = await check({ flags, logging });
+ const checkServer = await check({ flags });
if (checkServer) {
if (checkServer.isWatchMode) {
await checkServer.watch();
@@ -176,7 +169,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
}
case 'sync': {
const { sync } = await import('./sync/index.js');
- const exitCode = await sync({ flags, logging });
+ const exitCode = await sync({ flags });
return process.exit(exitCode);
}
}
diff --git a/packages/astro/src/cli/info/index.ts b/packages/astro/src/cli/info/index.ts
index 3d27d7f3f..4944432e7 100644
--- a/packages/astro/src/cli/info/index.ts
+++ b/packages/astro/src/cli/info/index.ts
@@ -3,14 +3,16 @@ import * as colors from 'kleur/colors';
import { arch, platform } from 'node:os';
import whichPm from 'which-pm';
import type yargs from 'yargs-parser';
-import { openConfig } from '../../core/config/index.js';
+import { resolveConfig } from '../../core/config/index.js';
import { ASTRO_VERSION } from '../../core/constants.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
interface InfoOptions {
flags: yargs.Arguments;
}
export async function printInfo({ flags }: InfoOptions) {
+ const inlineConfig = flagsToAstroInlineConfig(flags);
const packageManager = await whichPm(process.cwd());
let adapter = "Couldn't determine.";
let integrations = [];
@@ -22,11 +24,7 @@ export async function printInfo({ flags }: InfoOptions) {
}
try {
- const { userConfig } = await openConfig({
- cwd: flags.root,
- flags,
- cmd: 'info',
- });
+ const { userConfig } = await resolveConfig(inlineConfig, 'info');
if (userConfig.adapter?.name) {
adapter = userConfig.adapter.name;
}
diff --git a/packages/astro/src/cli/load-settings.ts b/packages/astro/src/cli/load-settings.ts
deleted file mode 100644
index 9377825c4..000000000
--- a/packages/astro/src/cli/load-settings.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/* eslint-disable no-console */
-import * as colors from 'kleur/colors';
-import fs from 'node:fs';
-import type { Arguments as Flags } from 'yargs-parser';
-import { ZodError } from 'zod';
-import { createSettings, openConfig, resolveConfigPath } from '../core/config/index.js';
-import { collectErrorMetadata } from '../core/errors/dev/index.js';
-import { error, type LogOptions } from '../core/logger/core.js';
-import { formatConfigErrorMessage, formatErrorMessage } from '../core/messages.js';
-import * as event from '../events/index.js';
-import { eventConfigError, telemetry } from '../events/index.js';
-
-interface LoadSettingsOptions {
- cmd: string;
- flags: Flags;
- logging: LogOptions;
-}
-
-export async function loadSettings({ cmd, flags, logging }: LoadSettingsOptions) {
- const root = flags.root;
- const { astroConfig: initialAstroConfig, userConfig: initialUserConfig } = await openConfig({
- cwd: root,
- flags,
- cmd,
- }).catch(async (e) => {
- await handleConfigError(e, { cmd, cwd: root, flags, logging });
- return {} as any;
- });
-
- if (!initialAstroConfig) return;
- telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags));
- return createSettings(initialAstroConfig, root);
-}
-
-export async function handleConfigError(
- e: any,
- { cmd, cwd, flags, logging }: { cmd: string; cwd?: string; flags?: Flags; logging: LogOptions }
-) {
- const path = await resolveConfigPath({ cwd, flags, fs });
- error(logging, 'astro', `Unable to load ${path ? colors.bold(path) : 'your Astro config'}\n`);
- if (e instanceof ZodError) {
- console.error(formatConfigErrorMessage(e) + '\n');
- telemetry.record(eventConfigError({ cmd, err: e, isFatal: true }));
- } else if (e instanceof Error) {
- console.error(formatErrorMessage(collectErrorMetadata(e)) + '\n');
- }
-}
diff --git a/packages/astro/src/cli/preview/index.ts b/packages/astro/src/cli/preview/index.ts
index 96146cebc..39bb2de0e 100644
--- a/packages/astro/src/cli/preview/index.ts
+++ b/packages/astro/src/cli/preview/index.ts
@@ -1,16 +1,32 @@
+import { cyan } from 'kleur/colors';
import type yargs from 'yargs-parser';
-import type { LogOptions } from '../../core/logger/core.js';
+import { printHelp } from '../../core/messages.js';
import previewServer from '../../core/preview/index.js';
-import { loadSettings } from '../load-settings.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
interface PreviewOptions {
flags: yargs.Arguments;
- logging: LogOptions;
}
-export async function preview({ flags, logging }: PreviewOptions) {
- const settings = await loadSettings({ cmd: 'preview', flags, logging });
- if (!settings) return;
+export async function preview({ flags }: PreviewOptions) {
+ if (flags?.help || flags?.h) {
+ printHelp({
+ commandName: 'astro preview',
+ usage: '[...flags]',
+ tables: {
+ Flags: [
+ ['--open', 'Automatically open the app in the browser on server start'],
+ ['--help (-h)', 'See all available flags.'],
+ ],
+ },
+ description: `Starts a local server to serve your static dist/ directory. Check ${cyan(
+ 'https://docs.astro.build/en/reference/cli-reference/#astro-preview'
+ )} for more information.`,
+ });
+ return;
+ }
- return await previewServer(settings, { flags, logging });
+ const inlineConfig = flagsToAstroInlineConfig(flags);
+
+ return await previewServer(inlineConfig);
}
diff --git a/packages/astro/src/cli/sync/index.ts b/packages/astro/src/cli/sync/index.ts
index f96b8fd0b..66a277e46 100644
--- a/packages/astro/src/cli/sync/index.ts
+++ b/packages/astro/src/cli/sync/index.ts
@@ -1,18 +1,27 @@
-import fs from 'node:fs';
import type yargs from 'yargs-parser';
-import type { LogOptions } from '../../core/logger/core.js';
-import { syncCli } from '../../core/sync/index.js';
-import { loadSettings } from '../load-settings.js';
+import { printHelp } from '../../core/messages.js';
+import { sync as _sync } from '../../core/sync/index.js';
+import { flagsToAstroInlineConfig } from '../flags.js';
interface SyncOptions {
flags: yargs.Arguments;
- logging: LogOptions;
}
-export async function sync({ flags, logging }: SyncOptions) {
- const settings = await loadSettings({ cmd: 'sync', flags, logging });
- if (!settings) return;
+export async function sync({ flags }: SyncOptions) {
+ if (flags?.help || flags?.h) {
+ printHelp({
+ commandName: 'astro sync',
+ usage: '[...flags]',
+ tables: {
+ Flags: [['--help (-h)', 'See all available flags.']],
+ },
+ description: `Generates TypeScript types for all Astro modules.`,
+ });
+ return 0;
+ }
- const exitCode = await syncCli(settings, { logging, fs, flags });
+ const inlineConfig = flagsToAstroInlineConfig(flags);
+
+ const exitCode = await _sync(inlineConfig);
return exitCode;
}
diff --git a/packages/astro/src/cli/throw-and-exit.ts b/packages/astro/src/cli/throw-and-exit.ts
index cea76ee98..3196092d2 100644
--- a/packages/astro/src/cli/throw-and-exit.ts
+++ b/packages/astro/src/cli/throw-and-exit.ts
@@ -1,5 +1,6 @@
/* eslint-disable no-console */
import { collectErrorMetadata } from '../core/errors/dev/index.js';
+import { isAstroConfigZodError } from '../core/errors/errors.js';
import { createSafeError } from '../core/errors/index.js';
import { debug } from '../core/logger/core.js';
import { formatErrorMessage } from '../core/messages.js';
@@ -7,6 +8,9 @@ import { eventError, telemetry } from '../events/index.js';
/** Display error and exit */
export async function throwAndExit(cmd: string, err: unknown) {
+ // Suppress ZodErrors from AstroConfig as the pre-logged error is sufficient
+ if (isAstroConfigZodError(err)) return;
+
let telemetryPromise: Promise<any>;
let errorMessage: string;
function exitWithErrorMessage() {
diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts
index a9e32186d..d32af35b8 100644
--- a/packages/astro/src/config/index.ts
+++ b/packages/astro/src/config/index.ts
@@ -17,7 +17,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
fs,
{ mergeConfig },
{ nodeLogDestination },
- { openConfig, createSettings },
+ { resolveConfig, createSettings },
{ createVite },
{ runHookConfigSetup, runHookConfigDone },
{ astroContentListenPlugin },
@@ -34,7 +34,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
dest: nodeLogDestination,
level: 'info',
};
- const { astroConfig: config } = await openConfig({ cmd });
+ const { astroConfig: config } = await resolveConfig({}, cmd);
const settings = createSettings(config, inlineConfig.root);
await runHookConfigSetup({ settings, command: cmd, logging });
const viteConfig = await createVite(
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index 5ac5d2b0f..5b1ecf404 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,10 +1,18 @@
import * as colors from 'kleur/colors';
import fs from 'node:fs';
import { performance } from 'node:perf_hooks';
+import { fileURLToPath } from 'node:url';
import type * as vite from 'vite';
-import type yargs from 'yargs-parser';
-import type { AstroConfig, AstroSettings, ManifestData, RuntimeMode } from '../../@types/astro';
+import type {
+ AstroConfig,
+ AstroInlineConfig,
+ AstroSettings,
+ ManifestData,
+ RuntimeMode,
+} from '../../@types/astro';
import { injectImageEndpoint } from '../../assets/internal.js';
+import { telemetry } from '../../events/index.js';
+import { eventCliSession } from '../../events/session.js';
import {
runHookBuildDone,
runHookBuildStart,
@@ -12,9 +20,11 @@ import {
runHookConfigSetup,
} from '../../integrations/index.js';
import { isServerLikeOutput } from '../../prerender/utils.js';
+import { resolveConfig } from '../config/config.js';
+import { createNodeLogging } from '../config/logging.js';
+import { createSettings } from '../config/settings.js';
import { createVite } from '../create-vite.js';
import { debug, info, levels, timerMessage, warn, type LogOptions } from '../logger/core.js';
-import { printHelp } from '../messages.js';
import { apply as applyPolyfill } from '../polyfill.js';
import { RouteCache } from '../render/route-cache.js';
import { createRouteManifest } from '../routing/index.js';
@@ -24,38 +34,38 @@ import type { StaticBuildOptions } from './types.js';
import { getTimeStat } from './util.js';
export interface BuildOptions {
- mode?: RuntimeMode;
- logging: LogOptions;
/**
* Teardown the compiler WASM instance after build. This can improve performance when
* building once, but may cause a performance hit if building multiple times in a row.
*/
teardownCompiler?: boolean;
- flags?: yargs.Arguments;
}
/** `astro build` */
-export default async function build(settings: AstroSettings, options: BuildOptions): Promise<void> {
+export default async function build(
+ inlineConfig: AstroInlineConfig,
+ options: BuildOptions
+): Promise<void> {
applyPolyfill();
- if (options.flags?.help || options.flags?.h) {
- printHelp({
- commandName: 'astro build',
- usage: '[...flags]',
- tables: {
- Flags: [
- ['--drafts', `Include Markdown draft pages in the build.`],
- ['--help (-h)', 'See all available flags.'],
- ],
- },
- description: `Builds your site for deployment.`,
- });
- return;
- }
+ const logging = createNodeLogging(inlineConfig);
+ const { userConfig, astroConfig } = await resolveConfig(inlineConfig, 'build');
+ telemetry.record(eventCliSession('build', userConfig));
+
+ const settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
- const builder = new AstroBuilder(settings, options);
+ const builder = new AstroBuilder(settings, {
+ ...options,
+ logging,
+ mode: inlineConfig.mode,
+ });
await builder.run();
}
+interface AstroBuilderOptions extends BuildOptions {
+ logging: LogOptions;
+ mode?: RuntimeMode;
+}
+
class AstroBuilder {
private settings: AstroSettings;
private logging: LogOptions;
@@ -66,7 +76,7 @@ class AstroBuilder {
private timer: Record<string, number>;
private teardownCompiler: boolean;
- constructor(settings: AstroSettings, options: BuildOptions) {
+ constructor(settings: AstroSettings, options: AstroBuilderOptions) {
if (options.mode) {
this.mode = options.mode;
}
@@ -112,8 +122,8 @@ class AstroBuilder {
);
await runHookConfigDone({ settings: this.settings, logging });
- const { sync } = await import('../sync/index.js');
- const syncRet = await sync(this.settings, { logging, fs });
+ const { syncInternal } = await import('../sync/index.js');
+ const syncRet = await syncInternal(this.settings, { logging, fs });
if (syncRet !== 0) {
return process.exit(syncRet);
}
diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts
index 1be371523..0872145db 100644
--- a/packages/astro/src/core/config/config.ts
+++ b/packages/astro/src/core/config/config.ts
@@ -1,16 +1,26 @@
import type { Arguments as Flags } from 'yargs-parser';
-import type { AstroConfig, AstroUserConfig, CLIFlags } from '../../@types/astro';
+import type {
+ AstroConfig,
+ AstroInlineConfig,
+ AstroInlineOnlyConfig,
+ AstroUserConfig,
+ CLIFlags,
+} from '../../@types/astro';
import * as colors from 'kleur/colors';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import { ZodError } from 'zod';
+import { eventConfigError, telemetry } from '../../events/index.js';
+import { trackAstroConfigZodError } from '../errors/errors.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
+import { formatConfigErrorMessage } from '../messages.js';
import { mergeConfig } from './merge.js';
import { createRelativeSchema } from './schema.js';
import { loadConfigWithVite } from './vite-load.js';
-export const LEGACY_ASTRO_CONFIG_KEYS = new Set([
+const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'projectRoot',
'src',
'pages',
@@ -80,13 +90,29 @@ export async function validateConfig(
const AstroConfigRelativeSchema = createRelativeSchema(cmd, root);
// First-Pass Validation
- const result = await AstroConfigRelativeSchema.parseAsync(userConfig);
+ let result: AstroConfig;
+ try {
+ result = await AstroConfigRelativeSchema.parseAsync(userConfig);
+ } catch (e) {
+ // Improve config zod error messages
+ if (e instanceof ZodError) {
+ // Mark this error so the callee can decide to suppress Zod's error if needed.
+ // We still want to throw the error to signal an error in validation.
+ trackAstroConfigZodError(e);
+ // eslint-disable-next-line no-console
+ console.error(formatConfigErrorMessage(e) + '\n');
+ telemetry.record(eventConfigError({ cmd, err: e, isFatal: true }));
+ }
+ throw e;
+ }
// If successful, return the result as a verified AstroConfig object.
return result;
}
/** Convert the generic "yargs" flag object into our own, custom TypeScript object. */
+// NOTE: This function will be removed in a later PR. Use `flagsToAstroInlineConfig` instead.
+// All CLI related flow should be located in the `packages/astro/src/cli` directory.
export function resolveFlags(flags: Partial<Flags>): CLIFlags {
return {
root: typeof flags.root === 'string' ? flags.root : undefined,
@@ -110,22 +136,6 @@ export function resolveRoot(cwd?: string | URL): string {
return cwd ? path.resolve(cwd) : process.cwd();
}
-/** Merge CLI flags & user config object (CLI flags take priority) */
-function mergeCLIFlags(astroConfig: AstroUserConfig, flags: CLIFlags) {
- return mergeConfig(astroConfig, {
- site: flags.site,
- base: flags.base,
- markdown: {
- drafts: flags.drafts,
- },
- server: {
- port: flags.port,
- host: flags.host,
- open: flags.open,
- },
- });
-}
-
async function search(fsMod: typeof fs, root: string) {
const paths = [
'astro.config.mjs',
@@ -143,19 +153,9 @@ async function search(fsMod: typeof fs, root: string) {
}
}
-interface LoadConfigOptions {
- cwd?: string;
- flags?: Flags;
- cmd: string;
- validate?: boolean;
- /** Invalidate when reloading a previously loaded config */
- isRestart?: boolean;
- fsMod?: typeof fs;
-}
-
interface ResolveConfigPathOptions {
- cwd?: string;
- flags?: Flags;
+ root: string;
+ configFile?: string;
fs: typeof fs;
}
@@ -163,87 +163,85 @@ interface ResolveConfigPathOptions {
* Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file
*/
export async function resolveConfigPath(
- configOptions: ResolveConfigPathOptions
+ options: ResolveConfigPathOptions
): Promise<string | undefined> {
- const root = resolveRoot(configOptions.cwd);
- const flags = resolveFlags(configOptions.flags || {});
-
let userConfigPath: string | undefined;
- if (flags?.config) {
- userConfigPath = /^\.*\//.test(flags.config) ? flags.config : `./${flags.config}`;
- userConfigPath = fileURLToPath(new URL(userConfigPath, `file://${root}/`));
- if (!configOptions.fs.existsSync(userConfigPath)) {
+ if (options.configFile) {
+ userConfigPath = path.join(options.root, options.configFile);
+ if (!options.fs.existsSync(userConfigPath)) {
throw new AstroError({
...AstroErrorData.ConfigNotFound,
- message: AstroErrorData.ConfigNotFound.message(flags.config),
+ message: AstroErrorData.ConfigNotFound.message(options.configFile),
});
}
} else {
- userConfigPath = await search(configOptions.fs, root);
+ userConfigPath = await search(options.fs, options.root);
}
return userConfigPath;
}
-interface OpenConfigResult {
- userConfig: AstroUserConfig;
- astroConfig: AstroConfig;
- flags: CLIFlags;
- root: string;
-}
-
-/** Load a configuration file, returning both the userConfig and astroConfig */
-export async function openConfig(configOptions: LoadConfigOptions): Promise<OpenConfigResult> {
- const root = resolveRoot(configOptions.cwd);
- const flags = resolveFlags(configOptions.flags || {});
-
- const userConfig = await loadConfig(configOptions, root);
- const astroConfig = await resolveConfig(userConfig, root, flags, configOptions.cmd);
-
- return {
- astroConfig,
- userConfig,
- flags,
- root,
- };
-}
-
async function loadConfig(
- configOptions: LoadConfigOptions,
- root: string
+ root: string,
+ configFile?: string | false,
+ fsMod = fs
): Promise<Record<string, any>> {
- const fsMod = configOptions.fsMod ?? fs;
+ if (configFile === false) return {};
+
const configPath = await resolveConfigPath({
- cwd: configOptions.cwd,
- flags: configOptions.flags,
+ root,
+ configFile,
fs: fsMod,
});
if (!configPath) return {};
// Create a vite server to load the config
- return await loadConfigWithVite({
- configPath,
- fs: fsMod,
- root,
- });
+ try {
+ return await loadConfigWithVite({
+ root,
+ configPath,
+ fs: fsMod,
+ });
+ } catch (e) {
+ const configPathText = configFile ? colors.bold(configFile) : 'your Astro config';
+ // Config errors should bypass log level as it breaks startup
+ // eslint-disable-next-line no-console
+ console.error(`${colors.bold(colors.red('[astro]'))} Unable to load ${configPathText}\n`);
+ throw e;
+ }
}
-/** Attempt to resolve an Astro configuration object. Normalize, validate, and return. */
-export async function resolveConfig(
- userConfig: AstroUserConfig,
- root: string,
- flags: CLIFlags = {},
- cmd: string
-): Promise<AstroConfig> {
- const mergedConfig = mergeCLIFlags(userConfig, flags);
- const validatedConfig = await validateConfig(mergedConfig, root, cmd);
+function splitInlineConfig(inlineConfig: AstroInlineConfig): {
+ inlineUserConfig: AstroUserConfig;
+ inlineOnlyConfig: AstroInlineOnlyConfig;
+} {
+ const { configFile, mode, logLevel, ...inlineUserConfig } = inlineConfig;
+ return {
+ inlineUserConfig,
+ inlineOnlyConfig: {
+ configFile,
+ mode,
+ logLevel,
+ },
+ };
+}
- return validatedConfig;
+interface ResolveConfigResult {
+ userConfig: AstroUserConfig;
+ astroConfig: AstroConfig;
}
-export function createDefaultDevConfig(
- userConfig: AstroUserConfig = {},
- root: string = process.cwd()
-) {
- return resolveConfig(userConfig, root, undefined, 'dev');
+export async function resolveConfig(
+ inlineConfig: AstroInlineConfig,
+ command: string,
+ fsMod = fs
+): Promise<ResolveConfigResult> {
+ const root = resolveRoot(inlineConfig.root);
+ const { inlineUserConfig, inlineOnlyConfig } = splitInlineConfig(inlineConfig);
+
+ const userConfig = await loadConfig(root, inlineOnlyConfig.configFile, fsMod);
+ const mergedConfig = mergeConfig(userConfig, inlineUserConfig);
+ const astroConfig = await validateConfig(mergedConfig, root, command);
+
+ return { userConfig, astroConfig };
}
diff --git a/packages/astro/src/core/config/index.ts b/packages/astro/src/core/config/index.ts
index b7b616951..23db73382 100644
--- a/packages/astro/src/core/config/index.ts
+++ b/packages/astro/src/core/config/index.ts
@@ -1,12 +1,6 @@
-export {
- createDefaultDevConfig,
- openConfig,
- resolveConfigPath,
- resolveFlags,
- resolveRoot,
- validateConfig,
-} from './config.js';
+export { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from './config.js';
+export { createNodeLogging } from './logging.js';
export { mergeConfig } from './merge.js';
export type { AstroConfigSchema } from './schema';
-export { createDefaultDevSettings, createSettings } from './settings.js';
+export { createSettings } from './settings.js';
export { loadTSConfig, updateTSConfigForFramework } from './tsconfig.js';
diff --git a/packages/astro/src/core/config/logging.ts b/packages/astro/src/core/config/logging.ts
new file mode 100644
index 000000000..ea0b29b88
--- /dev/null
+++ b/packages/astro/src/core/config/logging.ts
@@ -0,0 +1,13 @@
+import type { AstroInlineConfig } from '../../@types/astro.js';
+import type { LogOptions } from '../logger/core.js';
+import { nodeLogDestination } from '../logger/node.js';
+
+export function createNodeLogging(inlineConfig: AstroInlineConfig): LogOptions {
+ // For internal testing, the inline config can pass the raw `logging` object directly
+ if (inlineConfig.logging) return inlineConfig.logging;
+
+ return {
+ dest: nodeLogDestination,
+ level: inlineConfig.logLevel ?? 'info',
+ };
+}
diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts
index 35d7d252b..b15961e50 100644
--- a/packages/astro/src/core/config/settings.ts
+++ b/packages/astro/src/core/config/settings.ts
@@ -1,7 +1,7 @@
import yaml from 'js-yaml';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
-import type { AstroConfig, AstroSettings, AstroUserConfig } from '../../@types/astro';
+import type { AstroConfig, AstroSettings } from '../../@types/astro';
import { getContentPaths } from '../../content/index.js';
import jsxRenderer from '../../jsx/renderer.js';
import { markdownContentEntryType } from '../../vite-plugin-markdown/content-entry-type.js';
@@ -9,7 +9,6 @@ import { getDefaultClientDirectives } from '../client-directive/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { formatYAMLException, isYAMLException } from '../errors/utils.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../constants.js';
-import { createDefaultDevConfig } from './config.js';
import { AstroTimer } from './timer.js';
import { loadTSConfig } from './tsconfig.js';
@@ -119,14 +118,3 @@ export function createSettings(config: AstroConfig, cwd?: string): AstroSettings
settings.watchFiles = watchFiles;
return settings;
}
-
-export async function createDefaultDevSettings(
- userConfig: AstroUserConfig = {},
- root?: string | URL
-): Promise<AstroSettings> {
- if (root && typeof root !== 'string') {
- root = fileURLToPath(root);
- }
- const config = await createDefaultDevConfig(userConfig, root);
- return createBaseSettings(config);
-}
diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts
index d7ab1ae9f..4dd3f15b9 100644
--- a/packages/astro/src/core/dev/container.ts
+++ b/packages/astro/src/core/dev/container.ts
@@ -1,6 +1,6 @@
import type * as http from 'node:http';
import type { AddressInfo } from 'node:net';
-import type { AstroSettings, AstroUserConfig } from '../../@types/astro';
+import type { AstroInlineConfig, AstroSettings } from '../../@types/astro';
import nodeFs from 'node:fs';
import * as vite from 'vite';
@@ -11,52 +11,36 @@ import {
runHookServerDone,
runHookServerStart,
} from '../../integrations/index.js';
-import { createDefaultDevSettings, resolveRoot } from '../config/index.js';
import { createVite } from '../create-vite.js';
import type { LogOptions } from '../logger/core.js';
-import { nodeLogDestination } from '../logger/node.js';
-import { appendForwardSlash } from '../path.js';
import { apply as applyPolyfill } from '../polyfill.js';
-const defaultLogging: LogOptions = {
- dest: nodeLogDestination,
- level: 'error',
-};
-
export interface Container {
fs: typeof nodeFs;
logging: LogOptions;
settings: AstroSettings;
- viteConfig: vite.InlineConfig;
viteServer: vite.ViteDevServer;
- resolvedRoot: string;
- configFlag: string | undefined;
- configFlagPath: string | undefined;
+ inlineConfig: AstroInlineConfig;
restartInFlight: boolean; // gross
handle: (req: http.IncomingMessage, res: http.ServerResponse) => void;
close: () => Promise<void>;
}
export interface CreateContainerParams {
+ logging: LogOptions;
+ settings: AstroSettings;
+ inlineConfig?: AstroInlineConfig;
isRestart?: boolean;
- logging?: LogOptions;
- userConfig?: AstroUserConfig;
- settings?: AstroSettings;
fs?: typeof nodeFs;
- root?: string | URL;
- // The string passed to --config and the resolved path
- configFlag?: string;
- configFlagPath?: string;
}
-export async function createContainer(params: CreateContainerParams = {}): Promise<Container> {
- let {
- isRestart = false,
- logging = defaultLogging,
- settings = await createDefaultDevSettings(params.userConfig, params.root),
- fs = nodeFs,
- } = params;
-
+export async function createContainer({
+ isRestart = false,
+ logging,
+ inlineConfig,
+ settings,
+ fs = nodeFs,
+}: CreateContainerParams): Promise<Container> {
// Initialize
applyPolyfill();
settings = await runHookConfigSetup({
@@ -94,14 +78,11 @@ export async function createContainer(params: CreateContainerParams = {}): Promi
const viteServer = await vite.createServer(viteConfig);
const container: Container = {
- configFlag: params.configFlag,
- configFlagPath: params.configFlagPath,
+ inlineConfig: inlineConfig ?? {},
fs,
logging,
- resolvedRoot: appendForwardSlash(resolveRoot(params.root)),
restartInFlight: false,
settings,
- viteConfig,
viteServer,
handle(req, res) {
viteServer.middlewares.handle(req, res, Function.prototype);
@@ -143,18 +124,3 @@ export async function startContainer({
export function isStarted(container: Container): boolean {
return !!container.viteServer.httpServer?.listening;
}
-
-/**
- * Only used in tests
- */
-export async function runInContainer(
- params: CreateContainerParams,
- callback: (container: Container) => Promise<void> | void
-) {
- const container = await createContainer(params);
- try {
- await callback(container);
- } finally {
- await container.close();
- }
-}
diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts
index f6fd35685..b14656d26 100644
--- a/packages/astro/src/core/dev/dev.ts
+++ b/packages/astro/src/core/dev/dev.ts
@@ -1,27 +1,16 @@
-import { cyan } from 'kleur/colors';
+import fs from 'node:fs';
import type http from 'node:http';
import type { AddressInfo } from 'node:net';
import { performance } from 'perf_hooks';
import type * as vite from 'vite';
-import type yargs from 'yargs-parser';
-import type { AstroSettings } from '../../@types/astro';
+import type { AstroInlineConfig } from '../../@types/astro';
import { attachContentServerListeners } from '../../content/index.js';
import { telemetry } from '../../events/index.js';
-import { info, warn, type LogOptions } from '../logger/core.js';
+import { info, warn } from '../logger/core.js';
import * as msg from '../messages.js';
-import { printHelp } from '../messages.js';
import { startContainer } from './container.js';
import { createContainerWithAutomaticRestart } from './restart.js';
-export interface DevOptions {
- configFlag: string | undefined;
- configFlagPath: string | undefined;
- flags?: yargs.Arguments;
- logging: LogOptions;
- handleConfigError: (error: Error) => void;
- isRestart?: boolean;
-}
-
export interface DevServer {
address: AddressInfo;
handle: (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) => void;
@@ -30,68 +19,34 @@ export interface DevServer {
}
/** `astro dev` */
-export default async function dev(
- settings: AstroSettings,
- options: DevOptions
-): Promise<DevServer | undefined> {
- if (options.flags?.help || options.flags?.h) {
- printHelp({
- commandName: 'astro dev',
- usage: '[...flags]',
- tables: {
- Flags: [
- ['--port', `Specify which port to run on. Defaults to 3000.`],
- ['--host', `Listen on all addresses, including LAN and public addresses.`],
- ['--host <custom-address>', `Expose on a network IP address at <custom-address>`],
- ['--open', 'Automatically open the app in the browser on server start'],
- ['--help (-h)', 'See all available flags.'],
- ],
- },
- description: `Check ${cyan(
- 'https://docs.astro.build/en/reference/cli-reference/#astro-dev'
- )} for more information.`,
- });
- return;
- }
-
+export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevServer> {
const devStart = performance.now();
await telemetry.record([]);
// Create a container which sets up the Vite server.
- const restart = await createContainerWithAutomaticRestart({
- flags: options.flags ?? {},
- handleConfigError: options.handleConfigError,
- // eslint-disable-next-line no-console
- beforeRestart: () => console.clear(),
- params: {
- settings,
- root: options.flags?.root,
- logging: options.logging,
- isRestart: options.isRestart,
- },
- });
+ const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
+ const logging = restart.container.logging;
// Start listening to the port
const devServerAddressInfo = await startContainer(restart.container);
info(
- options.logging,
+ logging,
null,
msg.serverStart({
startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
- host: settings.config.server.host,
- base: settings.config.base,
- isRestart: options.isRestart,
+ host: restart.container.settings.config.server.host,
+ base: restart.container.settings.config.base,
})
);
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
if (currentVersion.includes('-')) {
- warn(options.logging, null, msg.prerelease({ currentVersion }));
+ warn(logging, null, msg.prerelease({ currentVersion }));
}
- if (restart.container.viteConfig.server?.fs?.strict === false) {
- warn(options.logging, null, msg.fsStrictWarning());
+ if (restart.container.viteServer.config.server?.fs?.strict === false) {
+ warn(logging, null, msg.fsStrictWarning());
}
await attachContentServerListeners(restart.container);
diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts
index a677ad928..473f7198a 100644
--- a/packages/astro/src/core/dev/index.ts
+++ b/packages/astro/src/core/dev/index.ts
@@ -1,3 +1,3 @@
-export { createContainer, isStarted, runInContainer, startContainer } from './container.js';
+export { createContainer, isStarted, startContainer } from './container.js';
export { default } from './dev.js';
export { createContainerWithAutomaticRestart } from './restart.js';
diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts
index d96cc0b50..1acda433e 100644
--- a/packages/astro/src/core/dev/restart.ts
+++ b/packages/astro/src/core/dev/restart.ts
@@ -1,9 +1,15 @@
+import nodeFs from 'node:fs';
+import { fileURLToPath } from 'node:url';
import * as vite from 'vite';
-import type { AstroSettings } from '../../@types/astro';
-import { createSettings, openConfig } from '../config/index.js';
+import type { AstroInlineConfig, AstroSettings } from '../../@types/astro';
+import { eventCliSession, telemetry } from '../../events/index.js';
+import { createNodeLogging, createSettings, resolveConfig } from '../config/index.js';
+import { collectErrorMetadata } from '../errors/dev/utils.js';
+import { isAstroConfigZodError } from '../errors/errors.js';
import { createSafeError } from '../errors/index.js';
-import { info } from '../logger/core.js';
-import type { Container, CreateContainerParams } from './container';
+import { info, error as logError } from '../logger/core.js';
+import { formatErrorMessage } from '../messages.js';
+import type { Container } from './container';
import { createContainer, isStarted, startContainer } from './container.js';
async function createRestartedContainer(
@@ -11,15 +17,13 @@ async function createRestartedContainer(
settings: AstroSettings,
needsStart: boolean
): Promise<Container> {
- const { logging, fs, resolvedRoot, configFlag, configFlagPath } = container;
+ const { logging, fs, inlineConfig } = container;
const newContainer = await createContainer({
isRestart: true,
logging,
settings,
+ inlineConfig,
fs,
- root: resolvedRoot,
- configFlag,
- configFlagPath,
});
if (needsStart) {
@@ -30,7 +34,7 @@ async function createRestartedContainer(
}
export function shouldRestartContainer(
- { settings, configFlag, configFlagPath, restartInFlight }: Container,
+ { settings, inlineConfig, restartInFlight }: Container,
changedFile: string
): boolean {
if (restartInFlight) return false;
@@ -38,10 +42,8 @@ export function shouldRestartContainer(
let shouldRestart = false;
// If the config file changed, reload the config and restart the server.
- if (configFlag) {
- if (!!configFlagPath) {
- shouldRestart = vite.normalizePath(configFlagPath) === vite.normalizePath(changedFile);
- }
+ if (inlineConfig.configFile) {
+ shouldRestart = vite.normalizePath(inlineConfig.configFile) === vite.normalizePath(changedFile);
}
// Otherwise, watch for any astro.config.* file changes in project root
else {
@@ -60,39 +62,16 @@ export function shouldRestartContainer(
return shouldRestart;
}
-interface RestartContainerParams {
- container: Container;
- flags: any;
- logMsg: string;
- handleConfigError: (err: Error) => Promise<void> | void;
- beforeRestart?: () => void;
-}
-
-export async function restartContainer({
- container,
- flags,
- logMsg,
- handleConfigError,
- beforeRestart,
-}: RestartContainerParams): Promise<{ container: Container; error: Error | null }> {
- const { logging, close, resolvedRoot, settings: existingSettings } = container;
+export async function restartContainer(
+ container: Container
+): Promise<{ container: Container; error: Error | null }> {
+ const { logging, close, settings: existingSettings } = container;
container.restartInFlight = true;
- if (beforeRestart) {
- beforeRestart();
- }
const needsStart = isStarted(container);
try {
- const newConfig = await openConfig({
- cwd: resolvedRoot,
- flags,
- cmd: 'dev',
- isRestart: true,
- fsMod: container.fs,
- });
- info(logging, 'astro', logMsg + '\n');
- let astroConfig = newConfig.astroConfig;
- const settings = createSettings(astroConfig, resolvedRoot);
+ const { astroConfig } = await resolveConfig(container.inlineConfig, 'dev', container.fs);
+ const settings = createSettings(astroConfig, fileURLToPath(existingSettings.config.root));
await close();
return {
container: await createRestartedContainer(container, settings, needsStart),
@@ -100,7 +79,18 @@ export async function restartContainer({
};
} catch (_err) {
const error = createSafeError(_err);
- await handleConfigError(error);
+ // Print all error messages except ZodErrors from AstroConfig as the pre-logged error is sufficient
+ if (!isAstroConfigZodError(_err)) {
+ logError(logging, 'config', formatErrorMessage(collectErrorMetadata(error)) + '\n');
+ }
+ // Inform connected clients of the config error
+ container.viteServer.ws.send({
+ type: 'error',
+ err: {
+ message: error.message,
+ stack: error.stack || '',
+ },
+ });
await close();
info(logging, 'astro', 'Continuing with previous valid configuration\n');
return {
@@ -111,10 +101,8 @@ export async function restartContainer({
}
export interface CreateContainerWithAutomaticRestart {
- flags: any;
- params: CreateContainerParams;
- handleConfigError?: (error: Error) => void | Promise<void>;
- beforeRestart?: () => void;
+ inlineConfig?: AstroInlineConfig;
+ fs: typeof nodeFs;
}
interface Restart {
@@ -123,12 +111,17 @@ interface Restart {
}
export async function createContainerWithAutomaticRestart({
- flags,
- handleConfigError = () => {},
- beforeRestart,
- params,
+ inlineConfig,
+ fs,
}: CreateContainerWithAutomaticRestart): Promise<Restart> {
- const initialContainer = await createContainer(params);
+ const logging = createNodeLogging(inlineConfig ?? {});
+ const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'dev', fs);
+ telemetry.record(eventCliSession('dev', userConfig));
+
+ const settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
+
+ const initialContainer = await createContainer({ settings, logging, inlineConfig, fs });
+
let resolveRestart: (value: Error | null) => void;
let restartComplete = new Promise<Error | null>((resolve) => {
resolveRestart = resolve;
@@ -142,24 +135,9 @@ export async function createContainerWithAutomaticRestart({
};
async function handleServerRestart(logMsg: string) {
+ info(logging, 'astro', logMsg + '\n');
const container = restart.container;
- const { container: newContainer, error } = await restartContainer({
- beforeRestart,
- container,
- flags,
- logMsg,
- async handleConfigError(err) {
- // Send an error message to the client if one is connected.
- await handleConfigError(err);
- container.viteServer.ws.send({
- type: 'error',
- err: {
- message: err.message,
- stack: err.stack || '',
- },
- });
- },
- });
+ const { container: newContainer, error } = await restartContainer(container);
restart.container = newContainer;
// Add new watches because this is a new container with a new Vite server
addWatches();
diff --git a/packages/astro/src/core/errors/errors.ts b/packages/astro/src/core/errors/errors.ts
index a73728124..ca4392891 100644
--- a/packages/astro/src/core/errors/errors.ts
+++ b/packages/astro/src/core/errors/errors.ts
@@ -1,3 +1,4 @@
+import type { ZodError } from 'zod';
import { codeFrame } from './printer.js';
import { getErrorDataByTitle } from './utils.js';
@@ -141,6 +142,23 @@ export class AggregateError extends AstroError {
}
}
+const astroConfigZodErrors = new WeakSet<ZodError>();
+
+/**
+ * Check if an error is a ZodError from an AstroConfig validation.
+ * Used to suppress formatting a ZodError if needed.
+ */
+export function isAstroConfigZodError(error: unknown): error is ZodError {
+ return astroConfigZodErrors.has(error as ZodError);
+}
+
+/**
+ * Track that a ZodError comes from an AstroConfig validation.
+ */
+export function trackAstroConfigZodError(error: ZodError): void {
+ astroConfigZodErrors.add(error);
+}
+
/**
* Generic object representing an error with all possible data
* Compatible with both Astro's and Vite's errors
diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts
index d47e54499..fdd5d6fe7 100644
--- a/packages/astro/src/core/preview/index.ts
+++ b/packages/astro/src/core/preview/index.ts
@@ -1,40 +1,24 @@
-import { cyan } from 'kleur/colors';
-import { createRequire } from 'module';
-import { pathToFileURL } from 'node:url';
-import type { Arguments } from 'yargs-parser';
-import type { AstroSettings, PreviewModule, PreviewServer } from '../../@types/astro';
+import { createRequire } from 'node:module';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import type { AstroInlineConfig, PreviewModule, PreviewServer } from '../../@types/astro';
+import { telemetry } from '../../events/index.js';
+import { eventCliSession } from '../../events/session.js';
import { runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
-import type { LogOptions } from '../logger/core';
-import { printHelp } from '../messages.js';
+import { resolveConfig } from '../config/config.js';
+import { createNodeLogging } from '../config/logging.js';
+import { createSettings } from '../config/settings.js';
import createStaticPreviewServer from './static-preview-server.js';
import { getResolvedHostForHttpServer } from './util.js';
-interface PreviewOptions {
- logging: LogOptions;
- flags?: Arguments;
-}
-
/** The primary dev action */
export default async function preview(
- _settings: AstroSettings,
- { logging, flags }: PreviewOptions
+ inlineConfig: AstroInlineConfig
): Promise<PreviewServer | undefined> {
- if (flags?.help || flags?.h) {
- printHelp({
- commandName: 'astro preview',
- usage: '[...flags]',
- tables: {
- Flags: [
- ['--open', 'Automatically open the app in the browser on server start'],
- ['--help (-h)', 'See all available flags.'],
- ],
- },
- description: `Starts a local server to serve your static dist/ directory. Check ${cyan(
- 'https://docs.astro.build/en/reference/cli-reference/#astro-preview'
- )} for more information.`,
- });
- return;
- }
+ const logging = createNodeLogging(inlineConfig);
+ const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'preview');
+ telemetry.record(eventCliSession('preview', userConfig));
+
+ const _settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
const settings = await runHookConfigSetup({
settings: _settings,
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index 95377eeb8..be51f0039 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -1,48 +1,54 @@
import { dim } from 'kleur/colors';
-import type fsMod from 'node:fs';
+import fsMod from 'node:fs';
import { performance } from 'node:perf_hooks';
+import { fileURLToPath } from 'node:url';
import { createServer, type HMRPayload } from 'vite';
-import type { Arguments } from 'yargs-parser';
-import type { AstroSettings } from '../../@types/astro';
+import type { AstroInlineConfig, AstroSettings } from '../../@types/astro';
import { createContentTypesGenerator } from '../../content/index.js';
import { globalContentConfigObserver } from '../../content/utils.js';
+import { telemetry } from '../../events/index.js';
+import { eventCliSession } from '../../events/session.js';
import { runHookConfigSetup } from '../../integrations/index.js';
import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js';
import { getTimeStat } from '../build/util.js';
+import { resolveConfig } from '../config/config.js';
+import { createNodeLogging } from '../config/logging.js';
+import { createSettings } from '../config/settings.js';
import { createVite } from '../create-vite.js';
import { AstroError, AstroErrorData, createSafeError, isAstroError } from '../errors/index.js';
import { info, type LogOptions } from '../logger/core.js';
-import { printHelp } from '../messages.js';
export type ProcessExit = 0 | 1;
export type SyncOptions = {
+ /**
+ * Only used for testing
+ * @internal
+ */
+ fs?: typeof fsMod;
+};
+
+export type SyncInternalOptions = SyncOptions & {
logging: LogOptions;
- fs: typeof fsMod;
};
-export async function syncCli(
- settings: AstroSettings,
- { logging, fs, flags }: { logging: LogOptions; fs: typeof fsMod; flags?: Arguments }
+export async function sync(
+ inlineConfig: AstroInlineConfig,
+ options?: SyncOptions
): Promise<ProcessExit> {
- if (flags?.help || flags?.h) {
- printHelp({
- commandName: 'astro sync',
- usage: '[...flags]',
- tables: {
- Flags: [['--help (-h)', 'See all available flags.']],
- },
- description: `Generates TypeScript types for all Astro modules.`,
- });
- return 0;
- }
+ const logging = createNodeLogging(inlineConfig);
+ const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'sync');
+ telemetry.record(eventCliSession('sync', userConfig));
+
+ const _settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
- const resolvedSettings = await runHookConfigSetup({
- settings,
- logging,
+ const settings = await runHookConfigSetup({
+ settings: _settings,
+ logging: logging,
command: 'build',
});
- return sync(resolvedSettings, { logging, fs });
+
+ return await syncInternal(settings, { logging, fs: options?.fs });
}
/**
@@ -50,15 +56,18 @@ export async function syncCli(
*
* A non-zero process signal is emitted in case there's an error while generating content collection types.
*
+ * This should only be used when the callee already has an `AstroSetting`, otherwise use `sync()` instead.
+ * @internal
+ *
* @param {SyncOptions} options
* @param {AstroSettings} settings Astro settings
* @param {typeof fsMod} options.fs The file system
* @param {LogOptions} options.logging Logging options
* @return {Promise<ProcessExit>}
*/
-export async function sync(
+export async function syncInternal(
settings: AstroSettings,
- { logging, fs }: SyncOptions
+ { logging, fs }: SyncInternalOptions
): Promise<ProcessExit> {
const timerStart = performance.now();
// Needed to load content config
@@ -88,7 +97,7 @@ export async function sync(
const contentTypesGenerator = await createContentTypesGenerator({
contentConfigObserver: globalContentConfigObserver,
logging,
- fs,
+ fs: fs ?? fsMod,
settings,
viteServer: tempViteServer,
});
@@ -124,7 +133,7 @@ export async function sync(
}
info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
- await setUpEnvTs({ settings, logging, fs });
+ await setUpEnvTs({ settings, logging, fs: fs ?? fsMod });
return 0;
}
diff --git a/packages/astro/test/astro-markdown-url.test.js b/packages/astro/test/astro-markdown-url.test.js
index 01d777b9f..a96ce60d4 100644
--- a/packages/astro/test/astro-markdown-url.test.js
+++ b/packages/astro/test/astro-markdown-url.test.js
@@ -9,7 +9,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: always', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/with-subpath-always/', import.meta.url),
+ outDir: './with-subpath-always',
base: '/my-cool-base',
trailingSlash: 'always',
});
@@ -24,7 +24,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: never', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/with-subpath-never/', import.meta.url),
+ outDir: './with-subpath-never',
base: '/my-cool-base',
trailingSlash: 'never',
});
@@ -39,7 +39,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: ignore', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/with-subpath-ignore/', import.meta.url),
+ outDir: './with-subpath-ignore',
base: '/my-cool-base',
trailingSlash: 'ignore',
});
@@ -58,7 +58,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: always', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/without-subpath-always/', import.meta.url),
+ outDir: './without-subpath-always',
trailingSlash: 'always',
});
await fixture.build();
@@ -72,7 +72,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: never', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/without-subpath-never/', import.meta.url),
+ outDir: './without-subpath-never',
trailingSlash: 'never',
});
await fixture.build();
@@ -86,7 +86,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: ignore', async () => {
let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/',
- outDir: new URL('./fixtures/astro-markdown-url/without-subpath-ignore/', import.meta.url),
+ outDir: './without-subpath-ignore',
trailingSlash: 'ignore',
});
await fixture.build();
diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js
index 5f1fb2d14..ba436fe21 100644
--- a/packages/astro/test/astro-sync.test.js
+++ b/packages/astro/test/astro-sync.test.js
@@ -19,7 +19,7 @@ describe('astro sync', () => {
},
},
};
- await fixture.sync({ fs: fsMock });
+ await fixture.sync({}, { fs: fsMock });
const expectedTypesFile = new URL('.astro/types.d.ts', fixture.config.root).href;
expect(writtenFiles).to.haveOwnProperty(expectedTypesFile);
@@ -55,7 +55,7 @@ describe('astro sync', () => {
},
},
};
- await fixture.sync({ fs: fsMock });
+ await fixture.sync({}, { fs: fsMock });
expect(writtenFiles, 'Did not try to update env.d.ts file.').to.haveOwnProperty(typesEnvPath);
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference path="../.astro/types.d.ts" />`);
@@ -79,7 +79,7 @@ describe('astro sync', () => {
},
},
};
- await fixture.sync({ fs: fsMock });
+ await fixture.sync({}, { fs: fsMock });
expect(writtenFiles, 'Did not try to write env.d.ts file.').to.haveOwnProperty(typesEnvPath);
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference types="astro/client" />`);
diff --git a/packages/astro/test/client-address.test.js b/packages/astro/test/client-address.test.js
index e351b44cd..6e78832ce 100644
--- a/packages/astro/test/client-address.test.js
+++ b/packages/astro/test/client-address.test.js
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import { loadFixture, silentLogging } from './test-utils.js';
+import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
import * as cheerio from 'cheerio';
@@ -108,8 +108,7 @@ describe('Astro.clientAddress', () => {
let devServer;
before(async () => {
- // We expect an error, so silence the output
- devServer = await fixture.startDevServer({ logging: silentLogging });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/dev-routing.test.js b/packages/astro/test/dev-routing.test.js
index c42c25dc0..186355b43 100644
--- a/packages/astro/test/dev-routing.test.js
+++ b/packages/astro/test/dev-routing.test.js
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import { loadFixture, silentLogging } from './test-utils.js';
+import { loadFixture } from './test-utils.js';
describe('Development Routing', () => {
describe('No site config', () => {
@@ -10,9 +10,7 @@ describe('Development Routing', () => {
before(async () => {
fixture = await loadFixture({ root: './fixtures/without-site-config/' });
- devServer = await fixture.startDevServer({
- logging: silentLogging,
- });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/dynamic-endpoint-collision.test.js b/packages/astro/test/dynamic-endpoint-collision.test.js
index c0a11b5f8..329e3603b 100644
--- a/packages/astro/test/dynamic-endpoint-collision.test.js
+++ b/packages/astro/test/dynamic-endpoint-collision.test.js
@@ -1,6 +1,6 @@
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
-import { loadFixture, silentLogging } from './test-utils.js';
+import { loadFixture } from './test-utils.js';
describe('Dynamic endpoint collision', () => {
describe('build', () => {
@@ -31,9 +31,7 @@ describe('Dynamic endpoint collision', () => {
root: './fixtures/dynamic-endpoint-collision/',
});
- devServer = await fixture.startDevServer({
- logging: silentLogging,
- });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/error-bad-js.test.js b/packages/astro/test/error-bad-js.test.js
index ba02c62ff..dc04016f5 100644
--- a/packages/astro/test/error-bad-js.test.js
+++ b/packages/astro/test/error-bad-js.test.js
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import { loadFixture, silentLogging } from './test-utils.js';
+import { loadFixture } from './test-utils.js';
describe('Errors in JavaScript', () => {
/** @type {import('./test-utils').Fixture} */
@@ -15,9 +15,7 @@ describe('Errors in JavaScript', () => {
logLevel: 'silent',
},
});
- devServer = await fixture.startDevServer({
- logging: silentLogging,
- });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/error-non-error.test.js b/packages/astro/test/error-non-error.test.js
index 023807be8..facf99633 100644
--- a/packages/astro/test/error-non-error.test.js
+++ b/packages/astro/test/error-non-error.test.js
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import { loadFixture, silentLogging } from './test-utils.js';
+import { loadFixture } from './test-utils.js';
describe('Can handle errors that are not instanceof Error', () => {
/** @type {import('./test-utils').Fixture} */
@@ -12,9 +12,7 @@ describe('Can handle errors that are not instanceof Error', () => {
fixture = await loadFixture({
root: './fixtures/error-non-error',
});
- devServer = await fixture.startDevServer({
- logging: silentLogging,
- });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/preview-routing.test.js b/packages/astro/test/preview-routing.test.js
index 591618126..5c365b07c 100644
--- a/packages/astro/test/preview-routing.test.js
+++ b/packages/astro/test/preview-routing.test.js
@@ -13,7 +13,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4000/', import.meta.url),
+ outDir: './dist-4000',
build: {
format: 'directory',
},
@@ -41,9 +41,10 @@ describe('Preview Routing', () => {
expect(response.redirected).to.equal(false);
});
- it('404 when loading subpath root without trailing slash', async () => {
+ it('200 when loading subpath root without trailing slash', async () => {
const response = await fixture.fetch('/blog');
- expect(response.status).to.equal(404);
+ expect(response.status).to.equal(200);
+ expect(response.redirected).to.equal(false);
});
it('404 when loading another page with subpath used', async () => {
@@ -72,7 +73,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4001/', import.meta.url),
+ outDir: './dist-4001',
trailingSlash: 'always',
server: {
port: 4001,
@@ -132,7 +133,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4002/', import.meta.url),
+ outDir: './dist-4002',
trailingSlash: 'ignore',
server: {
port: 4002,
@@ -194,7 +195,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4003/', import.meta.url),
+ outDir: './dist-4003',
build: {
format: 'file',
},
@@ -222,9 +223,10 @@ describe('Preview Routing', () => {
expect(response.redirected).to.equal(false);
});
- it('404 when loading subpath root without trailing slash', async () => {
+ it('200 when loading subpath root without trailing slash', async () => {
const response = await fixture.fetch('/blog');
- expect(response.status).to.equal(404);
+ expect(response.status).to.equal(200);
+ expect(response.redirected).to.equal(false);
});
it('404 when loading another page with subpath used', async () => {
@@ -253,7 +255,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4004/', import.meta.url),
+ outDir: './dist-4004',
build: {
format: 'file',
},
@@ -316,7 +318,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4005/', import.meta.url),
+ outDir: './dist-4005',
build: {
format: 'file',
},
@@ -379,7 +381,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog',
- outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4006/', import.meta.url),
+ outDir: './dist-4006',
build: {
format: 'file',
},
diff --git a/packages/astro/test/react-component.test.js b/packages/astro/test/react-component.test.js
index 7c0de3686..a6bb8cfae 100644
--- a/packages/astro/test/react-component.test.js
+++ b/packages/astro/test/react-component.test.js
@@ -1,6 +1,6 @@
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
-import { isWindows, loadFixture, silentLogging } from './test-utils.js';
+import { isWindows, loadFixture } from './test-utils.js';
let fixture;
@@ -108,9 +108,7 @@ describe('React Components', () => {
let devServer;
before(async () => {
- devServer = await fixture.startDevServer({
- logging: silentLogging,
- });
+ devServer = await fixture.startDevServer();
});
after(async () => {
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index 94912687f..27f5d83f7 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -3,6 +3,7 @@ import { execa } from 'execa';
import fastGlob from 'fast-glob';
import fs from 'node:fs';
import os from 'node:os';
+import path from 'node:path';
import { fileURLToPath } from 'node:url';
import stripAnsi from 'strip-ansi';
import { check } from '../dist/cli/check/index.js';
@@ -10,8 +11,7 @@ import build from '../dist/core/build/index.js';
import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js';
import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js';
import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js';
-import { openConfig } from '../dist/core/config/config.js';
-import { createSettings } from '../dist/core/config/index.js';
+import { mergeConfig, resolveConfig } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js';
import preview from '../dist/core/preview/index.js';
@@ -28,7 +28,7 @@ process.env.ASTRO_TELEMETRY_DISABLED = true;
/**
* @typedef {import('undici').Response} Response
* @typedef {import('../src/core/dev/dev').DedvServer} DevServer
- * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
+ * @typedef {import('../src/@types/astro').AstroInlineConfig & { root?: string | URL }} AstroInlineConfig
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
* @typedef {import('../src/core/app/index').App} App
* @typedef {import('../src/cli/check/index').AstroChecker} AstroChecker
@@ -43,12 +43,13 @@ process.env.ASTRO_TELEMETRY_DISABLED = true;
* @property {(path: string, updater: (content: string) => string) => Promise<void>} writeFile
* @property {(path: string) => Promise<string[]>} readdir
* @property {(pattern: string) => Promise<string[]>} glob
- * @property {() => Promise<DevServer>} startDevServer
- * @property {() => Promise<PreviewServer>} preview
+ * @property {typeof dev} startDevServer
+ * @property {typeof preview} preview
* @property {() => Promise<void>} clean
* @property {() => Promise<App>} loadTestAdapterApp
* @property {() => Promise<void>} onNextChange
- * @property {(opts: CheckPayload) => Promise<AstroChecker>} check
+ * @property {typeof check} check
+ * @property {typeof sync} sync
*
* This function returns an instance of the Check
*
@@ -82,7 +83,7 @@ export const silentLogging = {
/**
* Load Astro fixture
- * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`)
+ * @param {AstroInlineConfig} inlineConfig Astro config partial (note: must specify `root`)
* @returns {Promise<Fixture>} The fixture. Has the following properties:
* .config - Returns the final config. Will be automatically passed to the methods below:
*
@@ -103,50 +104,25 @@ export const silentLogging = {
export async function loadFixture(inlineConfig) {
if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
- // load config
- let cwd = inlineConfig.root;
- delete inlineConfig.root;
- if (typeof cwd === 'string') {
- try {
- cwd = new URL(cwd.replace(/\/?$/, '/'));
- } catch (err1) {
- cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url);
- }
- }
-
- /** @type {import('../src/core/logger/core').LogOptions} */
- const logging = defaultLogging;
+ // Silent by default during tests to not pollute the console output
+ inlineConfig.logLevel = 'silent';
- // Load the config.
- let { astroConfig: config } = await openConfig({
- cwd: fileURLToPath(cwd),
- logging,
- cmd: 'dev',
- });
- config = merge(config, { ...inlineConfig, root: cwd });
-
- // HACK: the inline config doesn't run through config validation where these normalizations usually occur
- if (typeof inlineConfig.site === 'string') {
- config.site = new URL(inlineConfig.site);
+ let root = inlineConfig.root;
+ // Handle URL, should already be absolute so just convert to path
+ if (typeof root !== 'string') {
+ root = fileURLToPath(root);
}
- if (inlineConfig.base && !inlineConfig.base.endsWith('/')) {
- config.base = inlineConfig.base + '/';
+ // Handle "file:///C:/Users/fred", convert to "C:/Users/fred"
+ else if (root.startsWith('file://')) {
+ root = fileURLToPath(new URL(root));
}
-
- /**
- * The dev/build/sync/check commands run integrations' `astro:config:setup` hook that could mutate
- * the `AstroSettings`. This function helps to create a fresh settings object that is used by the
- * command functions below to prevent tests from polluting each other.
- */
- const getSettings = async () => {
- let settings = createSettings(config, fileURLToPath(cwd));
- if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) {
- // Enable default JSX integration. It needs to come first, so unshift rather than push!
- const { default: jsxRenderer } = await import('astro/jsx/renderer.js');
- settings.renderers.unshift(jsxRenderer);
- }
- return settings;
- };
+ // Handle "./fixtures/...", convert to absolute path
+ else if (!path.isAbsolute(root)) {
+ root = fileURLToPath(new URL(root, import.meta.url));
+ }
+ inlineConfig = { ...inlineConfig, root };
+ // Load the config.
+ const { astroConfig: config } = await resolveConfig(inlineConfig, 'dev');
const resolveUrl = (url) =>
`http://${config.server.host || 'localhost'}:${config.server.port}${url.replace(/^\/?/, '/')}`;
@@ -177,17 +153,19 @@ export async function loadFixture(inlineConfig) {
let devServer;
return {
- build: async (opts = {}) => {
+ build: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'production';
- return build(await getSettings('build'), { logging, ...opts });
+ return build(mergeConfig(inlineConfig, extraInlineConfig));
+ },
+ sync: async (extraInlineConfig = {}, opts) => {
+ return sync(mergeConfig(inlineConfig, extraInlineConfig), opts);
},
- sync: async (opts) => sync(await getSettings('build'), { logging, fs, ...opts }),
check: async (opts) => {
- return await check(await getSettings('build'), { logging, ...opts });
+ return await check(opts);
},
- startDevServer: async (opts = {}) => {
+ startDevServer: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'development';
- devServer = await dev(await getSettings('dev'), { logging, ...opts });
+ devServer = await dev(mergeConfig(inlineConfig, extraInlineConfig));
config.server.host = parseAddressToHost(devServer.address.address); // update host
config.server.port = devServer.address.port; // update port
return devServer;
@@ -207,9 +185,9 @@ export async function loadFixture(inlineConfig) {
throw err;
}
},
- preview: async (opts = {}) => {
+ preview: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'production';
- const previewServer = await preview(await getSettings('build'), { logging, ...opts });
+ const previewServer = await preview(mergeConfig(inlineConfig, extraInlineConfig));
config.server.host = parseAddressToHost(previewServer.host); // update host
config.server.port = previewServer.port; // update port
return previewServer;
@@ -282,32 +260,6 @@ function parseAddressToHost(address) {
return address;
}
-/**
- * Basic object merge utility. Returns new copy of merged Object.
- * @param {Object} a
- * @param {Object} b
- * @returns {Object}
- */
-function merge(a, b) {
- const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
- const c = {};
- for (const k of allKeys) {
- const needsObjectMerge =
- typeof a[k] === 'object' &&
- typeof b[k] === 'object' &&
- (Object.keys(a[k]).length || Object.keys(b[k]).length) &&
- !Array.isArray(a[k]) &&
- !Array.isArray(b[k]);
- if (needsObjectMerge) {
- c[k] = merge(a[k] || {}, b[k] || {});
- continue;
- }
- c[k] = a[k];
- if (b[k] !== undefined) c[k] = b[k];
- }
- return c;
-}
-
const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url));
/** Returns a process running the Astro CLI. */
diff --git a/packages/astro/test/units/config/config-server.test.js b/packages/astro/test/units/config/config-server.test.js
index 3486374e8..8a60c53bf 100644
--- a/packages/astro/test/units/config/config-server.test.js
+++ b/packages/astro/test/units/config/config-server.test.js
@@ -1,24 +1,25 @@
import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
-import { defaultLogging } from '../test-utils.js';
-import { openConfig } from '../../../dist/core/config/index.js';
+import { flagsToAstroInlineConfig } from '../../../dist/cli/flags.js';
+import { resolveConfig } from '../../../dist/core/config/index.js';
const cwd = fileURLToPath(new URL('../../fixtures/config-host/', import.meta.url));
describe('config.server', () => {
- function openConfigWithFlags(flags) {
- return openConfig({
- cwd: flags.root || cwd,
- flags,
- cmd: 'dev',
- logging: defaultLogging,
- });
+ function resolveConfigWithFlags(flags) {
+ return resolveConfig(
+ flagsToAstroInlineConfig({
+ root: cwd,
+ ...flags,
+ }),
+ 'dev'
+ );
}
describe('host', () => {
it('can be specified via --host flag', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
- const { astroConfig } = await openConfigWithFlags({
+ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL),
host: true,
});
@@ -32,7 +33,7 @@ describe('config.server', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = 'my-config.mjs';
- const { astroConfig } = await openConfigWithFlags({
+ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL,
});
@@ -44,7 +45,7 @@ describe('config.server', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './my-config.mjs';
- const { astroConfig } = await openConfigWithFlags({
+ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL,
});
@@ -57,7 +58,7 @@ describe('config.server', () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './does-not-exist.mjs';
try {
- await openConfigWithFlags({
+ await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL,
});
diff --git a/packages/astro/test/units/config/config-validate.test.js b/packages/astro/test/units/config/config-validate.test.js
index fa7418c56..49fd6b418 100644
--- a/packages/astro/test/units/config/config-validate.test.js
+++ b/packages/astro/test/units/config/config-validate.test.js
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import { z } from 'zod';
import stripAnsi from 'strip-ansi';
import { formatConfigErrorMessage } from '../../../dist/core/messages.js';
-import { validateConfig } from '../../../dist/core/config/index.js';
+import { validateConfig } from '../../../dist/core/config/config.js';
describe('Config Validation', () => {
it('empty user config is valid', async () => {
diff --git a/packages/astro/test/units/config/format.test.js b/packages/astro/test/units/config/format.test.js
index b230ea6b4..6cd332101 100644
--- a/packages/astro/test/units/config/format.test.js
+++ b/packages/astro/test/units/config/format.test.js
@@ -1,8 +1,6 @@
+import { fileURLToPath } from 'node:url';
import { expect } from 'chai';
-
-import { createSettings, openConfig } from '../../../dist/core/config/index.js';
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, defaultLogging } from '../test-utils.js';
+import { createFs, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/tailwindcss-ts/', import.meta.url);
@@ -20,16 +18,7 @@ describe('Astro config formats', () => {
root
);
- const { astroConfig } = await openConfig({
- cwd: root,
- flags: {},
- cmd: 'dev',
- logging: defaultLogging,
- fsMod: fs,
- });
- const settings = createSettings(astroConfig);
-
- await runInContainer({ fs, root, settings }, () => {
+ await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, () => {
expect(true).to.equal(
true,
'We were able to get into the container which means the config loaded.'
diff --git a/packages/astro/test/units/content-collections/frontmatter.test.js b/packages/astro/test/units/content-collections/frontmatter.test.js
index d35a6df33..f06b33710 100644
--- a/packages/astro/test/units/content-collections/frontmatter.test.js
+++ b/packages/astro/test/units/content-collections/frontmatter.test.js
@@ -2,9 +2,8 @@ import { fileURLToPath } from 'node:url';
import nodeFS from 'node:fs';
import path from 'node:path';
-import { runInContainer } from '../../../dist/core/dev/index.js';
import { attachContentServerListeners } from '../../../dist/content/index.js';
-import { createFs, triggerFSEvent } from '../test-utils.js';
+import { createFs, runInContainer, triggerFSEvent } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -53,7 +52,7 @@ describe('frontmatter', () => {
root
);
- await runInContainer({ fs, root }, async (container) => {
+ await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
await attachContentServerListeners(container);
fs.writeFileFromRootSync(
diff --git a/packages/astro/test/units/dev/base.test.js b/packages/astro/test/units/dev/base.test.js
index d3dd94341..041d6bcb5 100644
--- a/packages/astro/test/units/dev/base.test.js
+++ b/packages/astro/test/units/dev/base.test.js
@@ -1,7 +1,6 @@
import { expect } from 'chai';
-
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse } from '../test-utils.js';
+import { fileURLToPath } from 'node:url';
+import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -19,8 +18,8 @@ describe('base configuration', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
base: '/docs',
trailingSlash: 'never',
},
@@ -48,8 +47,8 @@ describe('base configuration', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
base: '/docs',
trailingSlash: 'never',
},
@@ -79,8 +78,8 @@ describe('base configuration', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
base: '/docs',
trailingSlash: 'never',
},
@@ -108,8 +107,8 @@ describe('base configuration', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
base: '/docs',
trailingSlash: 'never',
},
diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
index 4ebf2b510..344615346 100644
--- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
+++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js
@@ -1,18 +1,12 @@
import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
-import { validateConfig } from '../../../dist/core/config/config.js';
-import { createSettings } from '../../../dist/core/config/index.js';
import { sync as _sync } from '../../../dist/core/sync/index.js';
-import { createFsWithFallback, defaultLogging } from '../test-utils.js';
+import { createFsWithFallback } from '../test-utils.js';
const root = new URL('../../fixtures/content-mixed-errors/', import.meta.url);
-const logging = defaultLogging;
async function sync({ fs, config = {} }) {
- const astroConfig = await validateConfig(config, fileURLToPath(root), 'prod');
- const settings = createSettings(astroConfig, fileURLToPath(root));
-
- return _sync(settings, { logging, fs });
+ return _sync({ ...config, root: fileURLToPath(root), logLevel: 'silent' }, { fs });
}
describe('Content Collections - mixed content errors', () => {
diff --git a/packages/astro/test/units/dev/collections-renderentry.test.js b/packages/astro/test/units/dev/collections-renderentry.test.js
index d3f784925..873bb9164 100644
--- a/packages/astro/test/units/dev/collections-renderentry.test.js
+++ b/packages/astro/test/units/dev/collections-renderentry.test.js
@@ -1,16 +1,16 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import os from 'node:os';
+import { fileURLToPath } from 'node:url';
-import mdx from '../../../../integrations/mdx/dist/index.js';
import { attachContentServerListeners } from '../../../dist/content/server-listeners.js';
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFsWithFallback, createRequestAndResponse } from '../test-utils.js';
+import { createFsWithFallback, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/content/', import.meta.url);
const describe = os.platform() === 'win32' ? global.describe.skip : global.describe;
+/** @type {typeof runInContainer} */
async function runInContainerWithContentListeners(params, callback) {
return await runInContainer(params, async (container) => {
await attachContentServerListeners(container);
@@ -56,9 +56,8 @@ describe('Content Collections - render()', () => {
await runInContainerWithContentListeners(
{
fs,
- root,
- userConfig: {
- integrations: [mdx()],
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
@@ -129,9 +128,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners(
{
fs,
- root,
- userConfig: {
- integrations: [mdx()],
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
@@ -200,9 +198,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners(
{
fs,
- root,
- userConfig: {
- integrations: [mdx()],
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
@@ -270,9 +267,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners(
{
fs,
- root,
- userConfig: {
- integrations: [mdx()],
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
diff --git a/packages/astro/test/units/dev/dev.test.js b/packages/astro/test/units/dev/dev.test.js
index 7c361b9de..9762be7eb 100644
--- a/packages/astro/test/units/dev/dev.test.js
+++ b/packages/astro/test/units/dev/dev.test.js
@@ -1,8 +1,12 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
-
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
+import { fileURLToPath } from 'node:url';
+import {
+ createFs,
+ createRequestAndResponse,
+ triggerFSEvent,
+ runInContainer,
+} from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -25,7 +29,7 @@ describe('dev container', () => {
root
);
- await runInContainer({ fs, root }, async (container) => {
+ await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
const { req, res, text } = createRequestAndResponse({
method: 'GET',
url: '/',
@@ -60,7 +64,7 @@ describe('dev container', () => {
root
);
- await runInContainer({ fs, root }, async (container) => {
+ await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
let r = createRequestAndResponse({
method: 'GET',
url: '/',
@@ -119,8 +123,8 @@ describe('dev container', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
output: 'server',
integrations: [
{
@@ -170,8 +174,8 @@ describe('dev container', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
output: 'server',
integrations: [
{
@@ -223,8 +227,8 @@ describe('dev container', () => {
it('items in public/ are not available from root when using a base', async () => {
await runInContainer(
{
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
base: '/sub/',
},
},
@@ -256,7 +260,7 @@ describe('dev container', () => {
});
it('items in public/ are available from root when not using a base', async () => {
- await runInContainer({ root }, async (container) => {
+ await runInContainer({ inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
// Try the root path
let r = createRequestAndResponse({
method: 'GET',
diff --git a/packages/astro/test/units/dev/head-injection.test.js b/packages/astro/test/units/dev/head-injection.test.js
index ed3e085d3..566e7ab48 100644
--- a/packages/astro/test/units/dev/head-injection.test.js
+++ b/packages/astro/test/units/dev/head-injection.test.js
@@ -1,8 +1,7 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
-
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse } from '../test-utils.js';
+import { fileURLToPath } from 'node:url';
+import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -65,8 +64,8 @@ describe('head injection', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
@@ -154,8 +153,8 @@ describe('head injection', () => {
await runInContainer(
{
fs,
- root,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
vite: { server: { middlewareMode: true } },
},
},
diff --git a/packages/astro/test/units/dev/hydration.test.js b/packages/astro/test/units/dev/hydration.test.js
index 7285f5e71..ae4cb2d99 100644
--- a/packages/astro/test/units/dev/hydration.test.js
+++ b/packages/astro/test/units/dev/hydration.test.js
@@ -1,12 +1,10 @@
import { expect } from 'chai';
-
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse, silentLogging } from '../test-utils.js';
-import svelte from '../../../../integrations/svelte/dist/index.js';
+import { fileURLToPath } from 'node:url';
+import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
-describe('dev container', () => {
+describe('hydration', () => {
it('should not crash when reassigning a hydrated component', async () => {
const fs = createFs(
{
@@ -31,10 +29,9 @@ describe('dev container', () => {
await runInContainer(
{
fs,
- root,
- logging: silentLogging,
- userConfig: {
- integrations: [svelte()],
+ inlineConfig: {
+ root: fileURLToPath(root),
+ logLevel: 'silent',
},
},
async (container) => {
diff --git a/packages/astro/test/units/dev/restart.test.js b/packages/astro/test/units/dev/restart.test.js
index 5dd03fe46..c1a7a3c21 100644
--- a/packages/astro/test/units/dev/restart.test.js
+++ b/packages/astro/test/units/dev/restart.test.js
@@ -2,18 +2,12 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url';
-import { createSettings, openConfig } from '../../../dist/core/config/index.js';
import {
createContainerWithAutomaticRestart,
isStarted,
startContainer,
} from '../../../dist/core/dev/index.js';
-import {
- createFs,
- createRequestAndResponse,
- defaultLogging,
- triggerFSEvent,
-} from '../test-utils.js';
+import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -36,8 +30,9 @@ describe('dev container restarts', () => {
root
);
- let restart = await createContainerWithAutomaticRestart({
- params: { fs, root },
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
});
try {
@@ -99,8 +94,9 @@ describe('dev container restarts', () => {
root
);
- let restart = await createContainerWithAutomaticRestart({
- params: { fs, root },
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
});
await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true);
@@ -127,16 +123,9 @@ describe('dev container restarts', () => {
troot
);
- const { astroConfig } = await openConfig({
- cwd: troot,
- flags: {},
- cmd: 'dev',
- logging: defaultLogging,
- });
- const settings = createSettings(astroConfig);
-
- let restart = await createContainerWithAutomaticRestart({
- params: { fs, root, settings },
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
});
await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true);
@@ -161,16 +150,9 @@ describe('dev container restarts', () => {
root
);
- const { astroConfig } = await openConfig({
- cwd: root,
- flags: {},
- cmd: 'dev',
- logging: defaultLogging,
- });
- const settings = createSettings(astroConfig, fileURLToPath(root));
-
- let restart = await createContainerWithAutomaticRestart({
- params: { fs, root, settings },
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
});
await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true);
@@ -193,16 +175,9 @@ describe('dev container restarts', () => {
root
);
- const { astroConfig } = await openConfig({
- cwd: root,
- flags: {},
- cmd: 'dev',
- logging: defaultLogging,
- });
- const settings = createSettings(astroConfig, fileURLToPath(root));
-
- let restart = await createContainerWithAutomaticRestart({
- params: { fs, root, settings },
+ const restart = await createContainerWithAutomaticRestart({
+ fs,
+ inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
});
await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true);
diff --git a/packages/astro/test/units/render/components.test.js b/packages/astro/test/units/render/components.test.js
index 38f9e1062..0b7352453 100644
--- a/packages/astro/test/units/render/components.test.js
+++ b/packages/astro/test/units/render/components.test.js
@@ -1,9 +1,8 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
-
-import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse, silentLogging } from '../test-utils.js';
+import { fileURLToPath } from 'node:url';
import svelte from '../../../../integrations/svelte/dist/index.js';
+import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -31,9 +30,9 @@ describe('core/render components', () => {
await runInContainer(
{
fs,
- root,
- logging: silentLogging,
- userConfig: {
+ inlineConfig: {
+ root: fileURLToPath(root),
+ logLevel: 'silent',
integrations: [svelte()],
},
},
diff --git a/packages/astro/test/units/routing/manifest.test.js b/packages/astro/test/units/routing/manifest.test.js
index 7b8ee3e26..cf3fb0bf1 100644
--- a/packages/astro/test/units/routing/manifest.test.js
+++ b/packages/astro/test/units/routing/manifest.test.js
@@ -1,10 +1,8 @@
import { expect } from 'chai';
-import { createFs } from '../test-utils.js';
-import { createRouteManifest } from '../../../dist/core/routing/manifest/create.js';
-import { createDefaultDevSettings } from '../../../dist/core/config/index.js';
import { fileURLToPath } from 'node:url';
-import { defaultLogging } from '../test-utils.js';
+import { createRouteManifest } from '../../../dist/core/routing/manifest/create.js';
+import { createBasicSettings, createFs, defaultLogging } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -16,13 +14,11 @@ describe('routing - createRouteManifest', () => {
},
root
);
- const settings = await createDefaultDevSettings(
- {
- base: '/search',
- trailingSlash: 'never',
- },
- root
- );
+ const settings = await createBasicSettings({
+ root: fileURLToPath(root),
+ base: '/search',
+ trailingSlash: 'never',
+ });
const manifest = createRouteManifest({
cwd: fileURLToPath(root),
settings,
@@ -41,17 +37,15 @@ describe('routing - createRouteManifest', () => {
},
root
);
- const settings = await createDefaultDevSettings(
- {
- base: '/search',
- trailingSlash: 'never',
- redirects: {
- '/blog/[...slug]': '/',
- '/blog/contributing': '/another',
- },
+ const settings = await createBasicSettings({
+ root: fileURLToPath(root),
+ base: '/search',
+ trailingSlash: 'never',
+ redirects: {
+ '/blog/[...slug]': '/',
+ '/blog/contributing': '/another',
},
- root
- );
+ });
const manifest = createRouteManifest({
cwd: fileURLToPath(root),
settings,
@@ -70,15 +64,13 @@ describe('routing - createRouteManifest', () => {
},
root
);
- const settings = await createDefaultDevSettings(
- {
- trailingSlash: 'never',
- redirects: {
- '/foo': '/bar',
- },
+ const settings = await createBasicSettings({
+ root: fileURLToPath(root),
+ trailingSlash: 'never',
+ redirects: {
+ '/foo': '/bar',
},
- root
- );
+ });
const manifest = createRouteManifest(
{
cwd: fileURLToPath(root),
diff --git a/packages/astro/test/units/routing/route-matching.test.js b/packages/astro/test/units/routing/route-matching.test.js
index 50b8d58c9..e1c4df5c5 100644
--- a/packages/astro/test/units/routing/route-matching.test.js
+++ b/packages/astro/test/units/routing/route-matching.test.js
@@ -1,5 +1,9 @@
-// @ts-check
-import { createFs, createRequestAndResponse, defaultLogging } from '../test-utils.js';
+import {
+ createBasicSettings,
+ createFs,
+ createRequestAndResponse,
+ defaultLogging,
+} from '../test-utils.js';
import { createRouteManifest, matchAllRoutes } from '../../../dist/core/routing/index.js';
import { fileURLToPath } from 'node:url';
import { createViteLoader } from '../../../dist/core/module-loader/vite.js';
@@ -127,16 +131,17 @@ describe('Route matching', () => {
before(async () => {
const fs = createFs(fileSystem, root);
+ settings = await createBasicSettings({
+ root: fileURLToPath(root),
+ trailingSlash: 'never',
+ output: 'hybrid',
+ adapter: testAdapter(),
+ });
container = await createContainer({
fs,
- root,
- userConfig: {
- trailingSlash: 'never',
- output: 'hybrid',
- adapter: testAdapter(),
- },
+ settings,
+ logging: defaultLogging,
});
- settings = container.settings;
const loader = createViteLoader(container.viteServer);
const manifest = createDevelopmentManifest(container.settings);
diff --git a/packages/astro/test/units/shiki/shiki.test.js b/packages/astro/test/units/shiki/shiki.test.js
index 0d67dda35..e5b78963f 100644
--- a/packages/astro/test/units/shiki/shiki.test.js
+++ b/packages/astro/test/units/shiki/shiki.test.js
@@ -1,6 +1,8 @@
import { expect } from 'chai';
+import { fileURLToPath } from 'node:url';
import { createContainer } from '../../../dist/core/dev/index.js';
import { createViteLoader } from '../../../dist/core/module-loader/index.js';
+import { createBasicSettings, defaultLogging } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -9,7 +11,8 @@ describe('<Code />', () => {
let container;
let mod;
before(async () => {
- container = await createContainer({ root });
+ const settings = await createBasicSettings({ root: fileURLToPath(root) });
+ container = await createContainer({ settings, logging: defaultLogging });
const loader = createViteLoader(container.viteServer);
mod = await loader.import('astro/components/Shiki.js');
});
diff --git a/packages/astro/test/units/test-utils.js b/packages/astro/test/units/test-utils.js
index 46910416d..80dff5ddb 100644
--- a/packages/astro/test/units/test-utils.js
+++ b/packages/astro/test/units/test-utils.js
@@ -8,6 +8,9 @@ import { getDefaultClientDirectives } from '../../dist/core/client-directive/ind
import { nodeLogDestination } from '../../dist/core/logger/node.js';
import { createEnvironment } from '../../dist/core/render/index.js';
import { RouteCache } from '../../dist/core/render/route-cache.js';
+import { resolveConfig } from '../../dist/core/config/index.js';
+import { createBaseSettings } from '../../dist/core/config/settings.js';
+import { createContainer } from '../../dist/core/dev/container.js';
import { unixify } from './correct-path.js';
/** @type {import('../../src/core/logger/core').LogOptions} */
@@ -189,3 +192,42 @@ export function createBasicEnvironment(options = {}) {
streaming: options.streaming ?? true,
});
}
+
+/**
+ * @param {import('../../src/@types/astro.js').AstroInlineConfig} inlineConfig
+ * @returns {Promise<import('../../src/@types/astro.js').AstroSettings>}
+ */
+export async function createBasicSettings(inlineConfig = {}) {
+ if (!inlineConfig.root) {
+ inlineConfig.root = fileURLToPath(new URL('.', import.meta.url));
+ }
+ const { astroConfig } = await resolveConfig(inlineConfig, 'dev');
+ return createBaseSettings(astroConfig);
+}
+
+/**
+ * @typedef {{
+ * fs?: typeof realFS,
+ * inlineConfig?: import('../../src/@types/astro.js').AstroInlineConfig,
+ * logging?: import('../../src/core/logger/core').LogOptions,
+ * }} RunInContainerOptions
+ */
+
+/**
+ * @param {RunInContainerOptions} options
+ * @param {(container: import('../../src/core/dev/container.js').Container) => Promise<void> | void} callback
+ */
+export async function runInContainer(options = {}, callback) {
+ const settings = await createBasicSettings(options.inlineConfig ?? {});
+ const container = await createContainer({
+ fs: options?.fs ?? realFS,
+ settings,
+ inlineConfig: options.inlineConfig ?? {},
+ logging: defaultLogging,
+ });
+ try {
+ await callback(container);
+ } finally {
+ await container.close();
+ }
+}
diff --git a/packages/astro/test/units/vite-plugin-astro-server/request.test.js b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
index fba0a49ab..48d449ccd 100644
--- a/packages/astro/test/units/vite-plugin-astro-server/request.test.js
+++ b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
@@ -1,6 +1,5 @@
import { expect } from 'chai';
-import { createDefaultDevSettings } from '../../../dist/core/config/index.js';
import { createLoader } from '../../../dist/core/module-loader/index.js';
import { createRouteManifest } from '../../../dist/core/routing/index.js';
import { createComponent, render } from '../../../dist/runtime/server/index.js';
@@ -8,6 +7,7 @@ import { createController, handleRequest } from '../../../dist/vite-plugin-astro
import {
createAstroModule,
createBasicEnvironment,
+ createBasicSettings,
createFs,
createRequestAndResponse,
defaultLogging,
@@ -15,7 +15,7 @@ import {
async function createDevEnvironment(overrides = {}) {
const env = createBasicEnvironment();
- env.settings = await createDefaultDevSettings({}, '/');
+ env.settings = await createBasicSettings({ root: '/' });
env.settings.renderers = [];
env.loader = createLoader();
Object.assign(env, overrides);
diff --git a/packages/integrations/mdx/test/mdx-get-static-paths.test.js b/packages/integrations/mdx/test/mdx-get-static-paths.test.js
index b4dc179d0..c5a34f7de 100644
--- a/packages/integrations/mdx/test/mdx-get-static-paths.test.js
+++ b/packages/integrations/mdx/test/mdx-get-static-paths.test.js
@@ -23,7 +23,7 @@ describe('getStaticPaths', () => {
const $ = cheerio.load(html);
expect($('p').text()).to.equal('First mdx file');
expect($('#one').text()).to.equal('hello', 'Frontmatter included');
- expect($('#url').text()).to.equal('/src/content/1.mdx', 'url is included');
+ expect($('#url').text()).to.equal('src/content/1.mdx', 'url is included');
expect($('#file').text()).to.contain(
'fixtures/mdx-get-static-paths/src/content/1.mdx',
'file is included'
diff --git a/packages/integrations/mdx/test/mdx-plugins.test.js b/packages/integrations/mdx/test/mdx-plugins.test.js
index 139d2042f..324e00c9c 100644
--- a/packages/integrations/mdx/test/mdx-plugins.test.js
+++ b/packages/integrations/mdx/test/mdx-plugins.test.js
@@ -96,7 +96,7 @@ describe('MDX plugins', () => {
it('ignores string-based plugins in markdown config', async () => {
const fixture = await buildFixture({
markdown: {
- remarkPlugins: [['remark-toc']],
+ remarkPlugins: [['remark-toc', {}]],
},
integrations: [mdx()],
});