aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/calm-baboons-watch.md5
-rw-r--r--.changeset/modern-candles-sip.md5
-rw-r--r--packages/astro/src/assets/build/generate.ts4
-rw-r--r--packages/astro/src/cli/install-package.ts2
-rw-r--r--packages/astro/src/cli/telemetry/index.ts12
-rw-r--r--packages/astro/src/content/server-listeners.ts14
-rw-r--r--packages/astro/src/content/types-generator.ts93
-rw-r--r--packages/astro/src/core/app/index.ts2
-rw-r--r--packages/astro/src/core/build/generate.ts34
-rw-r--r--packages/astro/src/core/build/index.ts27
-rw-r--r--packages/astro/src/core/build/static-build.ts9
-rw-r--r--packages/astro/src/core/build/util.ts2
-rw-r--r--packages/astro/src/core/create-vite.ts18
-rw-r--r--packages/astro/src/core/dev/dev.ts11
-rw-r--r--packages/astro/src/core/dev/restart.ts14
-rw-r--r--packages/astro/src/core/endpoint/index.ts8
-rw-r--r--packages/astro/src/core/errors/dev/vite.ts1
-rw-r--r--packages/astro/src/core/logger/console.ts40
-rw-r--r--packages/astro/src/core/logger/core.ts64
-rw-r--r--packages/astro/src/core/logger/node.ts103
-rw-r--r--packages/astro/src/core/messages.ts98
-rw-r--r--packages/astro/src/core/middleware/callMiddleware.ts5
-rw-r--r--packages/astro/src/core/preview/static-preview-server.ts4
-rw-r--r--packages/astro/src/core/render/core.ts2
-rw-r--r--packages/astro/src/core/render/result.ts2
-rw-r--r--packages/astro/src/core/render/route-cache.ts4
-rw-r--r--packages/astro/src/core/request.ts6
-rw-r--r--packages/astro/src/core/routing/manifest/create.ts16
-rw-r--r--packages/astro/src/core/routing/validation.ts8
-rw-r--r--packages/astro/src/core/sync/index.ts4
-rw-r--r--packages/astro/src/integrations/astroFeaturesValidation.ts12
-rw-r--r--packages/astro/src/integrations/index.ts25
-rw-r--r--packages/astro/src/runtime/server/endpoint.ts14
-rw-r--r--packages/astro/src/vite-plugin-astro-server/base.ts11
-rw-r--r--packages/astro/src/vite-plugin-astro-server/common.ts6
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts25
-rw-r--r--packages/astro/src/vite-plugin-astro/hmr.ts11
-rw-r--r--packages/astro/src/vite-plugin-astro/index.ts48
-rw-r--r--packages/astro/src/vite-plugin-inject-env-ts/index.ts6
-rw-r--r--packages/astro/src/vite-plugin-scanner/index.ts7
-rw-r--r--packages/astro/test/cli.test.js2
-rw-r--r--packages/astro/test/core-image.test.js1
-rw-r--r--packages/astro/test/static-build.test.js3
-rw-r--r--packages/astro/test/units/vite-plugin-astro-server/request.test.js2
-rw-r--r--packages/create-astro/src/index.ts4
-rw-r--r--packages/integrations/sitemap/src/index.ts6
-rw-r--r--packages/integrations/sitemap/src/utils/logger.ts46
47 files changed, 395 insertions, 451 deletions
diff --git a/.changeset/calm-baboons-watch.md b/.changeset/calm-baboons-watch.md
new file mode 100644
index 000000000..a0e8259e9
--- /dev/null
+++ b/.changeset/calm-baboons-watch.md
@@ -0,0 +1,5 @@
+---
+'astro': minor
+---
+
+Update CLI logging experience
diff --git a/.changeset/modern-candles-sip.md b/.changeset/modern-candles-sip.md
new file mode 100644
index 000000000..31e75c412
--- /dev/null
+++ b/.changeset/modern-candles-sip.md
@@ -0,0 +1,5 @@
+---
+'create-astro': patch
+---
+
+Stop clearing the console on start
diff --git a/packages/astro/src/assets/build/generate.ts b/packages/astro/src/assets/build/generate.ts
index be637c26d..c4109ea1e 100644
--- a/packages/astro/src/assets/build/generate.ts
+++ b/packages/astro/src/assets/build/generate.ts
@@ -58,7 +58,7 @@ export async function prepareAssetsGenerationEnv(
await fs.promises.mkdir(assetsCacheDir, { recursive: true });
} catch (err) {
logger.warn(
- 'astro:assets',
+ null,
`An error was encountered while creating the cache directory. Proceeding without caching. Error: ${err}`
);
useCache = false;
@@ -231,7 +231,7 @@ export async function generateImagesForPath(
}
} catch (e) {
env.logger.warn(
- 'astro:assets',
+ null,
`An error was encountered while creating the cache directory. Proceeding without caching. Error: ${e}`
);
} finally {
diff --git a/packages/astro/src/cli/install-package.ts b/packages/astro/src/cli/install-package.ts
index 689f81e3e..667037a0c 100644
--- a/packages/astro/src/cli/install-package.ts
+++ b/packages/astro/src/cli/install-package.ts
@@ -26,7 +26,7 @@ export async function getPackage<T>(
packageImport = await import(packageName);
} catch (e) {
logger.info(
- '',
+ null,
`To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`
);
const result = await installPackage([packageName, ...otherDeps], options, logger);
diff --git a/packages/astro/src/cli/telemetry/index.ts b/packages/astro/src/cli/telemetry/index.ts
index fd664fcc9..277b1cab6 100644
--- a/packages/astro/src/cli/telemetry/index.ts
+++ b/packages/astro/src/cli/telemetry/index.ts
@@ -1,23 +1,23 @@
/* eslint-disable no-console */
-import whichPm from 'which-pm';
import type yargs from 'yargs-parser';
import * as msg from '../../core/messages.js';
import { telemetry } from '../../events/index.js';
+import { createLoggerFromFlags } from '../flags.js';
interface TelemetryOptions {
flags: yargs.Arguments;
}
export async function notify() {
- const packageManager = (await whichPm(process.cwd()))?.name ?? 'npm';
await telemetry.notify(() => {
- console.log(msg.telemetryNotice(packageManager) + '\n');
+ console.log(msg.telemetryNotice() + '\n');
return true;
});
}
export async function update(subcommand: string, { flags }: TelemetryOptions) {
const isValid = ['enable', 'disable', 'reset'].includes(subcommand);
+ const logger = createLoggerFromFlags(flags);
if (flags.help || flags.h || !isValid) {
msg.printHelp({
@@ -37,17 +37,17 @@ export async function update(subcommand: string, { flags }: TelemetryOptions) {
switch (subcommand) {
case 'enable': {
telemetry.setEnabled(true);
- console.log(msg.telemetryEnabled());
+ logger.info('SKIP_FORMAT', msg.telemetryEnabled());
return;
}
case 'disable': {
telemetry.setEnabled(false);
- console.log(msg.telemetryDisabled());
+ logger.info('SKIP_FORMAT', msg.telemetryDisabled());
return;
}
case 'reset': {
telemetry.clear();
- console.log(msg.telemetryReset());
+ logger.info('SKIP_FORMAT', msg.telemetryReset());
return;
}
}
diff --git a/packages/astro/src/content/server-listeners.ts b/packages/astro/src/content/server-listeners.ts
index 699d5f271..3ff3148cb 100644
--- a/packages/astro/src/content/server-listeners.ts
+++ b/packages/astro/src/content/server-listeners.ts
@@ -1,4 +1,4 @@
-import { bold, cyan } from 'kleur/colors';
+import { bold, cyan, underline } from 'kleur/colors';
import type fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -26,7 +26,7 @@ export async function attachContentServerListeners({
const contentPaths = getContentPaths(settings.config, fs);
if (fs.existsSync(contentPaths.contentDir)) {
- logger.info(
+ logger.debug(
'content',
`Watching ${cyan(
contentPaths.contentDir.href.replace(settings.config.root.href, '')
@@ -39,7 +39,7 @@ export async function attachContentServerListeners({
viteServer.watcher.on('addDir', contentDirListener);
async function contentDirListener(dir: string) {
if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) {
- logger.info('content', `Content dir found. Watching for changes`);
+ logger.debug('content', `Content directory found. Watching for changes`);
await attachListeners();
viteServer.watcher.removeListener('addDir', contentDirListener);
}
@@ -55,7 +55,7 @@ export async function attachContentServerListeners({
contentConfigObserver: globalContentConfigObserver,
});
await contentGenerator.init();
- logger.info('content', 'Types generated');
+ logger.debug('content', 'Types generated');
viteServer.watcher.on('add', (entry) => {
contentGenerator.queueEvent({ name: 'add', entry });
@@ -90,9 +90,9 @@ function warnAllowJsIsFalse({
'true'
)} in your ${bold(tsConfigFileName)} file to have autocompletion in your ${bold(
contentConfigFileName
- )} file.
-See ${bold('https://www.typescriptlang.org/tsconfig#allowJs')} for more information.
- `
+ )} file. See ${underline(
+ cyan('https://www.typescriptlang.org/tsconfig#allowJs')
+ )} for more information.`
);
}
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index b50c597fd..dc9c1ecc7 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -1,5 +1,5 @@
import glob from 'fast-glob';
-import { cyan } from 'kleur/colors';
+import { bold, cyan } from 'kleur/colors';
import type fsMod from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -56,13 +56,6 @@ type CreateContentGeneratorParams = {
fs: typeof fsMod;
};
-type EventOpts = { logLevel: 'info' | 'warn' };
-
-type EventWithOptions = {
- type: ContentEvent;
- opts: EventOpts | undefined;
-};
-
class UnsupportedFileTypeError extends Error {}
export async function createContentTypesGenerator({
@@ -78,7 +71,7 @@ export async function createContentTypesGenerator({
const contentEntryExts = [...contentEntryConfigByExt.keys()];
const dataEntryExts = getDataEntryExts(settings);
- let events: EventWithOptions[] = [];
+ let events: ContentEvent[] = [];
let debounceTimeout: NodeJS.Timeout | undefined;
const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8');
@@ -90,10 +83,7 @@ export async function createContentTypesGenerator({
return { typesGenerated: false, reason: 'no-content-dir' };
}
- events.push({
- type: { name: 'add', entry: contentPaths.config.url },
- opts: { logLevel: 'warn' },
- });
+ events.push({ name: 'add', entry: contentPaths.config.url });
const globResult = await glob('**', {
cwd: fileURLToPath(contentPaths.contentDir),
@@ -110,12 +100,9 @@ export async function createContentTypesGenerator({
const entryURL = pathToFileURL(fullPath);
if (entryURL.href.startsWith(contentPaths.config.url.href)) continue;
if (entry.dirent.isFile()) {
- events.push({
- type: { name: 'add', entry: entryURL },
- opts: { logLevel: 'warn' },
- });
+ events.push({ name: 'add', entry: entryURL });
} else if (entry.dirent.isDirectory()) {
- events.push({ type: { name: 'addDir', entry: entryURL }, opts: { logLevel: 'warn' } });
+ events.push({ name: 'addDir', entry: entryURL });
}
}
await runEvents();
@@ -123,11 +110,8 @@ export async function createContentTypesGenerator({
}
async function handleEvent(
- event: ContentEvent,
- opts?: EventOpts
+ event: ContentEvent
): Promise<{ shouldGenerateTypes: boolean; error?: Error }> {
- const logLevel = opts?.logLevel ?? 'info';
-
if (event.name === 'addDir' || event.name === 'unlinkDir') {
const collection = normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
@@ -140,9 +124,7 @@ export async function createContentTypesGenerator({
switch (event.name) {
case 'addDir':
collectionEntryMap[JSON.stringify(collection)] = { type: 'unknown', entries: {} };
- if (logLevel === 'info') {
- logger.info('content', `${cyan(collection)} collection added`);
- }
+ logger.debug('content', `${cyan(collection)} collection added`);
break;
case 'unlinkDir':
if (collectionKey in collectionEntryMap) {
@@ -186,16 +168,14 @@ export async function createContentTypesGenerator({
const collection = getEntryCollectionName({ entry, contentDir });
if (collection === undefined) {
- if (['info', 'warn'].includes(logLevel)) {
- logger.warn(
- 'content',
- `${cyan(
- normalizePath(
- path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
- )
- )} must be nested in a collection directory. Skipping.`
- );
- }
+ logger.warn(
+ 'content',
+ `${bold(
+ normalizePath(
+ path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
+ )
+ )} must live in a ${bold('content/...')} collection subdirectory.`
+ );
return { shouldGenerateTypes: false };
}
@@ -308,22 +288,19 @@ export async function createContentTypesGenerator({
}
}
- function queueEvent(rawEvent: RawContentEvent, opts?: EventOpts) {
+ function queueEvent(rawEvent: RawContentEvent) {
const event = {
- type: {
- entry: pathToFileURL(rawEvent.entry),
- name: rawEvent.name,
- },
- opts,
+ entry: pathToFileURL(rawEvent.entry),
+ name: rawEvent.name,
};
- if (!event.type.entry.pathname.startsWith(contentPaths.contentDir.pathname)) return;
+ if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) return;
events.push(event);
debounceTimeout && clearTimeout(debounceTimeout);
const runEventsSafe = async () => {
try {
- await runEvents(opts);
+ await runEvents();
} catch {
// Prevent frontmatter errors from crashing the server. The errors
// are still reported on page reflects as desired.
@@ -333,30 +310,25 @@ export async function createContentTypesGenerator({
debounceTimeout = setTimeout(runEventsSafe, 50 /* debounce to batch chokidar events */);
}
- async function runEvents(opts?: EventOpts) {
- const logLevel = opts?.logLevel ?? 'info';
+ async function runEvents() {
const eventResponses = [];
for (const event of events) {
- const response = await handleEvent(event.type, event.opts);
+ const response = await handleEvent(event);
eventResponses.push(response);
}
events = [];
- let unsupportedFiles = [];
for (const response of eventResponses) {
if (response.error instanceof UnsupportedFileTypeError) {
- unsupportedFiles.push(response.error.message);
+ logger.warn(
+ 'content',
+ `Unsupported file type ${bold(
+ response.error.message
+ )} found. Prefix filename with an underscore (\`_\`) to ignore.`
+ );
}
}
- if (unsupportedFiles.length > 0 && ['info', 'warn'].includes(logLevel)) {
- logger.warn(
- 'content',
- `Unsupported file types found. Prefix with an underscore (\`_\`) to ignore:\n- ${unsupportedFiles.join(
- '\n'
- )}`
- );
- }
const observable = contentConfigObserver.get();
if (eventResponses.some((r) => r.shouldGenerateTypes)) {
await writeContentFiles({
@@ -369,7 +341,7 @@ export async function createContentTypesGenerator({
viteServer,
});
invalidateVirtualMod(viteServer);
- if (observable.status === 'loaded' && ['info', 'warn'].includes(logLevel)) {
+ if (observable.status === 'loaded') {
warnNonexistentCollections({
logger,
contentConfig: observable.config,
@@ -475,6 +447,7 @@ async function writeContentFiles({
let configPathRelativeToCacheDir = normalizePath(
path.relative(contentPaths.cacheDir.pathname, contentPaths.config.url.pathname)
);
+
if (!isRelativePath(configPathRelativeToCacheDir))
configPathRelativeToCacheDir = './' + configPathRelativeToCacheDir;
@@ -514,9 +487,9 @@ function warnNonexistentCollections({
if (!collectionEntryMap[JSON.stringify(configuredCollection)]) {
logger.warn(
'content',
- `The ${JSON.stringify(
- configuredCollection
- )} collection does not have an associated folder in your \`content\` directory. Make sure the folder exists, or check your content config for typos.`
+ `The ${bold(configuredCollection)} collection is defined but no ${bold(
+ 'content/' + configuredCollection
+ )} folder exists in the content directory. Create a new folder for the collection, or check your content configuration file for typos.`
);
}
}
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index ec7990119..4c6fb5783 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -190,7 +190,7 @@ export class App {
if (err instanceof EndpointNotFoundError) {
return this.#renderError(request, { status: 404, response: err.originalResponse });
} else {
- this.#logger.error('ssr', err.stack || err.message || String(err));
+ this.#logger.error(null, err.stack || err.message || String(err));
return this.#renderError(request, { status: 500 });
}
}
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 02837cf69..2beb85d62 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -1,5 +1,4 @@
-import * as colors from 'kleur/colors';
-import { bgGreen, black, cyan, dim, green, magenta } from 'kleur/colors';
+import { bgGreen, black, blue, bold, dim, green, magenta, red } from 'kleur/colors';
import fs from 'node:fs';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
@@ -149,7 +148,7 @@ export function chunkIsPage(
}
export async function generatePages(opts: StaticBuildOptions, internals: BuildInternals) {
- const timer = performance.now();
+ const generatePagesTimer = performance.now();
const ssr = isServerLikeOutput(opts.settings.config);
let manifest: SSRManifest;
if (ssr) {
@@ -179,7 +178,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
}
const verb = ssr ? 'prerendering' : 'generating';
- logger.info(null, `\n${bgGreen(black(` ${verb} static routes `))}`);
+ logger.info('SKIP_FORMAT', `\n${bgGreen(black(` ${verb} static routes `))}`);
const builtPaths = new Set<string>();
const pagesToGenerate = pipeline.retrieveRoutesToGenerate();
if (ssr) {
@@ -223,12 +222,14 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
}
}
}
-
- logger.info(null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
+ logger.info(
+ null,
+ green(`✓ Completed in ${getTimeStat(generatePagesTimer, performance.now())}.\n`)
+ );
const staticImageList = getStaticImageList();
if (staticImageList.size) {
- logger.info(null, `\n${bgGreen(black(` generating optimized images `))}`);
+ logger.info('SKIP_FORMAT', `${bgGreen(black(` generating optimized images `))}`);
const totalCount = Array.from(staticImageList.values())
.map((x) => x.transforms.size)
@@ -244,7 +245,10 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
await queue.onIdle();
const assetsTimeEnd = performance.now();
- logger.info(null, dim(`Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.\n`));
+ logger.info(
+ null,
+ green(`✓ Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.\n`)
+ );
delete globalThis?.astroAsset?.addStaticImage;
}
@@ -299,11 +303,11 @@ async function generatePage(
);
}
const pageModule = await pageModulePromise();
+ // TODO: Remove in Astro 4.0
if (shouldSkipDraft(pageModule, pipeline.getSettings())) {
logger.info(null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`);
- // TODO: Remove in Astro 4.0
logger.warn(
- 'astro',
+ null,
`The drafts feature is deprecated. You should migrate to content collections instead. See https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries for more information.`
);
return;
@@ -321,7 +325,7 @@ async function generatePage(
pageData.route.type === 'page' ||
pageData.route.type === 'redirect' ||
pageData.route.type === 'fallback'
- ? green('▶')
+ ? blue('▶')
: magenta('λ');
if (isRelativePath(pageData.route.component)) {
logger.info(null, `${icon} ${pageData.route.route}`);
@@ -338,10 +342,10 @@ async function generatePage(
await generatePath(path, generationOptions, pipeline);
const timeEnd = performance.now();
const timeChange = getTimeStat(prevTimeEnd, timeEnd);
- const timeIncrease = `(+${timeChange})`;
+ const timeIncrease = `(${timeChange})`;
const filePath = getOutputFilename(pipeline.getConfig(), path, pageData.route.type);
const lineIcon = i === paths.length - 1 ? '└─' : '├─';
- logger.info(null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
+ logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
prevTimeEnd = timeEnd;
}
}
@@ -367,14 +371,14 @@ async function getPathsForRoute(
logger,
ssr: isServerLikeOutput(opts.settings.config),
}).catch((err) => {
- logger.debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
+ logger.debug('build', `├── ${bold(red('✗'))} ${route.component}`);
throw err;
});
const label = staticPaths.length === 1 ? 'page' : 'pages';
logger.debug(
'build',
- `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.magenta(
+ `├── ${bold(green('✔'))} ${route.component} → ${magenta(
`[${staticPaths.length} ${label}]`
)}`
);
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index f096b8f76..19f263a01 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,4 +1,4 @@
-import * as colors from 'kleur/colors';
+import { blue, bold, green } from 'kleur/colors';
import fs from 'node:fs';
import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
@@ -69,8 +69,9 @@ export default async function build(
if (astroConfig.experimental.contentCollectionCache && options.force) {
const contentCacheDir = new URL('./content/', astroConfig.cacheDir);
if (fs.existsSync(contentCacheDir)) {
- logger.warn('content', 'clearing cache');
+ logger.debug('content', 'clearing content cache');
await fs.promises.rm(contentCacheDir, { force: true, recursive: true });
+ logger.warn('content', 'content cache cleared (force)');
}
}
@@ -157,9 +158,10 @@ class AstroBuilder {
await runHookBuildStart({ config: this.settings.config, logging: this.logger });
this.validateConfig();
- this.logger.info('build', `output target: ${colors.green(this.settings.config.output)}`);
+ this.logger.info('build', `output: ${blue('"' + this.settings.config.output + '"')}`);
+ this.logger.info('build', `directory: ${blue(fileURLToPath(this.settings.config.outDir))}`);
if (this.settings.adapter) {
- this.logger.info('build', `deploy adapter: ${colors.green(this.settings.adapter.name)}`);
+ this.logger.info('build', `adapter: ${green(this.settings.adapter.name)}`);
}
this.logger.info('build', 'Collecting build info...');
this.timer.loadStart = performance.now();
@@ -179,7 +181,7 @@ class AstroBuilder {
this.timer.buildStart = performance.now();
this.logger.info(
'build',
- colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
+ green(`✓ Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
);
const opts: StaticBuildOptions = {
@@ -252,27 +254,28 @@ class AstroBuilder {
);
}
+ // TODO: Remove in Astro 4.0
if (config.build.split === true) {
if (config.output === 'static') {
this.logger.warn(
- 'configuration',
+ 'config',
'The option `build.split` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
);
}
this.logger.warn(
- 'configuration',
+ 'deprecated',
'The option `build.split` is deprecated. Use the adapter options.'
);
}
if (config.build.excludeMiddleware === true) {
if (config.output === 'static') {
this.logger.warn(
- 'configuration',
+ 'config',
'The option `build.excludeMiddleware` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
);
}
this.logger.warn(
- 'configuration',
+ 'deprecated',
'The option `build.excludeMiddleware` is deprecated. Use the adapter options.'
);
}
@@ -294,12 +297,12 @@ class AstroBuilder {
let messages: string[] = [];
if (buildMode === 'static') {
- messages = [`${pageCount} page(s) built in`, colors.bold(total)];
+ messages = [`${pageCount} page(s) built in`, bold(total)];
} else {
- messages = ['Server built in', colors.bold(total)];
+ messages = ['Server built in', bold(total)];
}
logger.info('build', messages.join(' '));
- logger.info('build', `${colors.bold('Complete!')}`);
+ logger.info('build', `${bold('Complete!')}`);
}
}
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 81dcdb4a0..95404c6d6 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -1,7 +1,7 @@
import { teardown } from '@astrojs/compiler';
import * as eslexer from 'es-module-lexer';
import glob from 'fast-glob';
-import { bgGreen, bgMagenta, black, dim } from 'kleur/colors';
+import { bgGreen, bgMagenta, black, green } from 'kleur/colors';
import fs from 'node:fs';
import path, { extname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -80,7 +80,8 @@ export async function viteBuild(opts: StaticBuildOptions) {
const ssrTime = performance.now();
opts.logger.info('build', `Building ${settings.config.output} entrypoints...`);
const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
- opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
+ opts.logger.info('build', green(`✓ Completed in ${getTimeStat(ssrTime, performance.now())}.`));
+
settings.timer.end('SSR build');
settings.timer.start('Client build');
@@ -272,7 +273,6 @@ async function clientBuild(
container: AstroBuildPluginContainer
) {
const { settings, viteConfig } = opts;
- const timer = performance.now();
const ssr = isServerLikeOutput(settings.config);
const out = ssr ? settings.config.build.client : getOutDirWithinCwd(settings.config.outDir);
@@ -287,7 +287,7 @@ async function clientBuild(
}
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('client', input);
- opts.logger.info(null, `\n${bgGreen(black(' building client '))}`);
+ opts.logger.info('SKIP_FORMAT', `\n${bgGreen(black(' building client (vite) '))}`);
const viteBuildConfig: vite.InlineConfig = {
...viteConfig,
@@ -326,7 +326,6 @@ async function clientBuild(
});
const buildResult = await vite.build(viteBuildConfig);
- opts.logger.info(null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
return buildResult;
}
diff --git a/packages/astro/src/core/build/util.ts b/packages/astro/src/core/build/util.ts
index e46a0713a..fc12b486f 100644
--- a/packages/astro/src/core/build/util.ts
+++ b/packages/astro/src/core/build/util.ts
@@ -2,7 +2,7 @@ import type { AstroConfig } from '../../@types/astro.js';
export function getTimeStat(timeStart: number, timeEnd: number) {
const buildTime = timeEnd - timeStart;
- return buildTime < 750 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
+ return buildTime < 1000 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
}
/**
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index de6443729..2b677c1d9 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -1,10 +1,9 @@
-import type { AstroSettings } from '../@types/astro.js';
-import type { Logger } from './logger/core.js';
-
import nodeFs from 'node:fs';
import { fileURLToPath } from 'node:url';
+import type { Logger as ViteLogger } from 'vite';
import * as vite from 'vite';
import { crawlFrameworkPkgs } from 'vitefu';
+import type { AstroSettings } from '../@types/astro.js';
import astroAssetsPlugin from '../assets/vite-plugin-assets.js';
import {
astroContentAssetPropagationPlugin,
@@ -31,6 +30,7 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
+import type { Logger } from './logger/core.js';
import { vitePluginMiddleware } from './middleware/vite-plugin.js';
import { joinPaths } from './path.js';
@@ -102,6 +102,17 @@ export async function createVite(
},
});
+ const viteCustomLogger: ViteLogger = {
+ ...vite.createLogger('warn'),
+ // All error log messages are also thrown as real errors,
+ // so we can safely ignore them here and let the error handler
+ // log them for the user instead.
+ error: (msg) => logger.debug('vite', 'ERROR ' + msg),
+ // Warnings are usually otherwise ignored by Vite, so it's
+ // important that we catch and log them here.
+ warn: (msg) => logger.warn('vite', msg),
+ };
+
// Start with the Vite configuration that Astro core needs
const commonConfig: vite.InlineConfig = {
// Tell Vite not to combine config from vite.config.js with our provided inline config
@@ -109,6 +120,7 @@ export async function createVite(
cacheDir: fileURLToPath(new URL('./node_modules/.vite/', settings.config.root)), // using local caches allows Astro to be used in monorepos, etc.
clearScreen: false, // we want to control the output, not Vite
logLevel: 'warn', // log warnings and errors only
+ customLogger: viteCustomLogger,
appType: 'custom',
optimizeDeps: {
entries: ['src/**/*'],
diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts
index 02ba9d872..f8db6647e 100644
--- a/packages/astro/src/core/dev/dev.ts
+++ b/packages/astro/src/core/dev/dev.ts
@@ -9,6 +9,7 @@ import { telemetry } from '../../events/index.js';
import * as msg from '../messages.js';
import { startContainer } from './container.js';
import { createContainerWithAutomaticRestart } from './restart.js';
+import { green } from 'kleur/colors';
export interface DevServer {
address: AddressInfo;
@@ -33,9 +34,8 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
// Start listening to the port
const devServerAddressInfo = await startContainer(restart.container);
-
logger.info(
- null,
+ 'SKIP_FORMAT',
msg.serverStart({
startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
@@ -43,17 +43,20 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
base: restart.container.settings.config.base,
})
);
+
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
if (currentVersion.includes('-')) {
- logger.warn(null, msg.prerelease({ currentVersion }));
+ logger.warn('SKIP_FORMAT', msg.prerelease({ currentVersion }));
}
if (restart.container.viteServer.config.server?.fs?.strict === false) {
- logger.warn(null, msg.fsStrictWarning());
+ logger.warn('SKIP_FORMAT', msg.fsStrictWarning());
}
await attachContentServerListeners(restart.container);
+ logger.info(null, green('watching for file changes...'));
+
return {
address: devServerAddressInfo,
get watcher() {
diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts
index f03db39cf..137dde5df 100644
--- a/packages/astro/src/core/dev/restart.ts
+++ b/packages/astro/src/core/dev/restart.ts
@@ -82,7 +82,7 @@ export async function restartContainer(container: Container): Promise<Container
},
});
container.restartInFlight = false;
- logger.error('astro', 'Continuing with previous valid configuration\n');
+ logger.error(null, 'Continuing with previous valid configuration\n');
return error;
}
}
@@ -121,8 +121,8 @@ export async function createContainerWithAutomaticRestart({
},
};
- async function handleServerRestart(logMsg: string) {
- logger.info('astro', logMsg + '\n');
+ async function handleServerRestart(logMsg = '') {
+ logger.info(null, (logMsg + ' Restarting...').trim());
const container = restart.container;
const result = await restartContainer(container);
if (result instanceof Error) {
@@ -150,13 +150,13 @@ export async function createContainerWithAutomaticRestart({
// Set up watches
function addWatches() {
const watcher = restart.container.viteServer.watcher;
- watcher.on('change', handleChangeRestart('Configuration updated. Restarting...'));
- watcher.on('unlink', handleChangeRestart('Configuration removed. Restarting...'));
- watcher.on('add', handleChangeRestart('Configuration added. Restarting...'));
+ watcher.on('change', handleChangeRestart('Configuration file updated.'));
+ watcher.on('unlink', handleChangeRestart('Configuration file removed.'));
+ watcher.on('add', handleChangeRestart('Configuration file added.'));
// Restart the Astro dev server instead of Vite's when the API is called by plugins.
// Ignore the `forceOptimize` parameter for now.
- restart.container.viteServer.restart = () => handleServerRestart('Restarting...');
+ restart.container.viteServer.restart = () => handleServerRestart();
}
addWatches();
return restart;
diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts
index 33c659dca..d5484f0df 100644
--- a/packages/astro/src/core/endpoint/index.ts
+++ b/packages/astro/src/core/endpoint/index.ts
@@ -184,7 +184,7 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
if (response instanceof Response) {
if (isEndpointSSR && response.headers.get('X-Astro-Encoding')) {
env.logger.warn(
- 'ssr',
+ null,
'`ResponseWithEncoding` is ignored in SSR. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
@@ -196,21 +196,21 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
// TODO: Remove in Astro 4.0
env.logger.warn(
- 'astro',
+ null,
`${ctx.route.component} returns a simple object which is deprecated. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.`
);
if (isEndpointSSR) {
if (response.hasOwnProperty('headers')) {
env.logger.warn(
- 'ssr',
+ null,
'Setting headers is not supported when returning an object. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
if (response.encoding) {
env.logger.warn(
- 'ssr',
+ null,
'`encoding` is ignored in SSR. To return a charset other than UTF-8, please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts
index b3e49234d..7d67806f8 100644
--- a/packages/astro/src/core/errors/dev/vite.ts
+++ b/packages/astro/src/core/errors/dev/vite.ts
@@ -83,7 +83,6 @@ export function enhanceViteSSRError({
if (globPattern) {
safeError.message = InvalidGlob.message(globPattern);
safeError.name = 'InvalidGlob';
- safeError.hint = InvalidGlob.hint;
safeError.title = InvalidGlob.title;
const line = lns.findIndex((ln) => ln.includes(globPattern));
diff --git a/packages/astro/src/core/logger/console.ts b/packages/astro/src/core/logger/console.ts
index f39f6b74d..d55318d4c 100644
--- a/packages/astro/src/core/logger/console.ts
+++ b/packages/astro/src/core/logger/console.ts
@@ -1,10 +1,6 @@
-import { bold, cyan, dim, red, reset, yellow } from 'kleur/colors';
-import type { LogMessage } from './core.js';
-import { dateTimeFormat, levels } from './core.js';
+import { getEventPrefix, levels, type LogMessage, type LogWritable } from './core.js';
-let lastMessage: string;
-let lastMessageCount = 1;
-export const consoleLogDestination = {
+export const consoleLogDestination: LogWritable<LogMessage> = {
write(event: LogMessage) {
// eslint-disable-next-line no-console
let dest = console.error;
@@ -12,37 +8,11 @@ export const consoleLogDestination = {
// eslint-disable-next-line no-console
dest = console.log;
}
-
- function getPrefix() {
- let prefix = '';
- let type = event.label;
- if (type) {
- // hide timestamp when type is undefined
- prefix += dim(dateTimeFormat.format(new Date()) + ' ');
- if (event.level === 'info') {
- type = bold(cyan(`[${type}]`));
- } else if (event.level === 'warn') {
- type = bold(yellow(`[${type}]`));
- } else if (event.level === 'error') {
- type = bold(red(`[${type}]`));
- }
-
- prefix += `${type} `;
- }
- return reset(prefix);
- }
-
- let message = event.message;
- // For repeat messages, only update the message counter
- if (message === lastMessage) {
- lastMessageCount++;
- message = `${message} ${yellow(`(x${lastMessageCount})`)}`;
+ if (event.label === 'SKIP_FORMAT') {
+ dest(event.message);
} else {
- lastMessage = message;
- lastMessageCount = 1;
+ dest(getEventPrefix(event) + ' ' + event.message);
}
- const outMessage = getPrefix() + message;
- dest(outMessage);
return true;
},
};
diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts
index 11804dd01..5d617a1a2 100644
--- a/packages/astro/src/core/logger/core.ts
+++ b/packages/astro/src/core/logger/core.ts
@@ -1,12 +1,34 @@
-import { dim } from 'kleur/colors';
+import { blue, bold, dim, red, yellow } from 'kleur/colors';
import stringWidth from 'string-width';
-interface LogWritable<T> {
+export interface LogWritable<T> {
write: (chunk: T) => boolean;
}
export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
+/**
+ * Defined logger labels. Add more as needed, but keep them high-level & reusable,
+ * rather than specific to a single command, function, use, etc. The label will be
+ * shown in the log message to the user, so it should be relevant.
+ */
+export type LoggerLabel =
+ | 'add'
+ | 'build'
+ | 'check'
+ | 'config'
+ | 'content'
+ | 'deprecated'
+ | 'markdown'
+ | 'router'
+ | 'types'
+ | 'vite'
+ | 'watch'
+ | 'middleware'
+ // SKIP_FORMAT: A special label that tells the logger not to apply any formatting.
+ // Useful for messages that are already formatted, like the server start message.
+ | 'SKIP_FORMAT';
+
export interface LogOptions {
dest: LogWritable<LogMessage>;
level: LoggerLevel;
@@ -25,6 +47,7 @@ export const dateTimeFormat = new Intl.DateTimeFormat([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
+ hour12: false,
});
export interface LogMessage {
@@ -98,6 +121,35 @@ function padStr(str: string, len: number) {
return str + spaces;
}
+/**
+ * Get the prefix for a log message.
+ * This includes the timestamp, log level, and label all properly formatted
+ * with colors. This is shared across different loggers, so it's defined here.
+ */
+export function getEventPrefix({ level, label }: LogMessage) {
+ const timestamp = `${dateTimeFormat.format(new Date())}`;
+ const prefix = [];
+ if (level === 'error' || level === 'warn') {
+ prefix.push(bold(timestamp));
+ prefix.push(`[${level.toUpperCase()}]`);
+ } else {
+ prefix.push(timestamp);
+ }
+ if (label) {
+ prefix.push(`[${label}]`);
+ }
+ if (level === 'error') {
+ return red(prefix.join(' '));
+ }
+ if (level === 'warn') {
+ return yellow(prefix.join(' '));
+ }
+ if (prefix.length === 1) {
+ return dim(prefix[0]);
+ }
+ return dim(prefix[0]) + ' ' + blue(prefix.splice(1).join(' '));
+}
+
export let defaultLogLevel: LoggerLevel;
if (typeof process !== 'undefined') {
// This could be a shimmed environment so we don't know that `process` is the full
@@ -133,16 +185,16 @@ export class Logger {
this.options = options;
}
- info(label: string | null, message: string) {
+ info(label: LoggerLabel | null, message: string) {
info(this.options, label, message);
}
- warn(label: string | null, message: string) {
+ warn(label: LoggerLabel | null, message: string) {
warn(this.options, label, message);
}
- error(label: string | null, message: string) {
+ error(label: LoggerLabel | null, message: string) {
error(this.options, label, message);
}
- debug(label: string | null, ...messages: any[]) {
+ debug(label: LoggerLabel, ...messages: any[]) {
debug(label, ...messages);
}
diff --git a/packages/astro/src/core/logger/node.ts b/packages/astro/src/core/logger/node.ts
index 57aa59ed0..801df32f5 100644
--- a/packages/astro/src/core/logger/node.ts
+++ b/packages/astro/src/core/logger/node.ts
@@ -1,113 +1,35 @@
import debugPackage from 'debug';
-import { bold, cyan, dim, red, reset, yellow } from 'kleur/colors';
-import * as readline from 'node:readline';
import { Writable } from 'node:stream';
-import stringWidth from 'string-width';
-import { dateTimeFormat, error, info, warn } from './core.js';
+import { getEventPrefix, levels, type LogMessage, type LogWritable } from './core.js';
type ConsoleStream = Writable & {
fd: 1 | 2;
};
-let lastMessage: string;
-let lastMessageCount = 1;
-export const nodeLogDestination = new Writable({
- objectMode: true,
- write(event: LogMessage, _, callback) {
+export const nodeLogDestination: LogWritable<LogMessage> = {
+ write(event: LogMessage) {
let dest: ConsoleStream = process.stderr;
if (levels[event.level] < levels['error']) {
dest = process.stdout;
}
-
- function getPrefix() {
- let prefix = '';
- let label = event.label;
- if (label) {
- // hide timestamp when type is undefined
- prefix += dim(dateTimeFormat.format(new Date()) + ' ');
- if (event.level === 'info') {
- label = bold(cyan(`[${label}]`));
- } else if (event.level === 'warn') {
- label = bold(yellow(`[${label}]`));
- } else if (event.level === 'error') {
- label = bold(red(`[${label}]`));
- }
-
- prefix += `${label} `;
- }
- return reset(prefix);
- }
-
- // console.log({msg: event.message, args: event.args});
- let message = event.message;
- // For repeat messages, only update the message counter
- if (message === lastMessage) {
- lastMessageCount++;
- if (levels[event.level] < levels['error']) {
- let lines = 1;
- let len = stringWidth(`${getPrefix()}${message}`);
- let cols = (dest as unknown as typeof process.stdout).columns;
- if (len > cols) {
- lines = Math.ceil(len / cols);
- }
- for (let i = 0; i < lines; i++) {
- readline.clearLine(dest, 0);
- readline.cursorTo(dest, 0);
- readline.moveCursor(dest, 0, -1);
- }
- }
- message = `${message} ${yellow(`(x${lastMessageCount})`)}`;
+ if (event.label === 'SKIP_FORMAT') {
+ dest.write(event.message + '\n');
} else {
- lastMessage = message;
- lastMessageCount = 1;
+ dest.write(getEventPrefix(event) + ' ' + event.message + '\n');
}
-
- dest.write(getPrefix());
- dest.write(message);
- dest.write('\n');
- callback();
+ return true;
},
-});
-
-interface LogWritable<T> {
- write: (chunk: T) => boolean;
-}
-
-export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
-export type LoggerEvent = 'info' | 'warn' | 'error';
-
-export interface LogOptions {
- dest?: LogWritable<LogMessage>;
- level?: LoggerLevel;
-}
-
-export const nodeLogOptions: Required<LogOptions> = {
- dest: nodeLogDestination,
- level: 'info',
-};
-
-export interface LogMessage {
- label: string | null;
- level: LoggerLevel;
- message: string;
-}
-
-export const levels: Record<LoggerLevel, number> = {
- debug: 20,
- info: 30,
- warn: 40,
- error: 50,
- silent: 90,
};
const debuggers: Record<string, debugPackage.Debugger['log']> = {};
+
/**
* Emit a message only shown in debug mode.
* Astro (along with many of its dependencies) uses the `debug` package for debug logging.
* You can enable these logs with the `DEBUG=astro:*` environment variable.
* More info https://github.com/debug-js/debug#environment-variables
*/
-export function debug(type: string, ...messages: Array<any>) {
+function debug(type: string, ...messages: Array<any>) {
const namespace = `astro:${type}`;
debuggers[namespace] = debuggers[namespace] || debugPackage(namespace);
return debuggers[namespace](...messages);
@@ -116,13 +38,6 @@ export function debug(type: string, ...messages: Array<any>) {
// This is gross, but necessary since we are depending on globals.
(globalThis as any)._astroGlobalDebug = debug;
-// A default logger for when too lazy to pass LogOptions around.
-export const logger = {
- info: info.bind(null, nodeLogOptions),
- warn: warn.bind(null, nodeLogOptions),
- error: error.bind(null, nodeLogOptions),
-};
-
export function enableVerboseLogging() {
debugPackage.enable('*,-babel');
debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"');
diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts
index 758f5e581..7c1d663c5 100644
--- a/packages/astro/src/core/messages.ts
+++ b/packages/astro/src/core/messages.ts
@@ -1,10 +1,10 @@
import {
- bgCyan,
bgGreen,
bgRed,
bgWhite,
bgYellow,
black,
+ blue,
bold,
cyan,
dim,
@@ -22,36 +22,29 @@ import {
CompilerError,
type ErrorWithMetadata,
} from './errors/index.js';
-import { emoji, padMultilineString } from './util.js';
-
-const PREFIX_PADDING = 6;
+import { padMultilineString } from './util.js';
/** Display */
export function req({
url,
+ method,
statusCode,
reqTime,
}: {
url: string;
statusCode: number;
+ method?: string;
reqTime?: number;
}): string {
- let color = dim;
- if (statusCode >= 500) color = red;
- else if (statusCode >= 400) color = yellow;
- else if (statusCode >= 300) color = dim;
- else if (statusCode >= 200) color = green;
- return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${
- reqTime ? dim(Math.round(reqTime) + 'ms') : ''
- }`.trim();
-}
-
-export function reload({ file }: { file: string }): string {
- return `${green('reload'.padStart(PREFIX_PADDING))} ${file}`;
-}
-
-export function hmr({ file, style = false }: { file: string; style?: boolean }): string {
- return `${green('update'.padStart(PREFIX_PADDING))} ${file}${style ? ` ${dim('style')}` : ''}`;
+ const color = statusCode >= 400 ? red : statusCode >= 300 ? yellow : blue;
+ return (
+ color(`[${statusCode}]`) +
+ ` ` +
+ (method && method !== 'GET' ? color(method) + ' ' : '') +
+ url +
+ ` ` +
+ (reqTime ? dim(Math.round(reqTime) + 'ms') : '')
+ );
}
/** Display server host and startup time */
@@ -60,13 +53,11 @@ export function serverStart({
resolvedUrls,
host,
base,
- isRestart = false,
}: {
startupTime: number;
resolvedUrls: ResolvedServerUrls;
host: string | boolean;
base: string;
- isRestart?: boolean;
}): string {
// PACKAGE_VERSION is injected at build-time
const version = process.env.PACKAGE_VERSION ?? '0.0.0';
@@ -75,10 +66,10 @@ export function serverStart({
const emptyPrefix = ' '.repeat(11);
const localUrlMessages = resolvedUrls.local.map((url, i) => {
- return `${i === 0 ? localPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
+ return `${i === 0 ? localPrefix : emptyPrefix}${cyan(new URL(url).origin + base)}`;
});
const networkUrlMessages = resolvedUrls.network.map((url, i) => {
- return `${i === 0 ? networkPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
+ return `${i === 0 ? networkPrefix : emptyPrefix}${cyan(new URL(url).origin + base)}`;
});
if (networkUrlMessages.length === 0) {
@@ -91,58 +82,58 @@ export function serverStart({
}
const messages = [
- `${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(
- `${isRestart ? 're' : ''}started in ${Math.round(startupTime)}ms`
- )}`,
+ '',
+ `${bgGreen(bold(` astro `))} ${green(`v${version}`)} ${dim(`ready in`)} ${Math.round(
+ startupTime
+ )} ${dim('ms')}`,
'',
...localUrlMessages,
...networkUrlMessages,
'',
];
- return messages
- .filter((msg) => typeof msg === 'string')
- .map((msg) => ` ${msg}`)
- .join('\n');
+ return messages.filter((msg) => typeof msg === 'string').join('\n');
}
-export function telemetryNotice(packageManager = 'npm') {
- const headline = `${cyan('◆')} Astro collects completely anonymous usage data.`;
- const why = dim(' This optional program helps shape our roadmap.');
- const disable = dim(` Run \`${packageManager} run astro telemetry disable\` to opt-out.`);
- const details = ` Details: ${underline('https://astro.build/telemetry')}`;
- return [headline, why, disable, details].map((v) => ' ' + v).join('\n');
+export function telemetryNotice() {
+ const headline = blue(`▶ Astro collects anonymous usage data.`);
+ const why = ' This information helps us improve Astro.';
+ const disable = ` Run "astro telemetry disable" to opt-out.`;
+ const details = ` ${cyan(underline('https://astro.build/telemetry'))}`;
+ return [headline, why, disable, details].join('\n');
}
export function telemetryEnabled() {
- return `${green('◉')} Anonymous telemetry is now ${bgGreen(black(' enabled '))}\n ${dim(
- 'Thank you for improving Astro!'
- )}\n`;
+ return [
+ green('▶ Anonymous telemetry ') + bgGreen(' enabled '),
+ ` Thank you for helping us improve Astro!`,
+ ``,
+ ].join('\n');
}
export function telemetryDisabled() {
- return `${yellow('◯')} Anonymous telemetry is now ${bgYellow(black(' disabled '))}\n ${dim(
- "We won't ever record your usage data."
- )}\n`;
+ return [
+ green('▶ Anonymous telemetry ') + bgGreen(' disabled '),
+ ` Astro is no longer collecting anonymous usage data.`,
+ ``,
+ ].join('\n');
}
export function telemetryReset() {
- return `${cyan('◆')} Anonymous telemetry has been ${bgCyan(black(' reset '))}\n ${dim(
- 'You may be prompted again.'
- )}\n`;
+ return [green('▶ Anonymous telemetry preferences reset.'), ``].join('\n');
}
export function fsStrictWarning() {
- return yellow(
- '⚠️ Serving with vite.server.fs.strict: false. Note that all files on your machine will be accessible to anyone on your network!'
- );
+ const title = yellow('▶ ' + `${bold('vite.server.fs.strict')} has been disabled!`);
+ const subtitle = ` Files on your machine are likely accessible on your network.`;
+ return `${title}\n${subtitle}\n`;
}
export function prerelease({ currentVersion }: { currentVersion: string }) {
- const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '');
+ const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '') || 'unknown';
const badge = bgYellow(black(` ${tag} `));
- const headline = yellow(`▶ This is a ${badge} prerelease build`);
- const warning = ` Feedback? ${underline('https://astro.build/issues')}`;
- return [headline, warning, ''].map((msg) => ` ${msg}`).join('\n');
+ const title = yellow('▶ ' + `This is a ${badge} prerelease build!`);
+ const subtitle = ` Report issues here: ${cyan(underline('https://astro.build/issues'))}`;
+ return `${title}\n${subtitle}\n`;
}
export function success(message: string, tip?: string) {
@@ -196,6 +187,7 @@ export function formatConfigErrorMessage(err: ZodError) {
)}`;
}
+
export function formatErrorMessage(err: ErrorWithMetadata, args: string[] = []): string {
const isOurError = AstroError.is(err) || CompilerError.is(err) || AstroUserError.is(err);
diff --git a/packages/astro/src/core/middleware/callMiddleware.ts b/packages/astro/src/core/middleware/callMiddleware.ts
index 40513c152..d0a6ceca2 100644
--- a/packages/astro/src/core/middleware/callMiddleware.ts
+++ b/packages/astro/src/core/middleware/callMiddleware.ts
@@ -62,8 +62,9 @@ export async function callMiddleware<R>(
return await Promise.resolve(middlewarePromise).then(async (value) => {
if (isEndpointOutput(value)) {
logger.warn(
- 'middleware',
- 'Using simple endpoints can cause unexpected issues in the chain of middleware functions.' +
+ null,
+ apiContext.url.pathname +
+ ' Using simple endpoints can cause unexpected issues in the chain of middleware functions.' +
`\nIt's strongly suggested to use full ${bold('Response')} objects.`
);
}
diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts
index 68a700b6d..ba692611e 100644
--- a/packages/astro/src/core/preview/static-preview-server.ts
+++ b/packages/astro/src/core/preview/static-preview-server.ts
@@ -42,7 +42,7 @@ export default async function createStaticPreviewServer(
});
} catch (err) {
if (err instanceof Error) {
- logger.error('astro', err.stack || err.message);
+ logger.error(null, err.stack || err.message);
}
throw err;
}
@@ -51,7 +51,7 @@ export default async function createStaticPreviewServer(
// Log server start URLs
logger.info(
- null,
+ 'SKIP_FORMAT',
msg.serverStart({
startupTime: performance.now() - startServerTime,
resolvedUrls: previewServer.resolvedUrls ?? { local: [], network: [] },
diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts
index da9675f10..a3235003f 100644
--- a/packages/astro/src/core/render/core.ts
+++ b/packages/astro/src/core/render/core.ts
@@ -65,7 +65,7 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag
// TODO: Remove in Astro 4.0
if (mod.frontmatter && typeof mod.frontmatter === 'object' && 'draft' in mod.frontmatter) {
env.logger.warn(
- 'astro',
+ null,
`The drafts feature is deprecated and used in ${renderContext.route.component}. You should migrate to content collections instead. See https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries for more information.`
);
}
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index 91dc545df..f4a1b0769 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -94,7 +94,7 @@ class Slots {
const result = this.#result;
if (!Array.isArray(args)) {
this.#logger.warn(
- 'Astro.slots.render',
+ null,
`Expected second parameter to be an array, received a ${typeof args}. If you're trying to pass an array as a single argument and getting unexpected results, make sure you're passing your array as a item of an array. Ex: Astro.slots.render('default', [["Hello", "World"]])`
);
} else if (args.length > 0) {
diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts
index c318b8c44..322326957 100644
--- a/packages/astro/src/core/render/route-cache.ts
+++ b/packages/astro/src/core/render/route-cache.ts
@@ -108,7 +108,7 @@ export class RouteCache {
// isn't invisible and developer can track down the issue.
if (this.mode === 'production' && this.cache[route.component]?.staticPaths) {
this.logger.warn(
- 'routeCache',
+ null,
`Internal Warning: route cache overwritten. (${route.component})`
);
}
@@ -131,5 +131,5 @@ export function findPathItemByKey(
if (matchedStaticPath) {
return matchedStaticPath;
}
- logger.debug('findPathItemByKey', `Unexpected cache miss looking for ${paramsKey}`);
+ logger.debug('router', `findPathItemByKey() - Unexpected cache miss looking for ${paramsKey}`);
}
diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts
index f478b0a32..6d55caa15 100644
--- a/packages/astro/src/core/request.ts
+++ b/packages/astro/src/core/request.ts
@@ -42,7 +42,7 @@ export function createRequest({
Object.defineProperties(request, {
params: {
get() {
- logger.warn('deprecation', `Astro.request.params has been moved to Astro.params`);
+ logger.warn('deprecated', `Astro.request.params has been moved to Astro.params`);
return undefined;
},
},
@@ -56,8 +56,8 @@ export function createRequest({
...headersDesc,
get() {
logger.warn(
- 'ssg',
- `Headers are not exposed in static (SSG) output mode. To enable headers: set \`output: "server"\` in your config file.`
+ null,
+ `\`Astro.request.headers\` is not available in "static" output mode. To enable header access: set \`output: "server"\` or \`output: "hybrid"\` in your config file.`
);
return _headers;
},
diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts
index 6a57972e0..ded3e13a8 100644
--- a/packages/astro/src/core/routing/manifest/create.ts
+++ b/packages/astro/src/core/routing/manifest/create.ts
@@ -8,6 +8,7 @@ import type {
} from '../../../@types/astro.js';
import type { Logger } from '../../logger/core.js';
+import { bold } from 'kleur/colors';
import { createRequire } from 'module';
import nodeFs from 'node:fs';
import path from 'node:path';
@@ -234,8 +235,6 @@ export function createRouteManifest(
const localFs = fsMod ?? nodeFs;
const prerender = getPrerenderDefault(settings.config);
- const foundInvalidFileExtensions = new Set<string>();
-
function walk(
fs: typeof nodeFs,
dir: string,
@@ -259,10 +258,12 @@ export function createRouteManifest(
}
// filter out "foo.astro_tmp" files, etc
if (!isDir && !validPageExtensions.has(ext) && !validEndpointExtensions.has(ext)) {
- if (!foundInvalidFileExtensions.has(ext)) {
- foundInvalidFileExtensions.add(ext);
- logger.warn('astro', `Invalid file extension for Pages: ${ext}`);
- }
+ logger.warn(
+ null,
+ `Unsupported file type ${bold(
+ resolved
+ )} found. Prefix filename with an underscore (\`_\`) to ignore.`
+ );
return;
}
@@ -358,8 +359,7 @@ export function createRouteManifest(
walk(localFs, fileURLToPath(pages), [], []);
} else if (settings.injectedRoutes.length === 0) {
const pagesDirRootRelative = pages.href.slice(settings.config.root.href.length);
-
- logger.warn('astro', `Missing pages directory: ${pagesDirRootRelative}`);
+ logger.warn(null, `Missing pages directory: ${pagesDirRootRelative}`);
}
settings.injectedRoutes
diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts
index 0261c865a..b68d5f3e8 100644
--- a/packages/astro/src/core/routing/validation.ts
+++ b/packages/astro/src/core/routing/validation.ts
@@ -79,16 +79,16 @@ export function validateGetStaticPathsResult(
for (const [key, val] of Object.entries(pathObject.params)) {
if (!(typeof val === 'undefined' || typeof val === 'string' || typeof val === 'number')) {
logger.warn(
- 'getStaticPaths',
- `invalid path param: ${key}. A string, number or undefined value was expected, but got \`${JSON.stringify(
+ 'router',
+ `getStaticPaths() returned an invalid path param: "${key}". A string, number or undefined value was expected, but got \`${JSON.stringify(
val
)}\`.`
);
}
if (typeof val === 'string' && val === '') {
logger.warn(
- 'getStaticPaths',
- `invalid path param: ${key}. \`undefined\` expected for an optional param, but got empty string.`
+ 'router',
+ `getStaticPaths() returned an invalid path param: "${key}". \`undefined\` expected for an optional param, but got empty string.`
);
}
}
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index 0c7b81c3a..966454845 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -117,7 +117,7 @@ export async function syncInternal(
switch (typesResult.reason) {
case 'no-content-dir':
default:
- logger.info('content', 'No content directory found. Skipping type generation.');
+ logger.debug('types', 'No content directory found. Skipping type generation.');
return 0;
}
}
@@ -137,7 +137,7 @@ export async function syncInternal(
await tempViteServer.close();
}
- logger.info('content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
+ logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
await setUpEnvTs({ settings, logger, fs: fs ?? fsMod });
return 0;
diff --git a/packages/astro/src/integrations/astroFeaturesValidation.ts b/packages/astro/src/integrations/astroFeaturesValidation.ts
index a26f42afb..d9049f876 100644
--- a/packages/astro/src/integrations/astroFeaturesValidation.ts
+++ b/packages/astro/src/integrations/astroFeaturesValidation.ts
@@ -106,17 +106,17 @@ function validateSupportKind(
function featureIsUnsupported(adapterName: string, logger: Logger, featureName: string) {
logger.error(
- `${adapterName}`,
- `The feature ${featureName} is not supported by the adapter ${adapterName}.`
+ 'config',
+ `The feature ${featureName} is not supported (used by ${adapterName}).`
);
}
function featureIsExperimental(adapterName: string, logger: Logger) {
- logger.warn(`${adapterName}`, 'The feature is experimental and subject to issues or changes.');
+ logger.warn('config', `The feature is experimental and subject to change (used by ${adapterName}).`);
}
function featureIsDeprecated(adapterName: string, logger: Logger) {
- logger.warn(`${adapterName}`, 'The feature is deprecated and will be moved in the next release.');
+ logger.warn('config', `The feature is deprecated and will be removed in the future (used by ${adapterName}).`);
}
const SHARP_SERVICE = 'astro/assets/services/sharp';
@@ -135,7 +135,7 @@ function validateAssetsFeature(
} = assets;
if (config?.image?.service?.entrypoint === SHARP_SERVICE && !isSharpCompatible) {
logger.warn(
- 'astro',
+ null,
`The currently selected adapter \`${adapterName}\` is not compatible with the image service "Sharp".`
);
return false;
@@ -143,7 +143,7 @@ function validateAssetsFeature(
if (config?.image?.service?.entrypoint === SQUOOSH_SERVICE && !isSquooshCompatible) {
logger.warn(
- 'astro',
+ null,
`The currently selected adapter \`${adapterName}\` is not compatible with the image service "Squoosh".`
);
return false;
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index 8b40e5825..55c1bcbcc 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -1,4 +1,4 @@
-import { bold } from 'kleur/colors';
+import { bold, cyan, underline } from 'kleur/colors';
import fs from 'node:fs';
import type { AddressInfo } from 'node:net';
import { fileURLToPath } from 'node:url';
@@ -24,17 +24,24 @@ import { validateSupportedFeatures } from './astroFeaturesValidation.js';
async function withTakingALongTimeMsg<T>({
name,
+ hookName,
hookResult,
timeoutMs = 3000,
logger,
}: {
name: string;
+ hookName: string;
hookResult: T | Promise<T>;
timeoutMs?: number;
logger: Logger;
}): Promise<T> {
const timeout = setTimeout(() => {
- logger.info('build', `Waiting for the ${bold(name)} integration...`);
+ logger.info(
+ 'build',
+ `Waiting for integration ${bold(JSON.stringify(name))}, hook ${bold(
+ JSON.stringify(hookName)
+ )}...`
+ );
}, timeoutMs);
const result = await hookResult;
clearTimeout(timeout);
@@ -188,6 +195,7 @@ export async function runHookConfigSetup({
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:config:setup',
hookResult: integration.hooks['astro:config:setup'](hooks),
logger,
});
@@ -219,6 +227,7 @@ export async function runHookConfigDone({
if (integration?.hooks?.['astro:config:done']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:config:done',
hookResult: integration.hooks['astro:config:done']({
config: settings.config,
setAdapter(adapter) {
@@ -230,7 +239,7 @@ export async function runHookConfigDone({
if (!adapter.supportedAstroFeatures) {
// NOTE: throw an error in Astro 4.0
logger.warn(
- 'astro',
+ null,
`The adapter ${adapter.name} doesn't provide a feature map. From Astro 3.0, an adapter can provide a feature map. Not providing a feature map will cause an error in Astro 4.0.`
);
} else {
@@ -247,7 +256,7 @@ export async function runHookConfigDone({
// if we would refactor the validation to support more than boolean, we could still be able to differentiate between the two cases
if (!supported && featureName !== 'assets') {
logger.error(
- 'astro',
+ null,
`The adapter ${adapter.name} doesn't support the feature ${featureName}. Your project won't be built. You should not use it.`
);
}
@@ -276,6 +285,7 @@ export async function runHookServerSetup({
if (integration?.hooks?.['astro:server:setup']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:setup',
hookResult: integration.hooks['astro:server:setup']({
server,
logger: getLogger(integration, logger),
@@ -299,6 +309,7 @@ export async function runHookServerStart({
if (integration?.hooks?.['astro:server:start']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:start',
hookResult: integration.hooks['astro:server:start']({
address,
logger: getLogger(integration, logger),
@@ -320,6 +331,7 @@ export async function runHookServerDone({
if (integration?.hooks?.['astro:server:done']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:done',
hookResult: integration.hooks['astro:server:done']({
logger: getLogger(integration, logger),
}),
@@ -342,6 +354,7 @@ export async function runHookBuildStart({
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:start',
hookResult: integration.hooks['astro:build:start']({ logger }),
logger: logging,
});
@@ -368,6 +381,7 @@ export async function runHookBuildSetup({
if (integration?.hooks?.['astro:build:setup']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:setup',
hookResult: integration.hooks['astro:build:setup']({
vite,
pages,
@@ -404,6 +418,7 @@ export async function runHookBuildSsr({
if (integration?.hooks?.['astro:build:ssr']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:ssr',
hookResult: integration.hooks['astro:build:ssr']({
manifest,
entryPoints,
@@ -429,6 +444,7 @@ export async function runHookBuildGenerated({
if (integration?.hooks?.['astro:build:generated']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:generated',
hookResult: integration.hooks['astro:build:generated']({
dir,
logger: getLogger(integration, logger),
@@ -456,6 +472,7 @@ export async function runHookBuildDone({ config, pages, routes, logging }: RunHo
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:done',
hookResult: integration.hooks['astro:build:done']({
pages: pages.map((p) => ({ pathname: p })),
dir,
diff --git a/packages/astro/src/runtime/server/endpoint.ts b/packages/astro/src/runtime/server/endpoint.ts
index 7db5f07ee..df415ca67 100644
--- a/packages/astro/src/runtime/server/endpoint.ts
+++ b/packages/astro/src/runtime/server/endpoint.ts
@@ -1,3 +1,4 @@
+import { bold } from 'kleur/colors';
import type { APIContext, EndpointHandler, Params } from '../../@types/astro.js';
import type { Logger } from '../../core/logger/core.js';
@@ -7,7 +8,7 @@ function getHandlerFromModule(mod: EndpointHandler, method: string, logger: Logg
// TODO: remove in Astro 4.0
if (mod[lowerCaseMethod]) {
logger.warn(
- 'astro',
+ null,
`Lower case endpoint names are deprecated and will not be supported in Astro 4.0. Rename the endpoint ${lowerCaseMethod} to ${method}.`
);
}
@@ -44,15 +45,18 @@ export async function renderEndpoint(
ssr: boolean,
logger: Logger
) {
- const { request } = context;
+ const { request, url } = context;
const chosenMethod = request.method?.toUpperCase();
const handler = getHandlerFromModule(mod, chosenMethod, logger);
// TODO: remove the 'get' check in Astro 4.0
if (!ssr && ssr === false && chosenMethod && chosenMethod !== 'GET' && chosenMethod !== 'get') {
- // eslint-disable-next-line no-console
- console.warn(`
-${chosenMethod} requests are not available when building a static site. Update your config to \`output: 'server'\` or \`output: 'hybrid'\` with an \`export const prerender = false\` to handle ${chosenMethod} requests.`);
+ logger.warn(
+ null,
+ `${url.pathname} ${bold(
+ chosenMethod
+ )} requests are not available for a static site. Update your config to \`output: 'server'\` or \`output: 'hybrid'\` to enable.`
+ );
}
if (!handler || typeof handler !== 'function') {
// No handler found, so this should be a 404. Using a custom header
diff --git a/packages/astro/src/vite-plugin-astro-server/base.ts b/packages/astro/src/vite-plugin-astro-server/base.ts
index 7bf57d10a..e757515d7 100644
--- a/packages/astro/src/vite-plugin-astro-server/base.ts
+++ b/packages/astro/src/vite-plugin-astro-server/base.ts
@@ -1,10 +1,10 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
+import { bold } from 'kleur/colors';
import * as fs from 'node:fs';
import type { Logger } from '../core/logger/core.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
-import { log404 } from './common.js';
import { writeHtmlResponse } from './response.js';
export function baseMiddleware(
@@ -28,13 +28,11 @@ export function baseMiddleware(
}
if (pathname === '/' || pathname === '/index.html') {
- log404(logger, pathname);
const html = subpathNotUsedTemplate(devRoot, pathname);
return writeHtmlResponse(res, 404, html);
}
if (req.headers.accept?.includes('text/html')) {
- log404(logger, pathname);
const html = notFoundTemplate({
statusCode: 404,
title: 'Not found',
@@ -45,13 +43,16 @@ export function baseMiddleware(
}
// Check to see if it's in public and if so 404
+ // TODO: Remove redirect, turn this warning into an error in Astro 4.0
const publicPath = new URL('.' + req.url, config.publicDir);
fs.stat(publicPath, (_err, stats) => {
if (stats) {
const expectedLocation = new URL('.' + url, devRootURL).pathname;
logger.warn(
- 'dev',
- `Requests for items in your public folder must also include your base. ${url} should be ${expectedLocation}. Omitting the base will break in production.`
+ 'router',
+ `Request URLs for ${bold(
+ 'public/'
+ )} assets must also include your base. "${expectedLocation}" expected, but received "${url}".`
);
res.writeHead(301, {
Location: expectedLocation,
diff --git a/packages/astro/src/vite-plugin-astro-server/common.ts b/packages/astro/src/vite-plugin-astro-server/common.ts
deleted file mode 100644
index 9e331232c..000000000
--- a/packages/astro/src/vite-plugin-astro-server/common.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Logger } from '../core/logger/core.js';
-import * as msg from '../core/messages.js';
-
-export function log404(logger: Logger, pathname: string) {
- logger.info('serve', msg.req({ url: pathname, statusCode: 404 }));
-}
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index 7468f8819..0863ad1b4 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -10,6 +10,7 @@ import type {
} from '../@types/astro.js';
import { AstroErrorData, isAstroError } from '../core/errors/index.js';
import { sequence } from '../core/middleware/index.js';
+import { req } from '../core/messages.js';
import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
import {
createRenderContext,
@@ -24,7 +25,6 @@ import { createI18nMiddleware, i18nPipelineHook } from '../i18n/middleware.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js';
import { isServerLikeOutput } from '../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
-import { log404 } from './common.js';
import { getStylesForURL } from './css.js';
import type DevPipeline from './devPipeline.js';
import { preload } from './index.js';
@@ -48,6 +48,10 @@ export interface MatchedRoute {
mod: ComponentInstance;
}
+function isLoggedRequest(url: string) {
+ return url !== '/favicon.ico';
+}
+
function getCustom404Route(manifestData: ManifestData): RouteData | undefined {
const route404 = /^\/404\/?$/;
return manifestData.routes.find((r) => route404.test(r.route));
@@ -108,14 +112,13 @@ export async function matchRoute(
const possibleRoutes = matches.flatMap((route) => route.component);
pipeline.logger.warn(
- 'getStaticPaths',
+ 'router',
`${AstroErrorData.NoMatchingStaticPathFound.message(
pathname
)}\n\n${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
);
}
- log404(logger, pathname);
const custom404 = getCustom404Route(manifestData);
if (custom404) {
@@ -161,11 +164,15 @@ export async function handleRoute({
incomingResponse,
manifest,
}: HandleRoute): Promise<void> {
+ const timeStart = performance.now();
const env = pipeline.getEnvironment();
const config = pipeline.getConfig();
const moduleLoader = pipeline.getModuleLoader();
const { logger } = env;
if (!matchedRoute && !config.experimental.i18n) {
+ if (isLoggedRequest(pathname)) {
+ logger.info(null, req({ url: pathname, method: incomingRequest.method, statusCode: 404 }));
+ }
return handle404Response(origin, incomingRequest, incomingResponse);
}
@@ -298,6 +305,18 @@ export async function handleRoute({
}
let response = await pipeline.renderRoute(renderContext, mod);
+ if (isLoggedRequest(pathname)) {
+ const timeEnd = performance.now();
+ logger.info(
+ null,
+ req({
+ url: pathname,
+ method: incomingRequest.method,
+ statusCode: response.status,
+ reqTime: timeEnd - timeStart,
+ })
+ );
+ }
if (response.status === 404 && has404Route(manifestData)) {
const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline);
if (options && fourOhFourRoute?.route !== options.route)
diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts
index 27cc2d10f..50e1bd25a 100644
--- a/packages/astro/src/vite-plugin-astro/hmr.ts
+++ b/packages/astro/src/vite-plugin-astro/hmr.ts
@@ -9,7 +9,6 @@ import {
type CompileResult,
} from '../core/compile/index.js';
import type { Logger } from '../core/logger/core.js';
-import * as msg from '../core/messages.js';
import { isAstroScript } from './query.js';
const PKG_PREFIX = fileURLToPath(new URL('../../', import.meta.url));
@@ -92,11 +91,10 @@ export async function handleHotUpdate(
// Bugfix: sometimes style URLs get normalized and end with `lang.css=`
// These will cause full reloads, so filter them out here
const mods = [...filtered].filter((m) => !m.url.endsWith('='));
- const file = ctx.file.replace(slash(fileURLToPath(config.root)), '/');
// If only styles are changed, remove the component file from the update list
if (isStyleOnlyChange) {
- logger.info('astro', msg.hmr({ file, style: true }));
+ logger.debug('watch', 'style-only change');
// remove base file and hoisted scripts
return mods.filter((mod) => mod.id !== ctx.file && !mod.id?.endsWith('.ts'));
}
@@ -112,11 +110,8 @@ export async function handleHotUpdate(
// TODO: Svelte files should be marked as `isSelfAccepting` but they don't appear to be
const isSelfAccepting = mods.every((m) => m.isSelfAccepting || m.url.endsWith('.svelte'));
- if (isSelfAccepting) {
- if (/astro\.config\.[cm][jt]s$/.test(file)) return mods;
- logger.info('astro', msg.hmr({ file }));
- } else {
- logger.info('astro', msg.reload({ file }));
+ if (!isSelfAccepting) {
+ logger.debug('watch', 'full page reload triggered');
}
return mods;
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 1649d8069..631989903 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -4,6 +4,7 @@ import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
import type { PluginMetadata as AstroPluginMetadata } from './types.js';
+import { fileURLToPath } from 'url';
import { normalizePath } from 'vite';
import {
cachedCompilation,
@@ -23,6 +24,28 @@ interface AstroPluginOptions {
logger: Logger;
}
+const PKG_PREFIX = fileURLToPath(new URL('../../', import.meta.url));
+const E2E_PREFIX = fileURLToPath(new URL('../../e2e', import.meta.url));
+const isPkgFile = (id: string | null) => {
+ return id?.startsWith(PKG_PREFIX) && !id.startsWith(E2E_PREFIX);
+};
+
+const dedupeHotUpdateLogsCache = new Map<string, NodeJS.Timeout>();
+
+// TODO(fks): For some reason, we're seeing duplicate handleHotUpdate() calls
+// when hitting save multiple times in a row. This is a temporary workaround
+// to prevent duplicate logging until the (vite?) issue is fixed.
+function dedupeHotUpdateLogs(filename: string) {
+ if (dedupeHotUpdateLogsCache.has(filename)) {
+ return false;
+ }
+ dedupeHotUpdateLogsCache.set(
+ filename,
+ setTimeout(() => dedupeHotUpdateLogsCache.delete(filename), 150)
+ );
+ return true;
+}
+
/** Transform .astro files for Vite */
export default function astro({ settings, logger }: AstroPluginOptions): vite.Plugin[] {
const { config } = settings;
@@ -173,18 +196,27 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl
},
async handleHotUpdate(context) {
if (context.server.config.isProduction) return;
- const compileProps: CompileProps = {
- astroConfig: config,
- viteConfig: resolvedConfig,
- filename: context.file,
- source: await context.read(),
- };
- const compile = () => cachedCompilation(compileProps);
+ const filename = context.file;
+ const isSkipLog =
+ /astro\.config\.[cm][jt]s$/.test(filename) ||
+ /(\/|\\)\.astro(\/|\\)/.test(filename) ||
+ isPkgFile(filename);
+ if (!isSkipLog && dedupeHotUpdateLogs(filename)) {
+ logger.info('watch', filename.replace(config.root.pathname, '/'));
+ }
+ const source = await context.read();
+ const compile = () =>
+ cachedCompilation({
+ astroConfig: config,
+ viteConfig: resolvedConfig,
+ filename,
+ source,
+ });
return handleHotUpdate(context, {
config,
logger,
compile,
- source: compileProps.source,
+ source,
});
},
};
diff --git a/packages/astro/src/vite-plugin-inject-env-ts/index.ts b/packages/astro/src/vite-plugin-inject-env-ts/index.ts
index d884075ab..116b45e3c 100644
--- a/packages/astro/src/vite-plugin-inject-env-ts/index.ts
+++ b/packages/astro/src/vite-plugin-inject-env-ts/index.ts
@@ -57,7 +57,7 @@ export async function setUpEnvTs({
'types="astro/client"'
);
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
- logger.info('assets', `Removed ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Removed ${bold(envTsPathRelativetoRoot)} type declarations`);
}
if (!fs.existsSync(dotAstroDir))
@@ -68,7 +68,7 @@ export async function setUpEnvTs({
if (!typesEnvContents.includes(expectedTypeReference)) {
typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
- logger.info('content', `Added ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
}
} else {
// Otherwise, inject the `env.d.ts` file
@@ -81,6 +81,6 @@ export async function setUpEnvTs({
await fs.promises.mkdir(settings.config.srcDir, { recursive: true });
await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'), 'utf-8');
- logger.info('astro', `Added ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
}
}
diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts
index d48aed203..678f3ac18 100644
--- a/packages/astro/src/vite-plugin-scanner/index.ts
+++ b/packages/astro/src/vite-plugin-scanner/index.ts
@@ -52,12 +52,11 @@ export default function astroScannerPlugin({
// this should only be valid for `.astro`, `.js` and `.ts` files
KNOWN_FILE_EXTENSIONS.includes(extname(filename))
) {
- const reason = ` because \`output: "${settings.config.output}"\` is set`;
logger.warn(
- 'getStaticPaths',
- `The getStaticPaths() statement in ${bold(
+ 'router',
+ `getStaticPaths() ignored in dynamic page ${bold(
rootRelativePath(settings.config.root, fileURL, true)
- )} has been ignored${reason}.\n\nAdd \`export const prerender = true;\` to prerender this page.`
+ )}. Add \`export const prerender = true;\` to prerender the page as static HTML during the build process.`
);
}
diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js
index 82cf7a12d..caec4241d 100644
--- a/packages/astro/test/cli.test.js
+++ b/packages/astro/test/cli.test.js
@@ -106,7 +106,7 @@ describe('astro cli', () => {
expect(messages[0]).to.contain('astro');
expect(messages[0]).to.contain(pkgVersion);
- expect(messages[0]).to.contain('started in');
+ expect(messages[0]).to.contain('ready in');
});
['dev', 'preview'].forEach((cmd) => {
diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js
index 74f86ba7f..7d9ce4d3b 100644
--- a/packages/astro/test/core-image.test.js
+++ b/packages/astro/test/core-image.test.js
@@ -663,7 +663,6 @@ describe('astro:image', () => {
logs.length = 0;
let res = await fixture.fetch('/post');
await res.text();
-
expect(logs).to.have.a.lengthOf(1);
expect(logs[0].message).to.contain('Could not find requested image');
});
diff --git a/packages/astro/test/static-build.test.js b/packages/astro/test/static-build.test.js
index 8bde08132..5ec225133 100644
--- a/packages/astro/test/static-build.test.js
+++ b/packages/astro/test/static-build.test.js
@@ -186,8 +186,7 @@ describe('Static build', () => {
let found = false;
for (const log of logs) {
if (
- log.label === 'ssg' &&
- /[hH]eaders are not exposed in static \(SSG\) output mode/.test(log.message)
+ /\`Astro\.request\.headers\` is not available in "static" output mode/.test(log.message)
) {
found = true;
}
diff --git a/packages/astro/test/units/vite-plugin-astro-server/request.test.js b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
index d3472c56b..d1d0cf464 100644
--- a/packages/astro/test/units/vite-plugin-astro-server/request.test.js
+++ b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
@@ -21,7 +21,7 @@ async function createDevPipeline(overrides = {}) {
return new DevPipeline({
manifest,
settings,
- logging: defaultLogger,
+ logger: defaultLogger,
loader,
});
}
diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts
index 3ac5d231b..f641e876e 100644
--- a/packages/create-astro/src/index.ts
+++ b/packages/create-astro/src/index.ts
@@ -19,9 +19,9 @@ process.on('SIGTERM', exit);
// https://github.com/withastro/docs/blob/main/src/pages/en/install/auto.md
// if you make any changes to the flow or wording here.
export async function main() {
- // Clear console because PNPM startup is super ugly
+ // Add some extra spacing from the noisy npm/pnpm init output
// eslint-disable-next-line no-console
- console.clear();
+ console.log('');
// NOTE: In the v7.x version of npm, the default behavior of `npm init` was changed
// to no longer require `--` to pass args and instead pass `--` directly to us. This
// broke our arg parser, since `--` is a special kind of flag. Filtering for `--` here
diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts
index 43728b1a3..45f694887 100644
--- a/packages/integrations/sitemap/src/index.ts
+++ b/packages/integrations/sitemap/src/index.ts
@@ -10,7 +10,6 @@ import {
import { ZodError } from 'zod';
import { generateSitemap } from './generate-sitemap.js';
-import { Logger } from './utils/logger.js';
import { validateOptions } from './validate-options.js';
export { EnumChangefreq as ChangeFreqEnum } from 'sitemap';
@@ -62,7 +61,6 @@ function isStatusCodePage(pathname: string): boolean {
const createPlugin = (options?: SitemapOptions): AstroIntegration => {
let config: AstroConfig;
- const logger = new Logger(PKG_NAME);
return {
name: PKG_NAME,
@@ -72,7 +70,7 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
config = cfg;
},
- 'astro:build:done': async ({ dir, routes, pages }) => {
+ 'astro:build:done': async ({ dir, routes, pages, logger }) => {
try {
if (!config.site) {
logger.warn(
@@ -178,7 +176,7 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
limit: entryLimit,
gzip: false,
});
- logger.success(`\`${OUTFILE}\` created at \`${path.relative(process.cwd(), destDir)}\``);
+ logger.info(`\`${OUTFILE}\` created at \`${path.relative(process.cwd(), destDir)}\``);
} catch (err) {
if (err instanceof ZodError) {
logger.warn(formatConfigErrorMessage(err));
diff --git a/packages/integrations/sitemap/src/utils/logger.ts b/packages/integrations/sitemap/src/utils/logger.ts
deleted file mode 100644
index 3292bbdfc..000000000
--- a/packages/integrations/sitemap/src/utils/logger.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-// @internal
-export interface ILogger {
- info(msg: string): void;
- success(msg: string): void;
- warn(msg: string): void;
- error(msg: string): void;
-}
-
-// @internal
-export class Logger implements ILogger {
- private colors = {
- reset: '\x1b[0m',
- fg: {
- red: '\x1b[31m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- },
- } as const;
-
- private packageName: string;
-
- constructor(packageName: string) {
- this.packageName = packageName;
- }
-
- private log(msg: string, prefix = '') {
- // eslint-disable-next-line no-console
- console.log(`%s${this.packageName}:%s ${msg}\n`, prefix, prefix ? this.colors.reset : '');
- }
-
- info(msg: string) {
- this.log(msg);
- }
-
- success(msg: string) {
- this.log(msg, this.colors.fg.green);
- }
-
- warn(msg: string) {
- this.log(`Skipped!\n${msg}`, this.colors.fg.yellow);
- }
-
- error(msg: string) {
- this.log(`Failed!\n${msg}`, this.colors.fg.red);
- }
-}