Refactor and improve Astro config loading flow (#7879)

This commit is contained in:
Bjorn Lu 2023-08-01 17:11:26 +08:00 committed by GitHub
parent 9e22038472
commit ebf7ebbf7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 740 additions and 825 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Refactor and improve Astro config loading flow

View file

@ -1,5 +1,5 @@
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import { getErrorOverlayContent, silentLogging, testFactory } from './test-utils.js'; import { getErrorOverlayContent, testFactory } from './test-utils.js';
const test = testFactory({ const test = testFactory({
root: './fixtures/errors/', root: './fixtures/errors/',
@ -12,10 +12,7 @@ const test = testFactory({
let devServer; let devServer;
test.beforeAll(async ({ astro }) => { test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer({ devServer = await astro.startDevServer();
// Only test the error overlay, don't print to console
logging: silentLogging,
});
}); });
test.afterAll(async ({ astro }) => { test.afterAll(async ({ astro }) => {

View file

@ -1,6 +1,7 @@
import { expect, test as testBase } from '@playwright/test'; import { expect, test as testBase } from '@playwright/test';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadFixture as baseLoadFixture } from '../test/test-utils.js'; import { loadFixture as baseLoadFixture } from '../test/test-utils.js';
export const isWindows = process.platform === 'win32'; 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` // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
return baseLoadFixture({ return baseLoadFixture({
...inlineConfig, ...inlineConfig,
root: new URL(inlineConfig.root, import.meta.url).toString(), root: fileURLToPath(new URL(inlineConfig.root, import.meta.url)),
server: { server: {
port: testFileToPort.get(path.basename(inlineConfig.root)), port: testFileToPort.get(path.basename(inlineConfig.root)),
}, },

View file

@ -19,7 +19,7 @@ import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config'; import type { AstroConfigSchema } from '../core/config';
import type { AstroTimer } from '../core/config/timer'; import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies'; 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 { AstroComponentFactory, AstroComponentInstance } from '../runtime/server';
import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js'; import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
export type { export type {
@ -1331,6 +1331,16 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
// TypeScript still confirms zod validation matches this type. // TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[]; 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 = { export type ContentEntryModule = {
id: string; id: string;

View file

@ -9,7 +9,7 @@ import ora from 'ora';
import preferredPM from 'preferred-pm'; import preferredPM from 'preferred-pm';
import prompts from 'prompts'; import prompts from 'prompts';
import type yargs from 'yargs-parser'; import type yargs from 'yargs-parser';
import { loadTSConfig, resolveConfigPath } from '../../core/config/index.js'; import { loadTSConfig, resolveConfigPath, resolveRoot } from '../../core/config/index.js';
import { import {
defaultTSConfig, defaultTSConfig,
presets, presets,
@ -23,12 +23,12 @@ import { appendForwardSlash } from '../../core/path.js';
import { apply as applyPolyfill } from '../../core/polyfill.js'; import { apply as applyPolyfill } from '../../core/polyfill.js';
import { parseNpmName } from '../../core/util.js'; import { parseNpmName } from '../../core/util.js';
import { eventCliSession, telemetry } from '../../events/index.js'; import { eventCliSession, telemetry } from '../../events/index.js';
import { createLoggingFromFlags } from '../flags.js';
import { generate, parse, t, visit } from './babel.js'; import { generate, parse, t, visit } from './babel.js';
import { ensureImport } from './imports.js'; import { ensureImport } from './imports.js';
import { wrapDefaultExport } from './wrapper.js'; import { wrapDefaultExport } from './wrapper.js';
interface AddOptions { interface AddOptions {
logging: LogOptions;
flags: yargs.Arguments; 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')); telemetry.record(eventCliSession('add'));
applyPolyfill(); applyPolyfill();
if (flags.help || names.length === 0) { 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. // Some packages might have a common alias! We normalize those here.
const cwd = flags.root; const cwd = flags.root;
const logging = createLoggingFromFlags(flags);
const integrationNames = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name)); const integrationNames = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name));
const integrations = await validateIntegrations(integrationNames); const integrations = await validateIntegrations(integrationNames);
let installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logging }); 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 // Append forward slash to compute relative paths
root.href = appendForwardSlash(root.href); 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; let configURL = rawConfigPath ? pathToFileURL(rawConfigPath) : undefined;
if (configURL) { if (configURL) {

View file

@ -1,21 +1,31 @@
import type yargs from 'yargs-parser'; import type yargs from 'yargs-parser';
import _build from '../../core/build/index.js'; import _build from '../../core/build/index.js';
import type { LogOptions } from '../../core/logger/core.js'; import { printHelp } from '../../core/messages.js';
import { loadSettings } from '../load-settings.js'; import { flagsToAstroInlineConfig } from '../flags.js';
interface BuildOptions { interface BuildOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
logging: LogOptions;
} }
export async function build({ flags, logging }: BuildOptions) { export async function build({ flags }: BuildOptions) {
const settings = await loadSettings({ cmd: 'build', flags, logging }); if (flags?.help || flags?.h) {
if (!settings) return; 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, { const inlineConfig = flagsToAstroInlineConfig(flags);
flags,
logging, await _build(inlineConfig, {
teardownCompiler: true, teardownCompiler: true,
mode: flags.mode,
}); });
} }

View file

@ -13,11 +13,16 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
import ora from 'ora'; import ora from 'ora';
import type { Arguments as Flags } from 'yargs-parser'; import type { Arguments as Flags } from 'yargs-parser';
import type { AstroSettings } from '../../@types/astro'; 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 type { LogOptions } from '../../core/logger/core.js';
import { debug, info } from '../../core/logger/core.js'; import { debug, info } from '../../core/logger/core.js';
import { printHelp } from '../../core/messages.js'; import { printHelp } from '../../core/messages.js';
import type { ProcessExit, SyncOptions } from '../../core/sync'; import type { syncInternal } from '../../core/sync';
import { loadSettings } from '../load-settings.js'; import { eventCliSession, telemetry } from '../../events/index.js';
import { runHookConfigSetup } from '../../integrations/index.js';
import { flagsToAstroInlineConfig } from '../flags.js';
import { printDiagnostic } from './print.js'; import { printDiagnostic } from './print.js';
type DiagnosticResult = { type DiagnosticResult = {
@ -31,11 +36,6 @@ export type CheckPayload = {
* Flags passed via CLI * Flags passed via CLI
*/ */
flags: Flags; flags: Flags;
/**
* Logging options
*/
logging: LogOptions;
}; };
type CheckFlags = { type CheckFlags = {
@ -77,9 +77,8 @@ const ASTRO_GLOB_PATTERN = '**/*.astro';
* *
* @param {CheckPayload} options Options passed {@link AstroChecker} * @param {CheckPayload} options Options passed {@link AstroChecker}
* @param {Flags} options.flags Flags coming from the CLI * @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) { if (flags.help || flags.h) {
printHelp({ printHelp({
commandName: 'astro check', commandName: 'astro check',
@ -95,8 +94,12 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
return; return;
} }
const settings = await loadSettings({ cmd: 'check', flags, logging }); // Load settings
if (!settings) return; 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); const checkFlags = parseFlags(flags);
if (checkFlags.watch) { if (checkFlags.watch) {
@ -105,7 +108,7 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
info(logging, 'check', 'Checking files'); 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 root = settings.config.root;
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const diagnosticChecker = new AstroCheck( const diagnosticChecker = new AstroCheck(
@ -116,7 +119,7 @@ export async function check({ logging, flags }: CheckPayload): Promise<AstroChec
); );
return new AstroChecker({ return new AstroChecker({
syncCli, syncInternal,
settings, settings,
fileSystem: fs, fileSystem: fs,
logging, logging,
@ -130,7 +133,7 @@ type CheckerConstructor = {
isWatchMode: boolean; isWatchMode: boolean;
syncCli: (settings: AstroSettings, options: SyncOptions) => Promise<ProcessExit>; syncInternal: typeof syncInternal;
settings: Readonly<AstroSettings>; settings: Readonly<AstroSettings>;
@ -148,7 +151,7 @@ type CheckerConstructor = {
export class AstroChecker { export class AstroChecker {
readonly #diagnosticsChecker: AstroCheck; readonly #diagnosticsChecker: AstroCheck;
readonly #shouldWatch: boolean; readonly #shouldWatch: boolean;
readonly #syncCli: (settings: AstroSettings, opts: SyncOptions) => Promise<ProcessExit>; readonly #syncInternal: CheckerConstructor['syncInternal'];
readonly #settings: AstroSettings; readonly #settings: AstroSettings;
@ -162,14 +165,14 @@ export class AstroChecker {
constructor({ constructor({
diagnosticChecker, diagnosticChecker,
isWatchMode, isWatchMode,
syncCli, syncInternal,
settings, settings,
fileSystem, fileSystem,
logging, logging,
}: CheckerConstructor) { }: CheckerConstructor) {
this.#diagnosticsChecker = diagnosticChecker; this.#diagnosticsChecker = diagnosticChecker;
this.#shouldWatch = isWatchMode; this.#shouldWatch = isWatchMode;
this.#syncCli = syncCli; this.#syncInternal = syncInternal;
this.#logging = logging; this.#logging = logging;
this.#settings = settings; this.#settings = settings;
this.#fs = fileSystem; this.#fs = fileSystem;
@ -223,7 +226,14 @@ export class AstroChecker {
* @param openDocuments Whether the operation should open all `.astro` files * @param openDocuments Whether the operation should open all `.astro` files
*/ */
async #checkAllFiles(openDocuments: boolean): Promise<CheckResult> { 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, logging: this.#logging,
fs: this.#fs, fs: this.#fs,
}); });

View file

@ -1,31 +1,35 @@
import fs from 'node:fs'; import { cyan } from 'kleur/colors';
import type yargs from 'yargs-parser'; import type yargs from 'yargs-parser';
import { resolveConfigPath, resolveFlags } from '../../core/config/index.js';
import devServer from '../../core/dev/index.js'; import devServer from '../../core/dev/index.js';
import { info, type LogOptions } from '../../core/logger/core.js'; import { printHelp } from '../../core/messages.js';
import { handleConfigError, loadSettings } from '../load-settings.js'; import { flagsToAstroInlineConfig } from '../flags.js';
interface DevOptions { interface DevOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
logging: LogOptions;
} }
export async function dev({ flags, logging }: DevOptions) { export async function dev({ flags }: DevOptions) {
const settings = await loadSettings({ cmd: 'dev', flags, logging }); if (flags.help || flags.h) {
if (!settings) return; 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 inlineConfig = flagsToAstroInlineConfig(flags);
const configFlag = resolveFlags(flags).config;
const configFlagPath = configFlag ? await resolveConfigPath({ cwd: root, flags, fs }) : undefined;
return await devServer(settings, { return await devServer(inlineConfig);
configFlag,
configFlagPath,
flags,
logging,
handleConfigError(e) {
handleConfigError(e, { cmd: 'dev', cwd: root, flags, logging });
info(logging, 'astro', 'Continuing with previous valid configuration\n');
},
});
} }

View file

@ -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;
}

View file

@ -2,7 +2,6 @@
import * as colors from 'kleur/colors'; import * as colors from 'kleur/colors';
import yargs from 'yargs-parser'; import yargs from 'yargs-parser';
import { ASTRO_VERSION } from '../core/constants.js'; import { ASTRO_VERSION } from '../core/constants.js';
import type { LogOptions } from '../core/logger/core.js';
type CLICommand = type CLICommand =
| 'help' | 'help'
@ -112,16 +111,10 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
} }
} }
const { enableVerboseLogging, nodeLogDestination } = await import('../core/logger/node.js'); // In verbose/debug mode, we log the debug logs asap before any potential errors could appear
const logging: LogOptions = {
dest: nodeLogDestination,
level: 'info',
};
if (flags.verbose) { if (flags.verbose) {
logging.level = 'debug'; const { enableVerboseLogging } = await import('../core/logger/node.js');
enableVerboseLogging(); 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 // 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': { case 'add': {
const { add } = await import('./add/index.js'); const { add } = await import('./add/index.js');
const packages = flags._.slice(3) as string[]; const packages = flags._.slice(3) as string[];
await add(packages, { flags, logging }); await add(packages, { flags });
return; return;
} }
case 'dev': { case 'dev': {
const { dev } = await import('./dev/index.js'); const { dev } = await import('./dev/index.js');
const server = await dev({ flags, logging }); const server = await dev({ flags });
if (server) { if (server) {
return await new Promise(() => {}); // lives forever return await new Promise(() => {}); // lives forever
} }
@ -148,12 +141,12 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
} }
case 'build': { case 'build': {
const { build } = await import('./build/index.js'); const { build } = await import('./build/index.js');
await build({ flags, logging }); await build({ flags });
return; return;
} }
case 'preview': { case 'preview': {
const { preview } = await import('./preview/index.js'); const { preview } = await import('./preview/index.js');
const server = await preview({ flags, logging }); const server = await preview({ flags });
if (server) { if (server) {
return await server.closed(); // keep alive until the server is closed 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': { case 'check': {
const { check } = await import('./check/index.js'); const { check } = await import('./check/index.js');
// We create a server to start doing our operations // We create a server to start doing our operations
const checkServer = await check({ flags, logging }); const checkServer = await check({ flags });
if (checkServer) { if (checkServer) {
if (checkServer.isWatchMode) { if (checkServer.isWatchMode) {
await checkServer.watch(); await checkServer.watch();
@ -176,7 +169,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
} }
case 'sync': { case 'sync': {
const { sync } = await import('./sync/index.js'); const { sync } = await import('./sync/index.js');
const exitCode = await sync({ flags, logging }); const exitCode = await sync({ flags });
return process.exit(exitCode); return process.exit(exitCode);
} }
} }

View file

@ -3,14 +3,16 @@ import * as colors from 'kleur/colors';
import { arch, platform } from 'node:os'; import { arch, platform } from 'node:os';
import whichPm from 'which-pm'; import whichPm from 'which-pm';
import type yargs from 'yargs-parser'; 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 { ASTRO_VERSION } from '../../core/constants.js';
import { flagsToAstroInlineConfig } from '../flags.js';
interface InfoOptions { interface InfoOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
} }
export async function printInfo({ flags }: InfoOptions) { export async function printInfo({ flags }: InfoOptions) {
const inlineConfig = flagsToAstroInlineConfig(flags);
const packageManager = await whichPm(process.cwd()); const packageManager = await whichPm(process.cwd());
let adapter = "Couldn't determine."; let adapter = "Couldn't determine.";
let integrations = []; let integrations = [];
@ -22,11 +24,7 @@ export async function printInfo({ flags }: InfoOptions) {
} }
try { try {
const { userConfig } = await openConfig({ const { userConfig } = await resolveConfig(inlineConfig, 'info');
cwd: flags.root,
flags,
cmd: 'info',
});
if (userConfig.adapter?.name) { if (userConfig.adapter?.name) {
adapter = userConfig.adapter.name; adapter = userConfig.adapter.name;
} }

View file

@ -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');
}
}

View file

@ -1,16 +1,32 @@
import { cyan } from 'kleur/colors';
import type yargs from 'yargs-parser'; 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 previewServer from '../../core/preview/index.js';
import { loadSettings } from '../load-settings.js'; import { flagsToAstroInlineConfig } from '../flags.js';
interface PreviewOptions { interface PreviewOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
logging: LogOptions;
} }
export async function preview({ flags, logging }: PreviewOptions) { export async function preview({ flags }: PreviewOptions) {
const settings = await loadSettings({ cmd: 'preview', flags, logging }); if (flags?.help || flags?.h) {
if (!settings) return; 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);
} }

View file

@ -1,18 +1,27 @@
import fs from 'node:fs';
import type yargs from 'yargs-parser'; import type yargs from 'yargs-parser';
import type { LogOptions } from '../../core/logger/core.js'; import { printHelp } from '../../core/messages.js';
import { syncCli } from '../../core/sync/index.js'; import { sync as _sync } from '../../core/sync/index.js';
import { loadSettings } from '../load-settings.js'; import { flagsToAstroInlineConfig } from '../flags.js';
interface SyncOptions { interface SyncOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
logging: LogOptions;
} }
export async function sync({ flags, logging }: SyncOptions) { export async function sync({ flags }: SyncOptions) {
const settings = await loadSettings({ cmd: 'sync', flags, logging }); if (flags?.help || flags?.h) {
if (!settings) return; 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; return exitCode;
} }

View file

@ -1,5 +1,6 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { collectErrorMetadata } from '../core/errors/dev/index.js'; import { collectErrorMetadata } from '../core/errors/dev/index.js';
import { isAstroConfigZodError } from '../core/errors/errors.js';
import { createSafeError } from '../core/errors/index.js'; import { createSafeError } from '../core/errors/index.js';
import { debug } from '../core/logger/core.js'; import { debug } from '../core/logger/core.js';
import { formatErrorMessage } from '../core/messages.js'; import { formatErrorMessage } from '../core/messages.js';
@ -7,6 +8,9 @@ import { eventError, telemetry } from '../events/index.js';
/** Display error and exit */ /** Display error and exit */
export async function throwAndExit(cmd: string, err: unknown) { 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 telemetryPromise: Promise<any>;
let errorMessage: string; let errorMessage: string;
function exitWithErrorMessage() { function exitWithErrorMessage() {

View file

@ -17,7 +17,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
fs, fs,
{ mergeConfig }, { mergeConfig },
{ nodeLogDestination }, { nodeLogDestination },
{ openConfig, createSettings }, { resolveConfig, createSettings },
{ createVite }, { createVite },
{ runHookConfigSetup, runHookConfigDone }, { runHookConfigSetup, runHookConfigDone },
{ astroContentListenPlugin }, { astroContentListenPlugin },
@ -34,7 +34,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
dest: nodeLogDestination, dest: nodeLogDestination,
level: 'info', level: 'info',
}; };
const { astroConfig: config } = await openConfig({ cmd }); const { astroConfig: config } = await resolveConfig({}, cmd);
const settings = createSettings(config, inlineConfig.root); const settings = createSettings(config, inlineConfig.root);
await runHookConfigSetup({ settings, command: cmd, logging }); await runHookConfigSetup({ settings, command: cmd, logging });
const viteConfig = await createVite( const viteConfig = await createVite(

View file

@ -1,10 +1,18 @@
import * as colors from 'kleur/colors'; import * as colors from 'kleur/colors';
import fs from 'node:fs'; import fs from 'node:fs';
import { performance } from 'node:perf_hooks'; import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
import type * as vite from 'vite'; import type * as vite from 'vite';
import type yargs from 'yargs-parser'; import type {
import type { AstroConfig, AstroSettings, ManifestData, RuntimeMode } from '../../@types/astro'; AstroConfig,
AstroInlineConfig,
AstroSettings,
ManifestData,
RuntimeMode,
} from '../../@types/astro';
import { injectImageEndpoint } from '../../assets/internal.js'; import { injectImageEndpoint } from '../../assets/internal.js';
import { telemetry } from '../../events/index.js';
import { eventCliSession } from '../../events/session.js';
import { import {
runHookBuildDone, runHookBuildDone,
runHookBuildStart, runHookBuildStart,
@ -12,9 +20,11 @@ import {
runHookConfigSetup, runHookConfigSetup,
} from '../../integrations/index.js'; } from '../../integrations/index.js';
import { isServerLikeOutput } from '../../prerender/utils.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 { createVite } from '../create-vite.js';
import { debug, info, levels, timerMessage, warn, type LogOptions } from '../logger/core.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 { apply as applyPolyfill } from '../polyfill.js';
import { RouteCache } from '../render/route-cache.js'; import { RouteCache } from '../render/route-cache.js';
import { createRouteManifest } from '../routing/index.js'; import { createRouteManifest } from '../routing/index.js';
@ -24,38 +34,38 @@ import type { StaticBuildOptions } from './types.js';
import { getTimeStat } from './util.js'; import { getTimeStat } from './util.js';
export interface BuildOptions { export interface BuildOptions {
mode?: RuntimeMode;
logging: LogOptions;
/** /**
* Teardown the compiler WASM instance after build. This can improve performance when * 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. * building once, but may cause a performance hit if building multiple times in a row.
*/ */
teardownCompiler?: boolean; teardownCompiler?: boolean;
flags?: yargs.Arguments;
} }
/** `astro build` */ /** `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(); applyPolyfill();
if (options.flags?.help || options.flags?.h) { const logging = createNodeLogging(inlineConfig);
printHelp({ const { userConfig, astroConfig } = await resolveConfig(inlineConfig, 'build');
commandName: 'astro build', telemetry.record(eventCliSession('build', userConfig));
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 builder = new AstroBuilder(settings, options); const settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
const builder = new AstroBuilder(settings, {
...options,
logging,
mode: inlineConfig.mode,
});
await builder.run(); await builder.run();
} }
interface AstroBuilderOptions extends BuildOptions {
logging: LogOptions;
mode?: RuntimeMode;
}
class AstroBuilder { class AstroBuilder {
private settings: AstroSettings; private settings: AstroSettings;
private logging: LogOptions; private logging: LogOptions;
@ -66,7 +76,7 @@ class AstroBuilder {
private timer: Record<string, number>; private timer: Record<string, number>;
private teardownCompiler: boolean; private teardownCompiler: boolean;
constructor(settings: AstroSettings, options: BuildOptions) { constructor(settings: AstroSettings, options: AstroBuilderOptions) {
if (options.mode) { if (options.mode) {
this.mode = options.mode; this.mode = options.mode;
} }
@ -112,8 +122,8 @@ class AstroBuilder {
); );
await runHookConfigDone({ settings: this.settings, logging }); await runHookConfigDone({ settings: this.settings, logging });
const { sync } = await import('../sync/index.js'); const { syncInternal } = await import('../sync/index.js');
const syncRet = await sync(this.settings, { logging, fs }); const syncRet = await syncInternal(this.settings, { logging, fs });
if (syncRet !== 0) { if (syncRet !== 0) {
return process.exit(syncRet); return process.exit(syncRet);
} }

View file

@ -1,16 +1,26 @@
import type { Arguments as Flags } from 'yargs-parser'; 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 * as colors from 'kleur/colors';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; 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 { AstroError, AstroErrorData } from '../errors/index.js';
import { formatConfigErrorMessage } from '../messages.js';
import { mergeConfig } from './merge.js'; import { mergeConfig } from './merge.js';
import { createRelativeSchema } from './schema.js'; import { createRelativeSchema } from './schema.js';
import { loadConfigWithVite } from './vite-load.js'; import { loadConfigWithVite } from './vite-load.js';
export const LEGACY_ASTRO_CONFIG_KEYS = new Set([ const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'projectRoot', 'projectRoot',
'src', 'src',
'pages', 'pages',
@ -80,13 +90,29 @@ export async function validateConfig(
const AstroConfigRelativeSchema = createRelativeSchema(cmd, root); const AstroConfigRelativeSchema = createRelativeSchema(cmd, root);
// First-Pass Validation // 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. // If successful, return the result as a verified AstroConfig object.
return result; return result;
} }
/** Convert the generic "yargs" flag object into our own, custom TypeScript object. */ /** 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 { export function resolveFlags(flags: Partial<Flags>): CLIFlags {
return { return {
root: typeof flags.root === 'string' ? flags.root : undefined, 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(); 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) { async function search(fsMod: typeof fs, root: string) {
const paths = [ const paths = [
'astro.config.mjs', '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 { interface ResolveConfigPathOptions {
cwd?: string; root: string;
flags?: Flags; configFile?: string;
fs: typeof fs; fs: typeof fs;
} }
@ -163,87 +163,85 @@ interface ResolveConfigPathOptions {
* Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file * Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file
*/ */
export async function resolveConfigPath( export async function resolveConfigPath(
configOptions: ResolveConfigPathOptions options: ResolveConfigPathOptions
): Promise<string | undefined> { ): Promise<string | undefined> {
const root = resolveRoot(configOptions.cwd);
const flags = resolveFlags(configOptions.flags || {});
let userConfigPath: string | undefined; let userConfigPath: string | undefined;
if (flags?.config) { if (options.configFile) {
userConfigPath = /^\.*\//.test(flags.config) ? flags.config : `./${flags.config}`; userConfigPath = path.join(options.root, options.configFile);
userConfigPath = fileURLToPath(new URL(userConfigPath, `file://${root}/`)); if (!options.fs.existsSync(userConfigPath)) {
if (!configOptions.fs.existsSync(userConfigPath)) {
throw new AstroError({ throw new AstroError({
...AstroErrorData.ConfigNotFound, ...AstroErrorData.ConfigNotFound,
message: AstroErrorData.ConfigNotFound.message(flags.config), message: AstroErrorData.ConfigNotFound.message(options.configFile),
}); });
} }
} else { } else {
userConfigPath = await search(configOptions.fs, root); userConfigPath = await search(options.fs, options.root);
} }
return userConfigPath; 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( async function loadConfig(
configOptions: LoadConfigOptions, root: string,
root: string configFile?: string | false,
fsMod = fs
): Promise<Record<string, any>> { ): Promise<Record<string, any>> {
const fsMod = configOptions.fsMod ?? fs; if (configFile === false) return {};
const configPath = await resolveConfigPath({ const configPath = await resolveConfigPath({
cwd: configOptions.cwd, root,
flags: configOptions.flags, configFile,
fs: fsMod, fs: fsMod,
}); });
if (!configPath) return {}; if (!configPath) return {};
// Create a vite server to load the config // Create a vite server to load the config
return await loadConfigWithVite({ try {
configPath, return await loadConfigWithVite({
fs: fsMod, root,
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;
}
}
function splitInlineConfig(inlineConfig: AstroInlineConfig): {
inlineUserConfig: AstroUserConfig;
inlineOnlyConfig: AstroInlineOnlyConfig;
} {
const { configFile, mode, logLevel, ...inlineUserConfig } = inlineConfig;
return {
inlineUserConfig,
inlineOnlyConfig: {
configFile,
mode,
logLevel,
},
};
}
interface ResolveConfigResult {
userConfig: AstroUserConfig;
astroConfig: AstroConfig;
} }
/** Attempt to resolve an Astro configuration object. Normalize, validate, and return. */
export async function resolveConfig( export async function resolveConfig(
userConfig: AstroUserConfig, inlineConfig: AstroInlineConfig,
root: string, command: string,
flags: CLIFlags = {}, fsMod = fs
cmd: string ): Promise<ResolveConfigResult> {
): Promise<AstroConfig> { const root = resolveRoot(inlineConfig.root);
const mergedConfig = mergeCLIFlags(userConfig, flags); const { inlineUserConfig, inlineOnlyConfig } = splitInlineConfig(inlineConfig);
const validatedConfig = await validateConfig(mergedConfig, root, cmd);
return validatedConfig; const userConfig = await loadConfig(root, inlineOnlyConfig.configFile, fsMod);
} const mergedConfig = mergeConfig(userConfig, inlineUserConfig);
const astroConfig = await validateConfig(mergedConfig, root, command);
export function createDefaultDevConfig( return { userConfig, astroConfig };
userConfig: AstroUserConfig = {},
root: string = process.cwd()
) {
return resolveConfig(userConfig, root, undefined, 'dev');
} }

View file

@ -1,12 +1,6 @@
export { export { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from './config.js';
createDefaultDevConfig, export { createNodeLogging } from './logging.js';
openConfig,
resolveConfigPath,
resolveFlags,
resolveRoot,
validateConfig,
} from './config.js';
export { mergeConfig } from './merge.js'; export { mergeConfig } from './merge.js';
export type { AstroConfigSchema } from './schema'; export type { AstroConfigSchema } from './schema';
export { createDefaultDevSettings, createSettings } from './settings.js'; export { createSettings } from './settings.js';
export { loadTSConfig, updateTSConfigForFramework } from './tsconfig.js'; export { loadTSConfig, updateTSConfigForFramework } from './tsconfig.js';

View file

@ -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',
};
}

View file

@ -1,7 +1,7 @@
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; 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 { getContentPaths } from '../../content/index.js';
import jsxRenderer from '../../jsx/renderer.js'; import jsxRenderer from '../../jsx/renderer.js';
import { markdownContentEntryType } from '../../vite-plugin-markdown/content-entry-type.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 { AstroError, AstroErrorData } from '../errors/index.js';
import { formatYAMLException, isYAMLException } from '../errors/utils.js'; import { formatYAMLException, isYAMLException } from '../errors/utils.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../constants.js'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../constants.js';
import { createDefaultDevConfig } from './config.js';
import { AstroTimer } from './timer.js'; import { AstroTimer } from './timer.js';
import { loadTSConfig } from './tsconfig.js'; import { loadTSConfig } from './tsconfig.js';
@ -119,14 +118,3 @@ export function createSettings(config: AstroConfig, cwd?: string): AstroSettings
settings.watchFiles = watchFiles; settings.watchFiles = watchFiles;
return settings; 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);
}

View file

@ -1,6 +1,6 @@
import type * as http from 'node:http'; import type * as http from 'node:http';
import type { AddressInfo } from 'node:net'; 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 nodeFs from 'node:fs';
import * as vite from 'vite'; import * as vite from 'vite';
@ -11,52 +11,36 @@ import {
runHookServerDone, runHookServerDone,
runHookServerStart, runHookServerStart,
} from '../../integrations/index.js'; } from '../../integrations/index.js';
import { createDefaultDevSettings, resolveRoot } from '../config/index.js';
import { createVite } from '../create-vite.js'; import { createVite } from '../create-vite.js';
import type { LogOptions } from '../logger/core.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'; import { apply as applyPolyfill } from '../polyfill.js';
const defaultLogging: LogOptions = {
dest: nodeLogDestination,
level: 'error',
};
export interface Container { export interface Container {
fs: typeof nodeFs; fs: typeof nodeFs;
logging: LogOptions; logging: LogOptions;
settings: AstroSettings; settings: AstroSettings;
viteConfig: vite.InlineConfig;
viteServer: vite.ViteDevServer; viteServer: vite.ViteDevServer;
resolvedRoot: string; inlineConfig: AstroInlineConfig;
configFlag: string | undefined;
configFlagPath: string | undefined;
restartInFlight: boolean; // gross restartInFlight: boolean; // gross
handle: (req: http.IncomingMessage, res: http.ServerResponse) => void; handle: (req: http.IncomingMessage, res: http.ServerResponse) => void;
close: () => Promise<void>; close: () => Promise<void>;
} }
export interface CreateContainerParams { export interface CreateContainerParams {
logging: LogOptions;
settings: AstroSettings;
inlineConfig?: AstroInlineConfig;
isRestart?: boolean; isRestart?: boolean;
logging?: LogOptions;
userConfig?: AstroUserConfig;
settings?: AstroSettings;
fs?: typeof nodeFs; 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> { export async function createContainer({
let { isRestart = false,
isRestart = false, logging,
logging = defaultLogging, inlineConfig,
settings = await createDefaultDevSettings(params.userConfig, params.root), settings,
fs = nodeFs, fs = nodeFs,
} = params; }: CreateContainerParams): Promise<Container> {
// Initialize // Initialize
applyPolyfill(); applyPolyfill();
settings = await runHookConfigSetup({ settings = await runHookConfigSetup({
@ -94,14 +78,11 @@ export async function createContainer(params: CreateContainerParams = {}): Promi
const viteServer = await vite.createServer(viteConfig); const viteServer = await vite.createServer(viteConfig);
const container: Container = { const container: Container = {
configFlag: params.configFlag, inlineConfig: inlineConfig ?? {},
configFlagPath: params.configFlagPath,
fs, fs,
logging, logging,
resolvedRoot: appendForwardSlash(resolveRoot(params.root)),
restartInFlight: false, restartInFlight: false,
settings, settings,
viteConfig,
viteServer, viteServer,
handle(req, res) { handle(req, res) {
viteServer.middlewares.handle(req, res, Function.prototype); viteServer.middlewares.handle(req, res, Function.prototype);
@ -143,18 +124,3 @@ export async function startContainer({
export function isStarted(container: Container): boolean { export function isStarted(container: Container): boolean {
return !!container.viteServer.httpServer?.listening; 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();
}
}

View file

@ -1,27 +1,16 @@
import { cyan } from 'kleur/colors'; import fs from 'node:fs';
import type http from 'node:http'; import type http from 'node:http';
import type { AddressInfo } from 'node:net'; import type { AddressInfo } from 'node:net';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import type * as vite from 'vite'; import type * as vite from 'vite';
import type yargs from 'yargs-parser'; import type { AstroInlineConfig } from '../../@types/astro';
import type { AstroSettings } from '../../@types/astro';
import { attachContentServerListeners } from '../../content/index.js'; import { attachContentServerListeners } from '../../content/index.js';
import { telemetry } from '../../events/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 * as msg from '../messages.js';
import { printHelp } from '../messages.js';
import { startContainer } from './container.js'; import { startContainer } from './container.js';
import { createContainerWithAutomaticRestart } from './restart.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 { export interface DevServer {
address: AddressInfo; address: AddressInfo;
handle: (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) => void; handle: (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) => void;
@ -30,68 +19,34 @@ export interface DevServer {
} }
/** `astro dev` */ /** `astro dev` */
export default async function dev( export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevServer> {
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;
}
const devStart = performance.now(); const devStart = performance.now();
await telemetry.record([]); await telemetry.record([]);
// Create a container which sets up the Vite server. // Create a container which sets up the Vite server.
const restart = await createContainerWithAutomaticRestart({ const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
flags: options.flags ?? {}, const logging = restart.container.logging;
handleConfigError: options.handleConfigError,
// eslint-disable-next-line no-console
beforeRestart: () => console.clear(),
params: {
settings,
root: options.flags?.root,
logging: options.logging,
isRestart: options.isRestart,
},
});
// Start listening to the port // Start listening to the port
const devServerAddressInfo = await startContainer(restart.container); const devServerAddressInfo = await startContainer(restart.container);
info( info(
options.logging, logging,
null, null,
msg.serverStart({ msg.serverStart({
startupTime: performance.now() - devStart, startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] }, resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
host: settings.config.server.host, host: restart.container.settings.config.server.host,
base: settings.config.base, base: restart.container.settings.config.base,
isRestart: options.isRestart,
}) })
); );
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0'; const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
if (currentVersion.includes('-')) { if (currentVersion.includes('-')) {
warn(options.logging, null, msg.prerelease({ currentVersion })); warn(logging, null, msg.prerelease({ currentVersion }));
} }
if (restart.container.viteConfig.server?.fs?.strict === false) { if (restart.container.viteServer.config.server?.fs?.strict === false) {
warn(options.logging, null, msg.fsStrictWarning()); warn(logging, null, msg.fsStrictWarning());
} }
await attachContentServerListeners(restart.container); await attachContentServerListeners(restart.container);

View file

@ -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 { default } from './dev.js';
export { createContainerWithAutomaticRestart } from './restart.js'; export { createContainerWithAutomaticRestart } from './restart.js';

View file

@ -1,9 +1,15 @@
import nodeFs from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as vite from 'vite'; import * as vite from 'vite';
import type { AstroSettings } from '../../@types/astro'; import type { AstroInlineConfig, AstroSettings } from '../../@types/astro';
import { createSettings, openConfig } from '../config/index.js'; 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 { createSafeError } from '../errors/index.js';
import { info } from '../logger/core.js'; import { info, error as logError } from '../logger/core.js';
import type { Container, CreateContainerParams } from './container'; import { formatErrorMessage } from '../messages.js';
import type { Container } from './container';
import { createContainer, isStarted, startContainer } from './container.js'; import { createContainer, isStarted, startContainer } from './container.js';
async function createRestartedContainer( async function createRestartedContainer(
@ -11,15 +17,13 @@ async function createRestartedContainer(
settings: AstroSettings, settings: AstroSettings,
needsStart: boolean needsStart: boolean
): Promise<Container> { ): Promise<Container> {
const { logging, fs, resolvedRoot, configFlag, configFlagPath } = container; const { logging, fs, inlineConfig } = container;
const newContainer = await createContainer({ const newContainer = await createContainer({
isRestart: true, isRestart: true,
logging, logging,
settings, settings,
inlineConfig,
fs, fs,
root: resolvedRoot,
configFlag,
configFlagPath,
}); });
if (needsStart) { if (needsStart) {
@ -30,7 +34,7 @@ async function createRestartedContainer(
} }
export function shouldRestartContainer( export function shouldRestartContainer(
{ settings, configFlag, configFlagPath, restartInFlight }: Container, { settings, inlineConfig, restartInFlight }: Container,
changedFile: string changedFile: string
): boolean { ): boolean {
if (restartInFlight) return false; if (restartInFlight) return false;
@ -38,10 +42,8 @@ export function shouldRestartContainer(
let shouldRestart = false; let shouldRestart = false;
// If the config file changed, reload the config and restart the server. // If the config file changed, reload the config and restart the server.
if (configFlag) { if (inlineConfig.configFile) {
if (!!configFlagPath) { shouldRestart = vite.normalizePath(inlineConfig.configFile) === vite.normalizePath(changedFile);
shouldRestart = vite.normalizePath(configFlagPath) === vite.normalizePath(changedFile);
}
} }
// Otherwise, watch for any astro.config.* file changes in project root // Otherwise, watch for any astro.config.* file changes in project root
else { else {
@ -60,39 +62,16 @@ export function shouldRestartContainer(
return shouldRestart; return shouldRestart;
} }
interface RestartContainerParams { export async function restartContainer(
container: Container; container: Container
flags: any; ): Promise<{ container: Container; error: Error | null }> {
logMsg: string; const { logging, close, settings: existingSettings } = container;
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;
container.restartInFlight = true; container.restartInFlight = true;
if (beforeRestart) {
beforeRestart();
}
const needsStart = isStarted(container); const needsStart = isStarted(container);
try { try {
const newConfig = await openConfig({ const { astroConfig } = await resolveConfig(container.inlineConfig, 'dev', container.fs);
cwd: resolvedRoot, const settings = createSettings(astroConfig, fileURLToPath(existingSettings.config.root));
flags,
cmd: 'dev',
isRestart: true,
fsMod: container.fs,
});
info(logging, 'astro', logMsg + '\n');
let astroConfig = newConfig.astroConfig;
const settings = createSettings(astroConfig, resolvedRoot);
await close(); await close();
return { return {
container: await createRestartedContainer(container, settings, needsStart), container: await createRestartedContainer(container, settings, needsStart),
@ -100,7 +79,18 @@ export async function restartContainer({
}; };
} catch (_err) { } catch (_err) {
const error = createSafeError(_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(); await close();
info(logging, 'astro', 'Continuing with previous valid configuration\n'); info(logging, 'astro', 'Continuing with previous valid configuration\n');
return { return {
@ -111,10 +101,8 @@ export async function restartContainer({
} }
export interface CreateContainerWithAutomaticRestart { export interface CreateContainerWithAutomaticRestart {
flags: any; inlineConfig?: AstroInlineConfig;
params: CreateContainerParams; fs: typeof nodeFs;
handleConfigError?: (error: Error) => void | Promise<void>;
beforeRestart?: () => void;
} }
interface Restart { interface Restart {
@ -123,12 +111,17 @@ interface Restart {
} }
export async function createContainerWithAutomaticRestart({ export async function createContainerWithAutomaticRestart({
flags, inlineConfig,
handleConfigError = () => {}, fs,
beforeRestart,
params,
}: CreateContainerWithAutomaticRestart): Promise<Restart> { }: 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 resolveRestart: (value: Error | null) => void;
let restartComplete = new Promise<Error | null>((resolve) => { let restartComplete = new Promise<Error | null>((resolve) => {
resolveRestart = resolve; resolveRestart = resolve;
@ -142,24 +135,9 @@ export async function createContainerWithAutomaticRestart({
}; };
async function handleServerRestart(logMsg: string) { async function handleServerRestart(logMsg: string) {
info(logging, 'astro', logMsg + '\n');
const container = restart.container; const container = restart.container;
const { container: newContainer, error } = await restartContainer({ const { container: newContainer, error } = await restartContainer(container);
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 || '',
},
});
},
});
restart.container = newContainer; restart.container = newContainer;
// Add new watches because this is a new container with a new Vite server // Add new watches because this is a new container with a new Vite server
addWatches(); addWatches();

View file

@ -1,3 +1,4 @@
import type { ZodError } from 'zod';
import { codeFrame } from './printer.js'; import { codeFrame } from './printer.js';
import { getErrorDataByTitle } from './utils.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 * Generic object representing an error with all possible data
* Compatible with both Astro's and Vite's errors * Compatible with both Astro's and Vite's errors

View file

@ -1,40 +1,24 @@
import { cyan } from 'kleur/colors'; import { createRequire } from 'node:module';
import { createRequire } from 'module'; import { fileURLToPath, pathToFileURL } from 'node:url';
import { pathToFileURL } from 'node:url'; import type { AstroInlineConfig, PreviewModule, PreviewServer } from '../../@types/astro';
import type { Arguments } from 'yargs-parser'; import { telemetry } from '../../events/index.js';
import type { AstroSettings, PreviewModule, PreviewServer } from '../../@types/astro'; import { eventCliSession } from '../../events/session.js';
import { runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js'; import { runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
import type { LogOptions } from '../logger/core'; import { resolveConfig } from '../config/config.js';
import { printHelp } from '../messages.js'; import { createNodeLogging } from '../config/logging.js';
import { createSettings } from '../config/settings.js';
import createStaticPreviewServer from './static-preview-server.js'; import createStaticPreviewServer from './static-preview-server.js';
import { getResolvedHostForHttpServer } from './util.js'; import { getResolvedHostForHttpServer } from './util.js';
interface PreviewOptions {
logging: LogOptions;
flags?: Arguments;
}
/** The primary dev action */ /** The primary dev action */
export default async function preview( export default async function preview(
_settings: AstroSettings, inlineConfig: AstroInlineConfig
{ logging, flags }: PreviewOptions
): Promise<PreviewServer | undefined> { ): Promise<PreviewServer | undefined> {
if (flags?.help || flags?.h) { const logging = createNodeLogging(inlineConfig);
printHelp({ const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'preview');
commandName: 'astro preview', telemetry.record(eventCliSession('preview', userConfig));
usage: '[...flags]',
tables: { const _settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
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 settings = await runHookConfigSetup({ const settings = await runHookConfigSetup({
settings: _settings, settings: _settings,

View file

@ -1,48 +1,54 @@
import { dim } from 'kleur/colors'; import { dim } from 'kleur/colors';
import type fsMod from 'node:fs'; import fsMod from 'node:fs';
import { performance } from 'node:perf_hooks'; import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
import { createServer, type HMRPayload } from 'vite'; import { createServer, type HMRPayload } from 'vite';
import type { Arguments } from 'yargs-parser'; import type { AstroInlineConfig, AstroSettings } from '../../@types/astro';
import type { AstroSettings } from '../../@types/astro';
import { createContentTypesGenerator } from '../../content/index.js'; import { createContentTypesGenerator } from '../../content/index.js';
import { globalContentConfigObserver } from '../../content/utils.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 { runHookConfigSetup } from '../../integrations/index.js';
import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js'; import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js';
import { getTimeStat } from '../build/util.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 { createVite } from '../create-vite.js';
import { AstroError, AstroErrorData, createSafeError, isAstroError } from '../errors/index.js'; import { AstroError, AstroErrorData, createSafeError, isAstroError } from '../errors/index.js';
import { info, type LogOptions } from '../logger/core.js'; import { info, type LogOptions } from '../logger/core.js';
import { printHelp } from '../messages.js';
export type ProcessExit = 0 | 1; export type ProcessExit = 0 | 1;
export type SyncOptions = { export type SyncOptions = {
logging: LogOptions; /**
fs: typeof fsMod; * Only used for testing
* @internal
*/
fs?: typeof fsMod;
}; };
export async function syncCli( export type SyncInternalOptions = SyncOptions & {
settings: AstroSettings, logging: LogOptions;
{ logging, fs, flags }: { logging: LogOptions; fs: typeof fsMod; flags?: Arguments } };
): 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 resolvedSettings = await runHookConfigSetup({ export async function sync(
settings, inlineConfig: AstroInlineConfig,
logging, options?: SyncOptions
): Promise<ProcessExit> {
const logging = createNodeLogging(inlineConfig);
const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'sync');
telemetry.record(eventCliSession('sync', userConfig));
const _settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
const settings = await runHookConfigSetup({
settings: _settings,
logging: logging,
command: 'build', 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. * 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 {SyncOptions} options
* @param {AstroSettings} settings Astro settings * @param {AstroSettings} settings Astro settings
* @param {typeof fsMod} options.fs The file system * @param {typeof fsMod} options.fs The file system
* @param {LogOptions} options.logging Logging options * @param {LogOptions} options.logging Logging options
* @return {Promise<ProcessExit>} * @return {Promise<ProcessExit>}
*/ */
export async function sync( export async function syncInternal(
settings: AstroSettings, settings: AstroSettings,
{ logging, fs }: SyncOptions { logging, fs }: SyncInternalOptions
): Promise<ProcessExit> { ): Promise<ProcessExit> {
const timerStart = performance.now(); const timerStart = performance.now();
// Needed to load content config // Needed to load content config
@ -88,7 +97,7 @@ export async function sync(
const contentTypesGenerator = await createContentTypesGenerator({ const contentTypesGenerator = await createContentTypesGenerator({
contentConfigObserver: globalContentConfigObserver, contentConfigObserver: globalContentConfigObserver,
logging, logging,
fs, fs: fs ?? fsMod,
settings, settings,
viteServer: tempViteServer, viteServer: tempViteServer,
}); });
@ -124,7 +133,7 @@ export async function sync(
} }
info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`); info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
await setUpEnvTs({ settings, logging, fs }); await setUpEnvTs({ settings, logging, fs: fs ?? fsMod });
return 0; return 0;
} }

View file

@ -9,7 +9,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: always', async () => { it('trailingSlash: always', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', 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', base: '/my-cool-base',
trailingSlash: 'always', trailingSlash: 'always',
}); });
@ -24,7 +24,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: never', async () => { it('trailingSlash: never', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', 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', base: '/my-cool-base',
trailingSlash: 'never', trailingSlash: 'never',
}); });
@ -39,7 +39,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: ignore', async () => { it('trailingSlash: ignore', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', 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', base: '/my-cool-base',
trailingSlash: 'ignore', trailingSlash: 'ignore',
}); });
@ -58,7 +58,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: always', async () => { it('trailingSlash: always', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', root: './fixtures/astro-markdown-url/',
outDir: new URL('./fixtures/astro-markdown-url/without-subpath-always/', import.meta.url), outDir: './without-subpath-always',
trailingSlash: 'always', trailingSlash: 'always',
}); });
await fixture.build(); await fixture.build();
@ -72,7 +72,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: never', async () => { it('trailingSlash: never', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', root: './fixtures/astro-markdown-url/',
outDir: new URL('./fixtures/astro-markdown-url/without-subpath-never/', import.meta.url), outDir: './without-subpath-never',
trailingSlash: 'never', trailingSlash: 'never',
}); });
await fixture.build(); await fixture.build();
@ -86,7 +86,7 @@ describe('Astro Markdown URL', () => {
it('trailingSlash: ignore', async () => { it('trailingSlash: ignore', async () => {
let fixture = await loadFixture({ let fixture = await loadFixture({
root: './fixtures/astro-markdown-url/', root: './fixtures/astro-markdown-url/',
outDir: new URL('./fixtures/astro-markdown-url/without-subpath-ignore/', import.meta.url), outDir: './without-subpath-ignore',
trailingSlash: 'ignore', trailingSlash: 'ignore',
}); });
await fixture.build(); await fixture.build();

View file

@ -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; const expectedTypesFile = new URL('.astro/types.d.ts', fixture.config.root).href;
expect(writtenFiles).to.haveOwnProperty(expectedTypesFile); 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, 'Did not try to update env.d.ts file.').to.haveOwnProperty(typesEnvPath);
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference path="../.astro/types.d.ts" />`); 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, 'Did not try to write env.d.ts file.').to.haveOwnProperty(typesEnvPath);
expect(writtenFiles[typesEnvPath]).to.include(`/// <reference types="astro/client" />`); expect(writtenFiles[typesEnvPath]).to.include(`/// <reference types="astro/client" />`);

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { loadFixture, silentLogging } from './test-utils.js'; import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js'; import testAdapter from './test-adapter.js';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
@ -108,8 +108,7 @@ describe('Astro.clientAddress', () => {
let devServer; let devServer;
before(async () => { before(async () => {
// We expect an error, so silence the output devServer = await fixture.startDevServer();
devServer = await fixture.startDevServer({ logging: silentLogging });
}); });
after(async () => { after(async () => {

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { loadFixture, silentLogging } from './test-utils.js'; import { loadFixture } from './test-utils.js';
describe('Development Routing', () => { describe('Development Routing', () => {
describe('No site config', () => { describe('No site config', () => {
@ -10,9 +10,7 @@ describe('Development Routing', () => {
before(async () => { before(async () => {
fixture = await loadFixture({ root: './fixtures/without-site-config/' }); fixture = await loadFixture({ root: './fixtures/without-site-config/' });
devServer = await fixture.startDevServer({ devServer = await fixture.startDevServer();
logging: silentLogging,
});
}); });
after(async () => { after(async () => {

View file

@ -1,6 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio'; import { load as cheerioLoad } from 'cheerio';
import { loadFixture, silentLogging } from './test-utils.js'; import { loadFixture } from './test-utils.js';
describe('Dynamic endpoint collision', () => { describe('Dynamic endpoint collision', () => {
describe('build', () => { describe('build', () => {
@ -31,9 +31,7 @@ describe('Dynamic endpoint collision', () => {
root: './fixtures/dynamic-endpoint-collision/', root: './fixtures/dynamic-endpoint-collision/',
}); });
devServer = await fixture.startDevServer({ devServer = await fixture.startDevServer();
logging: silentLogging,
});
}); });
after(async () => { after(async () => {

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { loadFixture, silentLogging } from './test-utils.js'; import { loadFixture } from './test-utils.js';
describe('Errors in JavaScript', () => { describe('Errors in JavaScript', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
@ -15,9 +15,7 @@ describe('Errors in JavaScript', () => {
logLevel: 'silent', logLevel: 'silent',
}, },
}); });
devServer = await fixture.startDevServer({ devServer = await fixture.startDevServer();
logging: silentLogging,
});
}); });
after(async () => { after(async () => {

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'; 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', () => { describe('Can handle errors that are not instanceof Error', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
@ -12,9 +12,7 @@ describe('Can handle errors that are not instanceof Error', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/error-non-error', root: './fixtures/error-non-error',
}); });
devServer = await fixture.startDevServer({ devServer = await fixture.startDevServer();
logging: silentLogging,
});
}); });
after(async () => { after(async () => {

View file

@ -13,7 +13,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4000/', import.meta.url), outDir: './dist-4000',
build: { build: {
format: 'directory', format: 'directory',
}, },
@ -41,9 +41,10 @@ describe('Preview Routing', () => {
expect(response.redirected).to.equal(false); 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'); 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 () => { it('404 when loading another page with subpath used', async () => {
@ -72,7 +73,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4001/', import.meta.url), outDir: './dist-4001',
trailingSlash: 'always', trailingSlash: 'always',
server: { server: {
port: 4001, port: 4001,
@ -132,7 +133,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4002/', import.meta.url), outDir: './dist-4002',
trailingSlash: 'ignore', trailingSlash: 'ignore',
server: { server: {
port: 4002, port: 4002,
@ -194,7 +195,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4003/', import.meta.url), outDir: './dist-4003',
build: { build: {
format: 'file', format: 'file',
}, },
@ -222,9 +223,10 @@ describe('Preview Routing', () => {
expect(response.redirected).to.equal(false); 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'); 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 () => { it('404 when loading another page with subpath used', async () => {
@ -253,7 +255,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4004/', import.meta.url), outDir: './dist-4004',
build: { build: {
format: 'file', format: 'file',
}, },
@ -316,7 +318,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4005/', import.meta.url), outDir: './dist-4005',
build: { build: {
format: 'file', format: 'file',
}, },
@ -379,7 +381,7 @@ describe('Preview Routing', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/with-subpath-no-trailing-slash/', root: './fixtures/with-subpath-no-trailing-slash/',
base: '/blog', base: '/blog',
outDir: new URL('./fixtures/with-subpath-no-trailing-slash/dist-4006/', import.meta.url), outDir: './dist-4006',
build: { build: {
format: 'file', format: 'file',
}, },

View file

@ -1,6 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio'; import { load as cheerioLoad } from 'cheerio';
import { isWindows, loadFixture, silentLogging } from './test-utils.js'; import { isWindows, loadFixture } from './test-utils.js';
let fixture; let fixture;
@ -108,9 +108,7 @@ describe('React Components', () => {
let devServer; let devServer;
before(async () => { before(async () => {
devServer = await fixture.startDevServer({ devServer = await fixture.startDevServer();
logging: silentLogging,
});
}); });
after(async () => { after(async () => {

View file

@ -3,6 +3,7 @@ import { execa } from 'execa';
import fastGlob from 'fast-glob'; import fastGlob from 'fast-glob';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os'; import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import stripAnsi from 'strip-ansi'; import stripAnsi from 'strip-ansi';
import { check } from '../dist/cli/check/index.js'; 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 { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js';
import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js'; import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js';
import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js'; import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js';
import { openConfig } from '../dist/core/config/config.js'; import { mergeConfig, resolveConfig } from '../dist/core/config/index.js';
import { createSettings } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js'; import dev from '../dist/core/dev/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js'; import { nodeLogDestination } from '../dist/core/logger/node.js';
import preview from '../dist/core/preview/index.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('undici').Response} Response
* @typedef {import('../src/core/dev/dev').DedvServer} DevServer * @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/preview/index').PreviewServer} PreviewServer
* @typedef {import('../src/core/app/index').App} App * @typedef {import('../src/core/app/index').App} App
* @typedef {import('../src/cli/check/index').AstroChecker} AstroChecker * @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, updater: (content: string) => string) => Promise<void>} writeFile
* @property {(path: string) => Promise<string[]>} readdir * @property {(path: string) => Promise<string[]>} readdir
* @property {(pattern: string) => Promise<string[]>} glob * @property {(pattern: string) => Promise<string[]>} glob
* @property {() => Promise<DevServer>} startDevServer * @property {typeof dev} startDevServer
* @property {() => Promise<PreviewServer>} preview * @property {typeof preview} preview
* @property {() => Promise<void>} clean * @property {() => Promise<void>} clean
* @property {() => Promise<App>} loadTestAdapterApp * @property {() => Promise<App>} loadTestAdapterApp
* @property {() => Promise<void>} onNextChange * @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 * This function returns an instance of the Check
* *
@ -82,7 +83,7 @@ export const silentLogging = {
/** /**
* Load Astro fixture * 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: * @returns {Promise<Fixture>} The fixture. Has the following properties:
* .config - Returns the final config. Will be automatically passed to the methods below: * .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) { export async function loadFixture(inlineConfig) {
if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }"); if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
// load config // Silent by default during tests to not pollute the console output
let cwd = inlineConfig.root; inlineConfig.logLevel = 'silent';
delete inlineConfig.root;
if (typeof cwd === 'string') { let root = inlineConfig.root;
try { // Handle URL, should already be absolute so just convert to path
cwd = new URL(cwd.replace(/\/?$/, '/')); if (typeof root !== 'string') {
} catch (err1) { root = fileURLToPath(root);
cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url);
}
} }
// Handle "file:///C:/Users/fred", convert to "C:/Users/fred"
/** @type {import('../src/core/logger/core').LogOptions} */ else if (root.startsWith('file://')) {
const logging = defaultLogging; root = fileURLToPath(new URL(root));
}
// 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. // Load the config.
let { astroConfig: config } = await openConfig({ const { astroConfig: config } = await resolveConfig(inlineConfig, 'dev');
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);
}
if (inlineConfig.base && !inlineConfig.base.endsWith('/')) {
config.base = inlineConfig.base + '/';
}
/**
* 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;
};
const resolveUrl = (url) => const resolveUrl = (url) =>
`http://${config.server.host || 'localhost'}:${config.server.port}${url.replace(/^\/?/, '/')}`; `http://${config.server.host || 'localhost'}:${config.server.port}${url.replace(/^\/?/, '/')}`;
@ -177,17 +153,19 @@ export async function loadFixture(inlineConfig) {
let devServer; let devServer;
return { return {
build: async (opts = {}) => { build: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'production'; 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) => { 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'; 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.host = parseAddressToHost(devServer.address.address); // update host
config.server.port = devServer.address.port; // update port config.server.port = devServer.address.port; // update port
return devServer; return devServer;
@ -207,9 +185,9 @@ export async function loadFixture(inlineConfig) {
throw err; throw err;
} }
}, },
preview: async (opts = {}) => { preview: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'production'; 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.host = parseAddressToHost(previewServer.host); // update host
config.server.port = previewServer.port; // update port config.server.port = previewServer.port; // update port
return previewServer; return previewServer;
@ -282,32 +260,6 @@ function parseAddressToHost(address) {
return 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)); const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url));
/** Returns a process running the Astro CLI. */ /** Returns a process running the Astro CLI. */

View file

@ -1,24 +1,25 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { defaultLogging } from '../test-utils.js'; import { flagsToAstroInlineConfig } from '../../../dist/cli/flags.js';
import { openConfig } from '../../../dist/core/config/index.js'; import { resolveConfig } from '../../../dist/core/config/index.js';
const cwd = fileURLToPath(new URL('../../fixtures/config-host/', import.meta.url)); const cwd = fileURLToPath(new URL('../../fixtures/config-host/', import.meta.url));
describe('config.server', () => { describe('config.server', () => {
function openConfigWithFlags(flags) { function resolveConfigWithFlags(flags) {
return openConfig({ return resolveConfig(
cwd: flags.root || cwd, flagsToAstroInlineConfig({
flags, root: cwd,
cmd: 'dev', ...flags,
logging: defaultLogging, }),
}); 'dev'
);
} }
describe('host', () => { describe('host', () => {
it('can be specified via --host flag', async () => { it('can be specified via --host flag', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url); const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const { astroConfig } = await openConfigWithFlags({ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL), root: fileURLToPath(projectRootURL),
host: true, host: true,
}); });
@ -32,7 +33,7 @@ describe('config.server', () => {
it('can be passed via relative --config', async () => { it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url); const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = 'my-config.mjs'; const configFileURL = 'my-config.mjs';
const { astroConfig } = await openConfigWithFlags({ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL), root: fileURLToPath(projectRootURL),
config: configFileURL, config: configFileURL,
}); });
@ -44,7 +45,7 @@ describe('config.server', () => {
it('can be passed via relative --config', async () => { it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url); const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './my-config.mjs'; const configFileURL = './my-config.mjs';
const { astroConfig } = await openConfigWithFlags({ const { astroConfig } = await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL), root: fileURLToPath(projectRootURL),
config: configFileURL, config: configFileURL,
}); });
@ -57,7 +58,7 @@ describe('config.server', () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url); const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './does-not-exist.mjs'; const configFileURL = './does-not-exist.mjs';
try { try {
await openConfigWithFlags({ await resolveConfigWithFlags({
root: fileURLToPath(projectRootURL), root: fileURLToPath(projectRootURL),
config: configFileURL, config: configFileURL,
}); });

View file

@ -2,7 +2,7 @@ import { expect } from 'chai';
import { z } from 'zod'; import { z } from 'zod';
import stripAnsi from 'strip-ansi'; import stripAnsi from 'strip-ansi';
import { formatConfigErrorMessage } from '../../../dist/core/messages.js'; 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', () => { describe('Config Validation', () => {
it('empty user config is valid', async () => { it('empty user config is valid', async () => {

View file

@ -1,8 +1,6 @@
import { fileURLToPath } from 'node:url';
import { expect } from 'chai'; import { expect } from 'chai';
import { createFs, runInContainer } from '../test-utils.js';
import { createSettings, openConfig } from '../../../dist/core/config/index.js';
import { runInContainer } from '../../../dist/core/dev/index.js';
import { createFs, defaultLogging } from '../test-utils.js';
const root = new URL('../../fixtures/tailwindcss-ts/', import.meta.url); const root = new URL('../../fixtures/tailwindcss-ts/', import.meta.url);
@ -20,16 +18,7 @@ describe('Astro config formats', () => {
root root
); );
const { astroConfig } = await openConfig({ await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, () => {
cwd: root,
flags: {},
cmd: 'dev',
logging: defaultLogging,
fsMod: fs,
});
const settings = createSettings(astroConfig);
await runInContainer({ fs, root, settings }, () => {
expect(true).to.equal( expect(true).to.equal(
true, true,
'We were able to get into the container which means the config loaded.' 'We were able to get into the container which means the config loaded.'

View file

@ -2,9 +2,8 @@ import { fileURLToPath } from 'node:url';
import nodeFS from 'node:fs'; import nodeFS from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { runInContainer } from '../../../dist/core/dev/index.js';
import { attachContentServerListeners } from '../../../dist/content/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); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -53,7 +52,7 @@ describe('frontmatter', () => {
root root
); );
await runInContainer({ fs, root }, async (container) => { await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
await attachContentServerListeners(container); await attachContentServerListeners(container);
fs.writeFileFromRootSync( fs.writeFileFromRootSync(

View file

@ -1,7 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
import { runInContainer } from '../../../dist/core/dev/index.js'; import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
import { createFs, createRequestAndResponse } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -19,8 +18,8 @@ describe('base configuration', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
base: '/docs', base: '/docs',
trailingSlash: 'never', trailingSlash: 'never',
}, },
@ -48,8 +47,8 @@ describe('base configuration', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
base: '/docs', base: '/docs',
trailingSlash: 'never', trailingSlash: 'never',
}, },
@ -79,8 +78,8 @@ describe('base configuration', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
base: '/docs', base: '/docs',
trailingSlash: 'never', trailingSlash: 'never',
}, },
@ -108,8 +107,8 @@ describe('base configuration', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
base: '/docs', base: '/docs',
trailingSlash: 'never', trailingSlash: 'never',
}, },

View file

@ -1,18 +1,12 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { fileURLToPath } from 'node:url'; 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 { 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 root = new URL('../../fixtures/content-mixed-errors/', import.meta.url);
const logging = defaultLogging;
async function sync({ fs, config = {} }) { async function sync({ fs, config = {} }) {
const astroConfig = await validateConfig(config, fileURLToPath(root), 'prod'); return _sync({ ...config, root: fileURLToPath(root), logLevel: 'silent' }, { fs });
const settings = createSettings(astroConfig, fileURLToPath(root));
return _sync(settings, { logging, fs });
} }
describe('Content Collections - mixed content errors', () => { describe('Content Collections - mixed content errors', () => {

View file

@ -1,16 +1,16 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import os from 'node:os'; 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 { attachContentServerListeners } from '../../../dist/content/server-listeners.js';
import { runInContainer } from '../../../dist/core/dev/index.js'; import { createFsWithFallback, createRequestAndResponse, runInContainer } from '../test-utils.js';
import { createFsWithFallback, createRequestAndResponse } from '../test-utils.js';
const root = new URL('../../fixtures/content/', import.meta.url); const root = new URL('../../fixtures/content/', import.meta.url);
const describe = os.platform() === 'win32' ? global.describe.skip : global.describe; const describe = os.platform() === 'win32' ? global.describe.skip : global.describe;
/** @type {typeof runInContainer} */
async function runInContainerWithContentListeners(params, callback) { async function runInContainerWithContentListeners(params, callback) {
return await runInContainer(params, async (container) => { return await runInContainer(params, async (container) => {
await attachContentServerListeners(container); await attachContentServerListeners(container);
@ -56,9 +56,8 @@ describe('Content Collections - render()', () => {
await runInContainerWithContentListeners( await runInContainerWithContentListeners(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
integrations: [mdx()],
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },
@ -129,9 +128,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners( await runInContainerWithContentListeners(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
integrations: [mdx()],
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },
@ -200,9 +198,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners( await runInContainerWithContentListeners(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
integrations: [mdx()],
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },
@ -270,9 +267,8 @@ description: Astro is launching this week!
await runInContainerWithContentListeners( await runInContainerWithContentListeners(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
integrations: [mdx()],
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },

View file

@ -1,8 +1,12 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url';
import { runInContainer } from '../../../dist/core/dev/index.js'; import {
import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js'; createFs,
createRequestAndResponse,
triggerFSEvent,
runInContainer,
} from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -25,7 +29,7 @@ describe('dev container', () => {
root root
); );
await runInContainer({ fs, root }, async (container) => { await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
const { req, res, text } = createRequestAndResponse({ const { req, res, text } = createRequestAndResponse({
method: 'GET', method: 'GET',
url: '/', url: '/',
@ -60,7 +64,7 @@ describe('dev container', () => {
root root
); );
await runInContainer({ fs, root }, async (container) => { await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => {
let r = createRequestAndResponse({ let r = createRequestAndResponse({
method: 'GET', method: 'GET',
url: '/', url: '/',
@ -119,8 +123,8 @@ describe('dev container', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
output: 'server', output: 'server',
integrations: [ integrations: [
{ {
@ -170,8 +174,8 @@ describe('dev container', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
output: 'server', output: 'server',
integrations: [ integrations: [
{ {
@ -223,8 +227,8 @@ describe('dev container', () => {
it('items in public/ are not available from root when using a base', async () => { it('items in public/ are not available from root when using a base', async () => {
await runInContainer( await runInContainer(
{ {
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
base: '/sub/', base: '/sub/',
}, },
}, },
@ -256,7 +260,7 @@ describe('dev container', () => {
}); });
it('items in public/ are available from root when not using a base', async () => { 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 // Try the root path
let r = createRequestAndResponse({ let r = createRequestAndResponse({
method: 'GET', method: 'GET',

View file

@ -1,8 +1,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url';
import { runInContainer } from '../../../dist/core/dev/index.js'; import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
import { createFs, createRequestAndResponse } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -65,8 +64,8 @@ describe('head injection', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },
@ -154,8 +153,8 @@ describe('head injection', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
userConfig: { root: fileURLToPath(root),
vite: { server: { middlewareMode: true } }, vite: { server: { middlewareMode: true } },
}, },
}, },

View file

@ -1,12 +1,10 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
import { runInContainer } from '../../../dist/core/dev/index.js'; import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
import { createFs, createRequestAndResponse, silentLogging } from '../test-utils.js';
import svelte from '../../../../integrations/svelte/dist/index.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
describe('dev container', () => { describe('hydration', () => {
it('should not crash when reassigning a hydrated component', async () => { it('should not crash when reassigning a hydrated component', async () => {
const fs = createFs( const fs = createFs(
{ {
@ -31,10 +29,9 @@ describe('dev container', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
logging: silentLogging, root: fileURLToPath(root),
userConfig: { logLevel: 'silent',
integrations: [svelte()],
}, },
}, },
async (container) => { async (container) => {

View file

@ -2,18 +2,12 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { createSettings, openConfig } from '../../../dist/core/config/index.js';
import { import {
createContainerWithAutomaticRestart, createContainerWithAutomaticRestart,
isStarted, isStarted,
startContainer, startContainer,
} from '../../../dist/core/dev/index.js'; } from '../../../dist/core/dev/index.js';
import { import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
createFs,
createRequestAndResponse,
defaultLogging,
triggerFSEvent,
} from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -36,8 +30,9 @@ describe('dev container restarts', () => {
root root
); );
let restart = await createContainerWithAutomaticRestart({ const restart = await createContainerWithAutomaticRestart({
params: { fs, root }, fs,
inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
}); });
try { try {
@ -99,8 +94,9 @@ describe('dev container restarts', () => {
root root
); );
let restart = await createContainerWithAutomaticRestart({ const restart = await createContainerWithAutomaticRestart({
params: { fs, root }, fs,
inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
}); });
await startContainer(restart.container); await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true); expect(isStarted(restart.container)).to.equal(true);
@ -127,16 +123,9 @@ describe('dev container restarts', () => {
troot troot
); );
const { astroConfig } = await openConfig({ const restart = await createContainerWithAutomaticRestart({
cwd: troot, fs,
flags: {}, inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
cmd: 'dev',
logging: defaultLogging,
});
const settings = createSettings(astroConfig);
let restart = await createContainerWithAutomaticRestart({
params: { fs, root, settings },
}); });
await startContainer(restart.container); await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true); expect(isStarted(restart.container)).to.equal(true);
@ -161,16 +150,9 @@ describe('dev container restarts', () => {
root root
); );
const { astroConfig } = await openConfig({ const restart = await createContainerWithAutomaticRestart({
cwd: root, fs,
flags: {}, inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
cmd: 'dev',
logging: defaultLogging,
});
const settings = createSettings(astroConfig, fileURLToPath(root));
let restart = await createContainerWithAutomaticRestart({
params: { fs, root, settings },
}); });
await startContainer(restart.container); await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true); expect(isStarted(restart.container)).to.equal(true);
@ -193,16 +175,9 @@ describe('dev container restarts', () => {
root root
); );
const { astroConfig } = await openConfig({ const restart = await createContainerWithAutomaticRestart({
cwd: root, fs,
flags: {}, inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' },
cmd: 'dev',
logging: defaultLogging,
});
const settings = createSettings(astroConfig, fileURLToPath(root));
let restart = await createContainerWithAutomaticRestart({
params: { fs, root, settings },
}); });
await startContainer(restart.container); await startContainer(restart.container);
expect(isStarted(restart.container)).to.equal(true); expect(isStarted(restart.container)).to.equal(true);

View file

@ -1,9 +1,8 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url';
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 svelte from '../../../../integrations/svelte/dist/index.js';
import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -31,9 +30,9 @@ describe('core/render components', () => {
await runInContainer( await runInContainer(
{ {
fs, fs,
root, inlineConfig: {
logging: silentLogging, root: fileURLToPath(root),
userConfig: { logLevel: 'silent',
integrations: [svelte()], integrations: [svelte()],
}, },
}, },

View file

@ -1,10 +1,8 @@
import { expect } from 'chai'; 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 { 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); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -16,13 +14,11 @@ describe('routing - createRouteManifest', () => {
}, },
root root
); );
const settings = await createDefaultDevSettings( const settings = await createBasicSettings({
{ root: fileURLToPath(root),
base: '/search', base: '/search',
trailingSlash: 'never', trailingSlash: 'never',
}, });
root
);
const manifest = createRouteManifest({ const manifest = createRouteManifest({
cwd: fileURLToPath(root), cwd: fileURLToPath(root),
settings, settings,
@ -41,17 +37,15 @@ describe('routing - createRouteManifest', () => {
}, },
root root
); );
const settings = await createDefaultDevSettings( const settings = await createBasicSettings({
{ root: fileURLToPath(root),
base: '/search', base: '/search',
trailingSlash: 'never', trailingSlash: 'never',
redirects: { redirects: {
'/blog/[...slug]': '/', '/blog/[...slug]': '/',
'/blog/contributing': '/another', '/blog/contributing': '/another',
},
}, },
root });
);
const manifest = createRouteManifest({ const manifest = createRouteManifest({
cwd: fileURLToPath(root), cwd: fileURLToPath(root),
settings, settings,
@ -70,15 +64,13 @@ describe('routing - createRouteManifest', () => {
}, },
root root
); );
const settings = await createDefaultDevSettings( const settings = await createBasicSettings({
{ root: fileURLToPath(root),
trailingSlash: 'never', trailingSlash: 'never',
redirects: { redirects: {
'/foo': '/bar', '/foo': '/bar',
},
}, },
root });
);
const manifest = createRouteManifest( const manifest = createRouteManifest(
{ {
cwd: fileURLToPath(root), cwd: fileURLToPath(root),

View file

@ -1,5 +1,9 @@
// @ts-check import {
import { createFs, createRequestAndResponse, defaultLogging } from '../test-utils.js'; createBasicSettings,
createFs,
createRequestAndResponse,
defaultLogging,
} from '../test-utils.js';
import { createRouteManifest, matchAllRoutes } from '../../../dist/core/routing/index.js'; import { createRouteManifest, matchAllRoutes } from '../../../dist/core/routing/index.js';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { createViteLoader } from '../../../dist/core/module-loader/vite.js'; import { createViteLoader } from '../../../dist/core/module-loader/vite.js';
@ -127,16 +131,17 @@ describe('Route matching', () => {
before(async () => { before(async () => {
const fs = createFs(fileSystem, root); const fs = createFs(fileSystem, root);
settings = await createBasicSettings({
root: fileURLToPath(root),
trailingSlash: 'never',
output: 'hybrid',
adapter: testAdapter(),
});
container = await createContainer({ container = await createContainer({
fs, fs,
root, settings,
userConfig: { logging: defaultLogging,
trailingSlash: 'never',
output: 'hybrid',
adapter: testAdapter(),
},
}); });
settings = container.settings;
const loader = createViteLoader(container.viteServer); const loader = createViteLoader(container.viteServer);
const manifest = createDevelopmentManifest(container.settings); const manifest = createDevelopmentManifest(container.settings);

View file

@ -1,6 +1,8 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
import { createContainer } from '../../../dist/core/dev/index.js'; import { createContainer } from '../../../dist/core/dev/index.js';
import { createViteLoader } from '../../../dist/core/module-loader/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); const root = new URL('../../fixtures/alias/', import.meta.url);
@ -9,7 +11,8 @@ describe('<Code />', () => {
let container; let container;
let mod; let mod;
before(async () => { before(async () => {
container = await createContainer({ root }); const settings = await createBasicSettings({ root: fileURLToPath(root) });
container = await createContainer({ settings, logging: defaultLogging });
const loader = createViteLoader(container.viteServer); const loader = createViteLoader(container.viteServer);
mod = await loader.import('astro/components/Shiki.js'); mod = await loader.import('astro/components/Shiki.js');
}); });

View file

@ -8,6 +8,9 @@ import { getDefaultClientDirectives } from '../../dist/core/client-directive/ind
import { nodeLogDestination } from '../../dist/core/logger/node.js'; import { nodeLogDestination } from '../../dist/core/logger/node.js';
import { createEnvironment } from '../../dist/core/render/index.js'; import { createEnvironment } from '../../dist/core/render/index.js';
import { RouteCache } from '../../dist/core/render/route-cache.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'; import { unixify } from './correct-path.js';
/** @type {import('../../src/core/logger/core').LogOptions} */ /** @type {import('../../src/core/logger/core').LogOptions} */
@ -189,3 +192,42 @@ export function createBasicEnvironment(options = {}) {
streaming: options.streaming ?? true, 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();
}
}

View file

@ -1,6 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { createDefaultDevSettings } from '../../../dist/core/config/index.js';
import { createLoader } from '../../../dist/core/module-loader/index.js'; import { createLoader } from '../../../dist/core/module-loader/index.js';
import { createRouteManifest } from '../../../dist/core/routing/index.js'; import { createRouteManifest } from '../../../dist/core/routing/index.js';
import { createComponent, render } from '../../../dist/runtime/server/index.js'; import { createComponent, render } from '../../../dist/runtime/server/index.js';
@ -8,6 +7,7 @@ import { createController, handleRequest } from '../../../dist/vite-plugin-astro
import { import {
createAstroModule, createAstroModule,
createBasicEnvironment, createBasicEnvironment,
createBasicSettings,
createFs, createFs,
createRequestAndResponse, createRequestAndResponse,
defaultLogging, defaultLogging,
@ -15,7 +15,7 @@ import {
async function createDevEnvironment(overrides = {}) { async function createDevEnvironment(overrides = {}) {
const env = createBasicEnvironment(); const env = createBasicEnvironment();
env.settings = await createDefaultDevSettings({}, '/'); env.settings = await createBasicSettings({ root: '/' });
env.settings.renderers = []; env.settings.renderers = [];
env.loader = createLoader(); env.loader = createLoader();
Object.assign(env, overrides); Object.assign(env, overrides);

View file

@ -23,7 +23,7 @@ describe('getStaticPaths', () => {
const $ = cheerio.load(html); const $ = cheerio.load(html);
expect($('p').text()).to.equal('First mdx file'); expect($('p').text()).to.equal('First mdx file');
expect($('#one').text()).to.equal('hello', 'Frontmatter included'); 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( expect($('#file').text()).to.contain(
'fixtures/mdx-get-static-paths/src/content/1.mdx', 'fixtures/mdx-get-static-paths/src/content/1.mdx',
'file is included' 'file is included'

View file

@ -96,7 +96,7 @@ describe('MDX plugins', () => {
it('ignores string-based plugins in markdown config', async () => { it('ignores string-based plugins in markdown config', async () => {
const fixture = await buildFixture({ const fixture = await buildFixture({
markdown: { markdown: {
remarkPlugins: [['remark-toc']], remarkPlugins: [['remark-toc', {}]],
}, },
integrations: [mdx()], integrations: [mdx()],
}); });