From 9b9bdbf4a13a77acda47468fd4a1adfd3523f047 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 23 Apr 2021 13:28:00 -0400 Subject: [PATCH] Add better CLI logging for dev/build (#126) * Add better CLI logging for dev/build This adds better CLI logging for dev and build. * Fix the sorting --- src/build.ts | 29 +++++++++++++++++++++++++++-- src/dev.ts | 10 +++++++--- src/logger.ts | 34 +++++++++++++++++++++++----------- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/build.ts b/src/build.ts index 2c61b3fed..f5e825ea4 100644 --- a/src/build.ts +++ b/src/build.ts @@ -3,11 +3,12 @@ import type { LogOptions } from './logger'; import type { AstroRuntime, LoadResult } from './runtime'; import { existsSync, promises as fsPromises } from 'fs'; +import { bold, green, yellow, underline } from 'kleur/colors'; import path from 'path'; import cheerio from 'cheerio'; import { fileURLToPath } from 'url'; import { fdir } from 'fdir'; -import { defaultLogDestination, error, info } from './logger.js'; +import { defaultLogDestination, error, info, trapWarn } from './logger.js'; import { createRuntime } from './runtime.js'; import { bundle, collectDynamicImports } from './build/bundle.js'; import { generateRSS } from './build/rss.js'; @@ -15,6 +16,7 @@ import { generateSitemap } from './build/sitemap.js'; import { collectStatics } from './build/static.js'; import { canonicalURL } from './build/util.js'; + const { mkdir, readFile, writeFile } = fsPromises; interface PageBuildOptions { @@ -197,7 +199,11 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> { const pages = await allPages(pageRoot); let builtURLs: string[] = []; + try { + info(logging , 'build', yellow('! building pages...')); + // Vue also console.warns, this silences it. + const release = trapWarn(); await Promise.all( pages.map(async (pathname) => { const filepath = new URL(`file://${pathname}`); @@ -222,21 +228,27 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> { mergeSet(imports, await collectDynamicImports(filepath, collectImportsOptions)); }) ); + info(logging, 'build', green('✔'), 'pages built.'); + release(); } catch (err) { error(logging, 'generate', err); await runtime.shutdown(); return 1; } + info(logging, 'build', yellow('! scanning pages...')); for (const pathname of await allPages(componentRoot)) { mergeSet(imports, await collectDynamicImports(new URL(`file://${pathname}`), collectImportsOptions)); } + info(logging, 'build', green('✔'), 'pages scanned.'); if (imports.size > 0) { try { + info(logging, 'build', yellow('! bundling client-side code.')); await bundle(imports, { dist, runtime, astroConfig }); + info(logging, 'build', green('✔'), 'bundling complete.'); } catch (err) { - error(logging, 'generate', err); + error(logging, 'build', err); await runtime.shutdown(); return 1; } @@ -250,6 +262,7 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> { } if (existsSync(astroConfig.public)) { + info(logging, 'build', yellow(`! copying public folder...`)); const pub = astroConfig.public; const publicFiles = (await new fdir().withFullPaths().crawl(fileURLToPath(pub)).withPromise()) as string[]; for (const filepath of publicFiles) { @@ -260,16 +273,28 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> { const bytes = await readFile(fileUrl); await writeFilep(outUrl, bytes, null); } + info(logging, 'build', green('✔'), 'public folder copied.'); } // build sitemap if (astroConfig.buildOptions.sitemap && astroConfig.buildOptions.site) { + info(logging, 'build', yellow('! creating a sitemap...')); const sitemap = generateSitemap(builtURLs.map((url) => ({ canonicalURL: canonicalURL(url, astroConfig.buildOptions.site) }))); await writeFile(new URL('./sitemap.xml', dist), sitemap, 'utf8'); + info(logging, 'build', green('✔'), 'sitemap built.'); } else if (astroConfig.buildOptions.sitemap) { info(logging, 'tip', `Set "buildOptions.site" in astro.config.mjs to generate a sitemap.xml`); } + builtURLs.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })); + info(logging, 'build', underline('Pages')); + const lastIndex = builtURLs.length - 1; + builtURLs.forEach((url, index) => { + const sep = index === 0 ? '┌' : index === lastIndex ? '└' : '├'; + info(logging, null, ' ' + sep, url === '/' ? url : url + '/'); + }); + await runtime.shutdown(); + info(logging, 'build', bold(green('▶ Build Complete!'))); return 0; } diff --git a/src/dev.ts b/src/dev.ts index 8f93aabf6..c20d9c723 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -2,9 +2,11 @@ import type { AstroConfig } from './@types/astro'; import type { LogOptions } from './logger.js'; import { logger as snowpackLogger } from 'snowpack'; +import { bold, green } from 'kleur/colors'; import http from 'http'; import { relative as pathRelative } from 'path'; -import { defaultLogDestination, error, parseError } from './logger.js'; +import { performance } from 'perf_hooks'; +import { defaultLogDestination, error, info, parseError } from './logger.js'; import { createRuntime } from './runtime.js'; const hostname = '127.0.0.1'; @@ -19,6 +21,7 @@ const logging: LogOptions = { /** The primary dev action */ export default async function dev(astroConfig: AstroConfig) { + const startServerTime = performance.now(); const { projectRoot } = astroConfig; const runtime = await createRuntime(astroConfig, { mode: 'development', logging }); @@ -66,8 +69,9 @@ export default async function dev(astroConfig: AstroConfig) { const port = astroConfig.devOptions.port; server.listen(port, hostname, () => { - // eslint-disable-next-line no-console - console.log(`Server running at http://${hostname}:${port}/`); + const endServerTime = performance.now(); + info(logging, 'dev server', green(`Server started in ${Math.floor(endServerTime - startServerTime)}ms.`)); + info(logging, 'dev server', `${green('Local:')} http://${hostname}:${port}/`); }); } diff --git a/src/logger.ts b/src/logger.ts index 7ffc2da5a..7bfde67d9 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -15,13 +15,16 @@ export const defaultLogDestination = new Writable({ dest = process.stdout; } let type = event.type; - if (event.level === 'info') { - type = bold(blue(type)); - } else if (event.level === 'error') { - type = bold(red(type)); + if(type !== null) { + if (event.level === 'info') { + type = bold(blue(type)); + } else if (event.level === 'error') { + type = bold(red(type)); + } + + dest.write(`[${type}] `); } - dest.write(`[${type}] `); dest.write(utilFormat(...event.args)); dest.write('\n'); @@ -47,7 +50,7 @@ export const defaultLogOptions: LogOptions = { }; export interface LogMessage { - type: string; + type: string | null; level: LoggerLevel; message: string; args: Array; @@ -62,7 +65,7 @@ const levels: Record = { }; /** Full logging API */ -export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string, ...args: Array) { +export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string | null, ...args: Array) { const event: LogMessage = { type, level, @@ -79,22 +82,22 @@ export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, ty } /** Emit a message only shown in debug mode */ -export function debug(opts: LogOptions, type: string, ...messages: Array) { +export function debug(opts: LogOptions, type: string | null, ...messages: Array) { return log(opts, 'debug', type, ...messages); } /** Emit a general info message (be careful using this too much!) */ -export function info(opts: LogOptions, type: string, ...messages: Array) { +export function info(opts: LogOptions, type: string | null, ...messages: Array) { return log(opts, 'info', type, ...messages); } /** Emit a warning a user should be aware of */ -export function warn(opts: LogOptions, type: string, ...messages: Array) { +export function warn(opts: LogOptions, type: string | null, ...messages: Array) { return log(opts, 'warn', type, ...messages); } /** Emit a fatal error message the user should address. */ -export function error(opts: LogOptions, type: string, ...messages: Array) { +export function error(opts: LogOptions, type: string | null, ...messages: Array) { return log(opts, 'error', type, ...messages); } @@ -129,3 +132,12 @@ export const logger = { warn: warn.bind(null, defaultLogOptions), error: error.bind(null, defaultLogOptions), }; + +// For silencing libraries that go directly to console.warn +export function trapWarn(cb: (...args: any[]) => void = () =>{}) { + const warn = console.warn; + console.warn = function(...args: any[]) { + cb(...args); + }; + return () => console.warn = warn; +} \ No newline at end of file