From 02b3d775c96ff3294ee7511a8201014c44957489 Mon Sep 17 00:00:00 2001 From: spring-raining Date: Wed, 15 Jan 2025 01:37:03 +0900 Subject: [PATCH] chore: Provide logger in Vite plugin mode --- docs/api-javascript.md | 52 +++++++++++++++++++++++++++++++++++----- src/config/schema.ts | 11 +++++++++ src/core/build.ts | 1 + src/core/init.ts | 1 + src/core/preview.ts | 1 + src/logger.ts | 46 +++++++++++++++++++++++++---------- src/processor/compile.ts | 7 +++--- src/vite-adapter.ts | 34 ++++++++++++++++++-------- 8 files changed, 121 insertions(+), 32 deletions(-) diff --git a/docs/api-javascript.md b/docs/api-javascript.md index 81ffa30a..e35fc69f 100644 --- a/docs/api-javascript.md +++ b/docs/api-javascript.md @@ -60,6 +60,8 @@ build({ • **options.executableBrowser?**: `string` = `...` +• **options.host?**: `string` \| `boolean` = `...` + • **options.ignoreHttpsErrors?**: `boolean` = `...` • **options.image?**: `string` = `...` @@ -68,12 +70,16 @@ build({ • **options.language?**: `string` = `...` -• **options.logLevel?**: `"silent"` \| `"info"` \| `"verbose"` \| `"debug"` = `...` +• **options.logger?**: `LoggerInterface` = `...` + +• **options.logLevel?**: `"info"` \| `"silent"` \| `"verbose"` \| `"debug"` = `...` • **options.openViewer?**: `boolean` = `...` • **options.output?**: `string` \| `object` & `object` \| (`string` \| `object` & `object`)[] = `...` +• **options.port?**: `number` = `...` + • **options.preflight?**: `"press-ready"` \| `"press-ready-local"` = `...` • **options.preflightOption?**: `string` \| `string`[] = `...` @@ -114,6 +120,10 @@ build({ • **options.viewerParam?**: `string` = `...` +• **options.vite?**: `UserConfig` = `...` + +• **options.viteConfigFile?**: `string` \| `boolean` = `...` + #### Returns `Promise`\<`void`\> @@ -152,6 +162,8 @@ build({ • **inlineConfig.executableBrowser?**: `string` = `...` +• **inlineConfig.host?**: `string` \| `boolean` = `...` + • **inlineConfig.ignoreHttpsErrors?**: `boolean` = `...` • **inlineConfig.image?**: `string` = `...` @@ -160,12 +172,16 @@ build({ • **inlineConfig.language?**: `string` = `...` -• **inlineConfig.logLevel?**: `"silent"` \| `"info"` \| `"verbose"` \| `"debug"` = `...` +• **inlineConfig.logger?**: `LoggerInterface` = `...` + +• **inlineConfig.logLevel?**: `"info"` \| `"silent"` \| `"verbose"` \| `"debug"` = `...` • **inlineConfig.openViewer?**: `boolean` = `...` • **inlineConfig.output?**: `string` \| `object` & `object` \| (`string` \| `object` & `object`)[] = `...` +• **inlineConfig.port?**: `number` = `...` + • **inlineConfig.preflight?**: `"press-ready"` \| `"press-ready-local"` = `...` • **inlineConfig.preflightOption?**: `string` \| `string`[] = `...` @@ -206,6 +222,10 @@ build({ • **inlineConfig.viewerParam?**: `string` = `...` +• **inlineConfig.vite?**: `UserConfig` = `...` + +• **inlineConfig.viteConfigFile?**: `string` \| `boolean` = `...` + #### Returns `Promise`\<`Plugin`[]\> @@ -246,6 +266,8 @@ Initialize a new vivliostyle.config.js file. • **options.executableBrowser?**: `string` = `...` +• **options.host?**: `string` \| `boolean` = `...` + • **options.ignoreHttpsErrors?**: `boolean` = `...` • **options.image?**: `string` = `...` @@ -254,12 +276,16 @@ Initialize a new vivliostyle.config.js file. • **options.language?**: `string` = `...` -• **options.logLevel?**: `"silent"` \| `"info"` \| `"verbose"` \| `"debug"` = `...` +• **options.logger?**: `LoggerInterface` = `...` + +• **options.logLevel?**: `"info"` \| `"silent"` \| `"verbose"` \| `"debug"` = `...` • **options.openViewer?**: `boolean` = `...` • **options.output?**: `string` \| `object` & `object` \| (`string` \| `object` & `object`)[] = `...` +• **options.port?**: `number` = `...` + • **options.preflight?**: `"press-ready"` \| `"press-ready-local"` = `...` • **options.preflightOption?**: `string` \| `string`[] = `...` @@ -300,6 +326,10 @@ Initialize a new vivliostyle.config.js file. • **options.viewerParam?**: `string` = `...` +• **options.vite?**: `UserConfig` = `...` + +• **options.viteConfigFile?**: `string` \| `boolean` = `...` + #### Returns `Promise`\<`void`\> @@ -308,7 +338,7 @@ Initialize a new vivliostyle.config.js file. ### preview() -> **preview**(`options`): `Promise`\<`void`\> +> **preview**(`options`): `Promise`\<`ViteDevServer`\> Open a browser for previewing the publication. @@ -340,6 +370,8 @@ Open a browser for previewing the publication. • **options.executableBrowser?**: `string` = `...` +• **options.host?**: `string` \| `boolean` = `...` + • **options.ignoreHttpsErrors?**: `boolean` = `...` • **options.image?**: `string` = `...` @@ -348,12 +380,16 @@ Open a browser for previewing the publication. • **options.language?**: `string` = `...` -• **options.logLevel?**: `"silent"` \| `"info"` \| `"verbose"` \| `"debug"` = `...` +• **options.logger?**: `LoggerInterface` = `...` + +• **options.logLevel?**: `"info"` \| `"silent"` \| `"verbose"` \| `"debug"` = `...` • **options.openViewer?**: `boolean` = `...` • **options.output?**: `string` \| `object` & `object` \| (`string` \| `object` & `object`)[] = `...` +• **options.port?**: `number` = `...` + • **options.preflight?**: `"press-ready"` \| `"press-ready-local"` = `...` • **options.preflightOption?**: `string` \| `string`[] = `...` @@ -394,9 +430,13 @@ Open a browser for previewing the publication. • **options.viewerParam?**: `string` = `...` +• **options.vite?**: `UserConfig` = `...` + +• **options.viteConfigFile?**: `string` \| `boolean` = `...` + #### Returns -`Promise`\<`void`\> +`Promise`\<`ViteDevServer`\> ## Type Aliases diff --git a/src/config/schema.ts b/src/config/schema.ts index ffc3161d..c606b6b2 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -2,6 +2,7 @@ import { Metadata, StringifyMarkdownOptions } from '@vivliostyle/vfm'; import { type Processor } from 'unified'; import upath from 'upath'; import * as v from 'valibot'; +import { LoggerInterface } from '../logger.js'; const $ = (strings: TemplateStringsArray, ...values: any[]) => { const lines = String.raw({ raw: strings }, ...values).split('\n'); @@ -1159,6 +1160,15 @@ export const VivliostyleInlineConfig = v.pipe( Port the server should listen on. (default: \`13000\`) `), ), + logger: v.pipe( + v.custom(() => true), + v.metadata({ + typeString: 'LoggerInterface', + }), + v.description($` + Custom logger interface. + `), + ), }), ), v.check( @@ -1211,4 +1221,5 @@ export type InlineOptions = Pick< | 'openViewer' | 'enableStaticServe' | 'enableViewerStartPage' + | 'logger' >; diff --git a/src/core/build.ts b/src/core/build.ts index 2858cd91..2d1e1571 100644 --- a/src/core/build.ts +++ b/src/core/build.ts @@ -24,6 +24,7 @@ import { cwd, isInContainer, runExitHandlers } from '../util.js'; export async function build(inlineConfig: ParsedVivliostyleInlineConfig) { Logger.setLogLevel(inlineConfig.logLevel); + Logger.setCustomLogger(inlineConfig.logger); Logger.debug('build > inlineConfig %O', inlineConfig); let vivliostyleConfig = diff --git a/src/core/init.ts b/src/core/init.ts index 50c9f4ea..22433ce2 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -8,6 +8,7 @@ import { cwd, runExitHandlers } from '../util.js'; export async function init(inlineConfig: ParsedVivliostyleInlineConfig) { Logger.setLogLevel(inlineConfig.logLevel); + Logger.setCustomLogger(inlineConfig.logger); const vivliostyleConfigPath = upath.join( inlineConfig.cwd ?? cwd, diff --git a/src/core/preview.ts b/src/core/preview.ts index fda4216a..ac3f4427 100644 --- a/src/core/preview.ts +++ b/src/core/preview.ts @@ -13,6 +13,7 @@ import { createViteServer, getViewerFullUrl } from '../server.js'; export async function preview(inlineConfig: ParsedVivliostyleInlineConfig) { Logger.setLogLevel(inlineConfig.logLevel); + Logger.setCustomLogger(inlineConfig.logger); Logger.debug('preview > inlineConfig %O', inlineConfig); let vivliostyleConfig = diff --git a/src/logger.ts b/src/logger.ts index a05d2a5c..6fc910a7 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -4,9 +4,7 @@ import { blueBright, gray, greenBright, - red, redBright, - yellow, yellowBright, } from 'yoctocolors'; import { isInContainer } from './util.js'; @@ -23,6 +21,12 @@ const successSymbol = greenBright('SUCCESS'); const warnSymbol = yellowBright('WARN'); const errorSymbol = redBright('ERROR'); +export interface LoggerInterface { + info(message: string): void; + warn(message: string): void; + error(message: string): void; +} + export class Logger { /** * 0: silent 1: info 2: verbose 3: debug @@ -30,6 +34,7 @@ export class Logger { static #logLevel: 0 | 1 | 2 | 3 = 0; static #loggerInstance: Logger | undefined; static #nonBlockingLogPrinted = false; + static #customLogger: LoggerInterface | undefined; static debug = debug('vs-cli'); @@ -39,7 +44,8 @@ export class Logger { static get isInteractive() { return Boolean( - process.stderr.isTTY && + !this.#customLogger && + process.stderr.isTTY && process.env.TERM !== 'dumb' && !('CI' in process.env) && !import.meta.env?.VITEST && @@ -109,15 +115,21 @@ export class Logger { this.#nonBlockingLogPrinted = false; } + static getMessage(message: string, symbol?: string) { + return !this.#customLogger && symbol ? `${symbol} ${message}` : message; + } + static #nonBlockingLog( - fallback: (...messages: any[]) => void, + logMethod: 'warn' | 'error' | 'info', message: string, ) { if (!this.#spinner || !this.isInteractive) { if (isInContainer()) { message = `${gray('[Docker]')} ${message}`; } - this.#logLevel >= 3 ? this.debug(message) : fallback(message); + this.#logLevel >= 3 + ? this.debug(message) + : (this.#customLogger || console)[logMethod](message); return; } this.logUpdate(this.#spinner.text); @@ -130,7 +142,10 @@ export class Logger { if (this.#logLevel < 1) { return; } - this.#nonBlockingLog(console.log, `${successSymbol} ${messages.join(' ')}`); + this.#nonBlockingLog( + 'info', + this.getMessage(messages.join(' '), successSymbol), + ); } static logError(...messages: any[]) { @@ -138,8 +153,8 @@ export class Logger { return; } this.#nonBlockingLog( - console.error, - `${errorSymbol} ${red(messages.join(' '))}`, + 'error', + this.getMessage(messages.join(' '), errorSymbol), ); } @@ -148,8 +163,8 @@ export class Logger { return; } this.#nonBlockingLog( - console.warn, - `${warnSymbol} ${yellow(messages.join(' '))}`, + 'warn', + this.getMessage(messages.join(' '), warnSymbol), ); } @@ -157,14 +172,17 @@ export class Logger { if (this.#logLevel < 1) { return; } - this.#nonBlockingLog(console.info, `${infoSymbol} ${messages.join(' ')}`); + this.#nonBlockingLog( + 'info', + this.getMessage(messages.join(' '), infoSymbol), + ); } static logVerbose(...messages: any[]) { if (this.#logLevel < 2) { return; } - this.#nonBlockingLog(console.log, messages.join(' ')); + this.#nonBlockingLog('info', this.getMessage(messages.join(' '))); } static setLogLevel(level?: 'silent' | 'info' | 'verbose' | 'debug') { @@ -184,6 +202,10 @@ export class Logger { } } + static setCustomLogger(logger: LoggerInterface | undefined) { + this.#customLogger = logger; + } + #_spinner: Spinner; constructor() { diff --git a/src/processor/compile.ts b/src/processor/compile.ts index 0685a496..a6ed9124 100644 --- a/src/processor/compile.ts +++ b/src/processor/compile.ts @@ -212,14 +212,13 @@ export async function transformManuscript( } } else if (source?.type === 'uri') { resourceLoader = new ResourceLoader(); - const virtualConsole = createVirtualConsole(() => { - // TODO: handle console messages - }); try { await getJsdomFromUrlOrFile({ src: source.href, resourceLoader, - virtualConsole, + virtualConsole: createVirtualConsole((error) => { + Logger.logError(`Failed to fetch resources: ${error.detail}`); + }), }); } catch (error: any) { throw new DetailError( diff --git a/src/vite-adapter.ts b/src/vite-adapter.ts index 2c9cc344..3214fd29 100644 --- a/src/vite-adapter.ts +++ b/src/vite-adapter.ts @@ -1,4 +1,5 @@ import * as v from 'valibot'; +import * as vite from 'vite'; import { setupConfigFromFlags } from './commands/cli-flags.js'; import { loadVivliostyleConfig, warnDeprecatedConfig } from './config/load.js'; import { mergeInlineConfig } from './config/merge.js'; @@ -11,25 +12,38 @@ import { vsStaticServePlugin } from './vite/vite-plugin-static-serve.js'; import { vsViewerPlugin } from './vite/vite-plugin-viewer.js'; export async function createVitePlugin( - _inlineConfig: VivliostyleInlineConfig = {}, + inlineConfig: VivliostyleInlineConfig = {}, ): Promise { - const inlineConfig = v.parse(VivliostyleInlineConfig, _inlineConfig); - Logger.debug('inlineConfig %O', inlineConfig); + const parsedInlineConfig = v.parse(VivliostyleInlineConfig, inlineConfig); + Logger.setLogLevel(parsedInlineConfig.logLevel); + if (parsedInlineConfig.logger) { + Logger.setCustomLogger(parsedInlineConfig.logger); + } else { + const { info, warn, error } = vite.createLogger('info', { + prefix: '[vivliostyle]', + }); + Logger.setCustomLogger({ + info: (msg) => info(msg, { timestamp: true }), + warn: (msg) => warn(msg, { timestamp: true }), + error: (msg) => error(msg, { timestamp: true }), + }); + } + Logger.debug('inlineConfig %O', parsedInlineConfig); const vivliostyleConfig = - (await loadVivliostyleConfig(inlineConfig)) ?? - setupConfigFromFlags(inlineConfig); + (await loadVivliostyleConfig(parsedInlineConfig)) ?? + setupConfigFromFlags(parsedInlineConfig); warnDeprecatedConfig(vivliostyleConfig); const { tasks, inlineOptions } = mergeInlineConfig( vivliostyleConfig, - inlineConfig, + parsedInlineConfig, ); const config = resolveTaskConfig(tasks[0], inlineOptions); Logger.debug('config %O', config); return [ - vsDevServerPlugin({ config, inlineConfig }), - vsViewerPlugin({ config, inlineConfig }), - vsBrowserPlugin({ config, inlineConfig }), - vsStaticServePlugin({ config, inlineConfig }), + vsDevServerPlugin({ config, inlineConfig: parsedInlineConfig }), + vsViewerPlugin({ config, inlineConfig: parsedInlineConfig }), + vsBrowserPlugin({ config, inlineConfig: parsedInlineConfig }), + vsStaticServePlugin({ config, inlineConfig: parsedInlineConfig }), ]; }