From 6201bbe96c2a083fb201e4a43a9bd88499821a3e Mon Sep 17 00:00:00 2001 From: "Fred K. Schott" Date: Sat, 18 Nov 2023 00:38:54 -0800 Subject: [PATCH] Logging rewrite 2 (#9105) * update logging add back warning for header access improve labels and formatting improve error logging remove outdated error fix build new error messages and hints new error messages and hints * walk back error message changes * fix rebase issues * add changeset * Remove accidental log * revert bad env change * Update index.ts * Update packages/astro/src/core/build/index.ts Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> --------- Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> --- .changeset/calm-baboons-watch.md | 5 + .changeset/modern-candles-sip.md | 5 + packages/astro/src/assets/build/generate.ts | 4 +- packages/astro/src/cli/install-package.ts | 2 +- packages/astro/src/cli/telemetry/index.ts | 12 +- .../astro/src/content/server-listeners.ts | 14 +-- packages/astro/src/content/types-generator.ts | 93 ++++++---------- packages/astro/src/core/app/index.ts | 2 +- packages/astro/src/core/build/generate.ts | 34 +++--- packages/astro/src/core/build/index.ts | 27 +++-- packages/astro/src/core/build/static-build.ts | 9 +- packages/astro/src/core/build/util.ts | 2 +- packages/astro/src/core/create-vite.ts | 18 ++- packages/astro/src/core/dev/dev.ts | 11 +- packages/astro/src/core/dev/restart.ts | 14 +-- packages/astro/src/core/endpoint/index.ts | 8 +- packages/astro/src/core/errors/dev/vite.ts | 1 - packages/astro/src/core/logger/console.ts | 40 +------ packages/astro/src/core/logger/core.ts | 64 ++++++++++- packages/astro/src/core/logger/node.ts | 103 ++---------------- packages/astro/src/core/messages.ts | 98 ++++++++--------- .../src/core/middleware/callMiddleware.ts | 5 +- .../src/core/preview/static-preview-server.ts | 4 +- packages/astro/src/core/render/core.ts | 2 +- packages/astro/src/core/render/result.ts | 2 +- packages/astro/src/core/render/route-cache.ts | 4 +- packages/astro/src/core/request.ts | 6 +- .../astro/src/core/routing/manifest/create.ts | 16 +-- packages/astro/src/core/routing/validation.ts | 8 +- packages/astro/src/core/sync/index.ts | 4 +- .../integrations/astroFeaturesValidation.ts | 12 +- packages/astro/src/integrations/index.ts | 25 ++++- packages/astro/src/runtime/server/endpoint.ts | 14 ++- .../src/vite-plugin-astro-server/base.ts | 11 +- .../src/vite-plugin-astro-server/common.ts | 6 - .../src/vite-plugin-astro-server/route.ts | 25 ++++- packages/astro/src/vite-plugin-astro/hmr.ts | 11 +- packages/astro/src/vite-plugin-astro/index.ts | 48 ++++++-- .../src/vite-plugin-inject-env-ts/index.ts | 6 +- .../astro/src/vite-plugin-scanner/index.ts | 7 +- packages/astro/test/cli.test.js | 2 +- packages/astro/test/core-image.test.js | 1 - packages/astro/test/static-build.test.js | 3 +- .../vite-plugin-astro-server/request.test.js | 2 +- packages/create-astro/src/index.ts | 4 +- packages/integrations/sitemap/src/index.ts | 6 +- .../integrations/sitemap/src/utils/logger.ts | 46 -------- 47 files changed, 395 insertions(+), 451 deletions(-) create mode 100644 .changeset/calm-baboons-watch.md create mode 100644 .changeset/modern-candles-sip.md delete mode 100644 packages/astro/src/vite-plugin-astro-server/common.ts delete mode 100644 packages/integrations/sitemap/src/utils/logger.ts diff --git a/.changeset/calm-baboons-watch.md b/.changeset/calm-baboons-watch.md new file mode 100644 index 000000000000..a0e8259e965e --- /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 000000000000..31e75c412551 --- /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 be637c26da7a..c4109ea1edb1 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 689f81e3e879..667037a0cb3d 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( 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 fd664fcc9441..277b1cab6721 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 699d5f2710dd..3ff3148cbdfc 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 b50c597fdf5e..dc9c1ecc76ed 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 ec799011907b..4c6fb5783e76 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 02837cf69cc8..2beb85d62ee5 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(); 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 f096b8f767e9..19f263a017a6 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 81dcdb4a00b0..95404c6d677f 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 e46a0713a0b4..fc12b486f164 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 de644372996c..2b677c1d9d34 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 02ba9d872ffc..f8db6647e811 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 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 33c659dcafd5..d5484f0df30f 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 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 // 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 b3e49234d603..7d67806f8fa4 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 f39f6b74d233..d55318d4ccac 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 = { 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 11804dd01ab7..5d617a1a2fa2 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 { +export interface LogWritable { 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; 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 57aa59ed02d4..801df32f50b2 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 = { + 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 { - 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; - level?: LoggerLevel; -} - -export const nodeLogOptions: Required = { - dest: nodeLogDestination, - level: 'info', -}; - -export interface LogMessage { - label: string | null; - level: LoggerLevel; - message: string; -} - -export const levels: Record = { - debug: 20, - info: 30, - warn: 40, - error: 50, - silent: 90, }; const debuggers: Record = {}; + /** * 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) { +function debug(type: string, ...messages: Array) { 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) { // 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 758f5e5813c4..7c1d663c528f 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 40513c152ff2..d0a6ceca201a 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( 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 68a700b6d81f..ba692611e939 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 da9675f105d1..a3235003f3fd 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 91dc545df4c1..f4a1b0769999 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 c318b8c44308..322326957bb7 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 f478b0a320db..6d55caa15de6 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 6a57972e0712..ded3e13a8a5b 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(); - 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 0261c865a433..b68d5f3e8993 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 0c7b81c3aa67..966454845b30 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 a26f42afbe4c..d9049f8765bf 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 8b40e5825fdb..55c1bcbcc81d 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({ name, + hookName, hookResult, timeoutMs = 3000, logger, }: { name: string; + hookName: string; hookResult: T | Promise; timeoutMs?: number; logger: Logger; }): Promise { 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 7db5f07ee6ff..df415ca67a47 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 7bf57d10a394..e757515d739c 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 9e331232c1c7..000000000000 --- 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 7468f881907a..0863ad1b4e06 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 { + 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 27cc2d10fca8..50e1bd25ae5b 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 1649d8069151..63198990387b 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(); + +// 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 d884075ab09e..116b45e3c00a 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 d48aed203dde..678f3ac187d8 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 82cf7a12dca8..caec4241dace 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 74f86ba7ff6e..7d9ce4d3bfaf 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 8bde08132e97..5ec225133458 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 d3472c56b59b..d1d0cf464e84 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 3ac5d231bfcc..f641e876e414 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 43728b1a3d6b..45f694887733 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 3292bbdfc6bb..000000000000 --- 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); - } -}