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
This commit is contained in:
Matthew Phillips 2021-04-23 13:28:00 -04:00 committed by GitHub
parent 510e7920d2
commit 9b9bdbf4a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 16 deletions

View file

@ -3,11 +3,12 @@ import type { LogOptions } from './logger';
import type { AstroRuntime, LoadResult } from './runtime'; import type { AstroRuntime, LoadResult } from './runtime';
import { existsSync, promises as fsPromises } from 'fs'; import { existsSync, promises as fsPromises } from 'fs';
import { bold, green, yellow, underline } from 'kleur/colors';
import path from 'path'; import path from 'path';
import cheerio from 'cheerio'; import cheerio from 'cheerio';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { fdir } from 'fdir'; 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 { createRuntime } from './runtime.js';
import { bundle, collectDynamicImports } from './build/bundle.js'; import { bundle, collectDynamicImports } from './build/bundle.js';
import { generateRSS } from './build/rss.js'; import { generateRSS } from './build/rss.js';
@ -15,6 +16,7 @@ import { generateSitemap } from './build/sitemap.js';
import { collectStatics } from './build/static.js'; import { collectStatics } from './build/static.js';
import { canonicalURL } from './build/util.js'; import { canonicalURL } from './build/util.js';
const { mkdir, readFile, writeFile } = fsPromises; const { mkdir, readFile, writeFile } = fsPromises;
interface PageBuildOptions { interface PageBuildOptions {
@ -197,7 +199,11 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
const pages = await allPages(pageRoot); const pages = await allPages(pageRoot);
let builtURLs: string[] = []; let builtURLs: string[] = [];
try { try {
info(logging , 'build', yellow('! building pages...'));
// Vue also console.warns, this silences it.
const release = trapWarn();
await Promise.all( await Promise.all(
pages.map(async (pathname) => { pages.map(async (pathname) => {
const filepath = new URL(`file://${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)); mergeSet(imports, await collectDynamicImports(filepath, collectImportsOptions));
}) })
); );
info(logging, 'build', green('✔'), 'pages built.');
release();
} catch (err) { } catch (err) {
error(logging, 'generate', err); error(logging, 'generate', err);
await runtime.shutdown(); await runtime.shutdown();
return 1; return 1;
} }
info(logging, 'build', yellow('! scanning pages...'));
for (const pathname of await allPages(componentRoot)) { for (const pathname of await allPages(componentRoot)) {
mergeSet(imports, await collectDynamicImports(new URL(`file://${pathname}`), collectImportsOptions)); mergeSet(imports, await collectDynamicImports(new URL(`file://${pathname}`), collectImportsOptions));
} }
info(logging, 'build', green('✔'), 'pages scanned.');
if (imports.size > 0) { if (imports.size > 0) {
try { try {
info(logging, 'build', yellow('! bundling client-side code.'));
await bundle(imports, { dist, runtime, astroConfig }); await bundle(imports, { dist, runtime, astroConfig });
info(logging, 'build', green('✔'), 'bundling complete.');
} catch (err) { } catch (err) {
error(logging, 'generate', err); error(logging, 'build', err);
await runtime.shutdown(); await runtime.shutdown();
return 1; return 1;
} }
@ -250,6 +262,7 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
} }
if (existsSync(astroConfig.public)) { if (existsSync(astroConfig.public)) {
info(logging, 'build', yellow(`! copying public folder...`));
const pub = astroConfig.public; const pub = astroConfig.public;
const publicFiles = (await new fdir().withFullPaths().crawl(fileURLToPath(pub)).withPromise()) as string[]; const publicFiles = (await new fdir().withFullPaths().crawl(fileURLToPath(pub)).withPromise()) as string[];
for (const filepath of publicFiles) { for (const filepath of publicFiles) {
@ -260,16 +273,28 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
const bytes = await readFile(fileUrl); const bytes = await readFile(fileUrl);
await writeFilep(outUrl, bytes, null); await writeFilep(outUrl, bytes, null);
} }
info(logging, 'build', green('✔'), 'public folder copied.');
} }
// build sitemap // build sitemap
if (astroConfig.buildOptions.sitemap && astroConfig.buildOptions.site) { 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) }))); const sitemap = generateSitemap(builtURLs.map((url) => ({ canonicalURL: canonicalURL(url, astroConfig.buildOptions.site) })));
await writeFile(new URL('./sitemap.xml', dist), sitemap, 'utf8'); await writeFile(new URL('./sitemap.xml', dist), sitemap, 'utf8');
info(logging, 'build', green('✔'), 'sitemap built.');
} else if (astroConfig.buildOptions.sitemap) { } else if (astroConfig.buildOptions.sitemap) {
info(logging, 'tip', `Set "buildOptions.site" in astro.config.mjs to generate a sitemap.xml`); 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(); await runtime.shutdown();
info(logging, 'build', bold(green('▶ Build Complete!')));
return 0; return 0;
} }

View file

@ -2,9 +2,11 @@ import type { AstroConfig } from './@types/astro';
import type { LogOptions } from './logger.js'; import type { LogOptions } from './logger.js';
import { logger as snowpackLogger } from 'snowpack'; import { logger as snowpackLogger } from 'snowpack';
import { bold, green } from 'kleur/colors';
import http from 'http'; import http from 'http';
import { relative as pathRelative } from 'path'; 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'; import { createRuntime } from './runtime.js';
const hostname = '127.0.0.1'; const hostname = '127.0.0.1';
@ -19,6 +21,7 @@ const logging: LogOptions = {
/** The primary dev action */ /** The primary dev action */
export default async function dev(astroConfig: AstroConfig) { export default async function dev(astroConfig: AstroConfig) {
const startServerTime = performance.now();
const { projectRoot } = astroConfig; const { projectRoot } = astroConfig;
const runtime = await createRuntime(astroConfig, { mode: 'development', logging }); const runtime = await createRuntime(astroConfig, { mode: 'development', logging });
@ -66,8 +69,9 @@ export default async function dev(astroConfig: AstroConfig) {
const port = astroConfig.devOptions.port; const port = astroConfig.devOptions.port;
server.listen(port, hostname, () => { server.listen(port, hostname, () => {
// eslint-disable-next-line no-console const endServerTime = performance.now();
console.log(`Server running at http://${hostname}:${port}/`); info(logging, 'dev server', green(`Server started in ${Math.floor(endServerTime - startServerTime)}ms.`));
info(logging, 'dev server', `${green('Local:')} http://${hostname}:${port}/`);
}); });
} }

View file

@ -15,13 +15,16 @@ export const defaultLogDestination = new Writable({
dest = process.stdout; dest = process.stdout;
} }
let type = event.type; let type = event.type;
if (event.level === 'info') { if(type !== null) {
type = bold(blue(type)); if (event.level === 'info') {
} else if (event.level === 'error') { type = bold(blue(type));
type = bold(red(type)); } else if (event.level === 'error') {
type = bold(red(type));
}
dest.write(`[${type}] `);
} }
dest.write(`[${type}] `);
dest.write(utilFormat(...event.args)); dest.write(utilFormat(...event.args));
dest.write('\n'); dest.write('\n');
@ -47,7 +50,7 @@ export const defaultLogOptions: LogOptions = {
}; };
export interface LogMessage { export interface LogMessage {
type: string; type: string | null;
level: LoggerLevel; level: LoggerLevel;
message: string; message: string;
args: Array<any>; args: Array<any>;
@ -62,7 +65,7 @@ const levels: Record<LoggerLevel, number> = {
}; };
/** Full logging API */ /** Full logging API */
export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string, ...args: Array<any>) { export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string | null, ...args: Array<any>) {
const event: LogMessage = { const event: LogMessage = {
type, type,
level, level,
@ -79,22 +82,22 @@ export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, ty
} }
/** Emit a message only shown in debug mode */ /** Emit a message only shown in debug mode */
export function debug(opts: LogOptions, type: string, ...messages: Array<any>) { export function debug(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'debug', type, ...messages); return log(opts, 'debug', type, ...messages);
} }
/** Emit a general info message (be careful using this too much!) */ /** Emit a general info message (be careful using this too much!) */
export function info(opts: LogOptions, type: string, ...messages: Array<any>) { export function info(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'info', type, ...messages); return log(opts, 'info', type, ...messages);
} }
/** Emit a warning a user should be aware of */ /** Emit a warning a user should be aware of */
export function warn(opts: LogOptions, type: string, ...messages: Array<any>) { export function warn(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'warn', type, ...messages); return log(opts, 'warn', type, ...messages);
} }
/** Emit a fatal error message the user should address. */ /** Emit a fatal error message the user should address. */
export function error(opts: LogOptions, type: string, ...messages: Array<any>) { export function error(opts: LogOptions, type: string | null, ...messages: Array<any>) {
return log(opts, 'error', type, ...messages); return log(opts, 'error', type, ...messages);
} }
@ -129,3 +132,12 @@ export const logger = {
warn: warn.bind(null, defaultLogOptions), warn: warn.bind(null, defaultLogOptions),
error: error.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;
}