diff --git a/lib/dovecot_vitepress_init.js b/lib/dovecot_vitepress_init.js index 41558fe7e..c5d255b06 100644 --- a/lib/dovecot_vitepress_init.js +++ b/lib/dovecot_vitepress_init.js @@ -1,41 +1,40 @@ import { dataFileList, publicDataDir } from './datafiles.js' import { dovecotMdInit } from './markdown.js' +import { logger } from './logger.js' import fs from 'fs' let has_run = false export default function dovecotVitepressInit() { - return { - name: 'dovecot-vitepress-init', - async configResolved(config) { - /*** Init Dovecot Markdown. ***/ + return { + name: 'dovecot-vitepress-init', + async configResolved(config) { + /*** Init Dovecot Markdown. ***/ - /* We need to synchronously initialize markdown, - * since we need to pre-populate various internal - * tables (e.g. links). */ - await dovecotMdInit() - console.log('\n✅ Dovecot Markdown initialized.') + /* We need to synchronously initialize markdown, + * since we need to pre-populate various internal + * tables (e.g. links). */ + await dovecotMdInit() + logger.success('Dovecot Markdown initialized', { once: true }) - /*** Create static downloadable data files. ***/ + /*** Create static downloadable data files. ***/ - if (has_run) { - return - } - has_run = true + if (has_run) return + has_run = true - /* Clean old data files (if they exist) and prepare directory. */ - fs.rmSync(publicDataDir, { force: true, recursive: true }); - fs.mkdirSync(publicDataDir, { recursive: true }); - console.log(`✅ Data files: Created ${publicDataDir}.`) + /* Clean old data files (if they exist) and prepare directory. */ + fs.rmSync(publicDataDir, { force: true, recursive: true }); + fs.mkdirSync(publicDataDir, { recursive: true }); + logger.success(`Data files: Created ${publicDataDir}`) - /* Build the data files. */ - for (const d of dataFileList) { - fs.writeFileSync( - d.json, - JSON.stringify(await d.data(), null, 2) - ) - console.log(`✅ Data files: Generated ${d.json}.`) - } - } - } + /* Build the data files. */ + for (const d of dataFileList) { + fs.writeFileSync( + d.json, + JSON.stringify(await d.data(), null, 2) + ) + logger.success(`Data files: Generated ${d.json}`) + } + } + } } diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 000000000..6c0427519 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,34 @@ +/* Standardized logging utility. */ + +if (!globalThis.__LOGGED_ONCE__) { + globalThis.__LOGGED_ONCE__ = new Set() +} + +let logQueue = Promise.resolve() + +export const logger = { + success(message, { once = false, newline = false } = {}) { + if (once) { + if (globalThis.__LOGGED_ONCE__.has(message)) return + globalThis.__LOGGED_ONCE__.add(message) + } + + logQueue = logQueue.then(() => { + console.log(`${newline ? '\n' : ''}✅ ${message}.`) + }) + }, + + fatal(message) { + const err = new Error(message) + const stack = err.stack.split('\n') + + /* The first line of stack is 'Error: message'. + * The second line is the call to logger.fatal(). + * The third line is the actual location of the error. */ + const callSite = stack[2] ? stack[2].trim() : 'unknown location' + + console.error(`\n❌ FATAL ERROR: ${message}`) + console.error(` at ${callSite}\n`) + process.exit(1) + } +} diff --git a/lib/markdown.js b/lib/markdown.js index 98c31778e..c424ac35b 100644 --- a/lib/markdown.js +++ b/lib/markdown.js @@ -3,6 +3,7 @@ import fg from 'fast-glob' import deflistPlugin from 'markdown-it-deflist' import path from 'path' import { createMarkdownRenderer } from 'vitepress' +import { logger } from "./logger.js" import { dovecotSetting, frontmatterIter, loadData } from './utility.js' let md_conf = false @@ -25,7 +26,7 @@ export async function dovecotMdInit() { for (const [k, v] of Object.entries(data.dovecotlinks)) { if (links[k]) { - throw new Error("Duplicate Dovecot Link key: " + k) + logger.fatal("Duplicate Dovecot Link key: " + k) } links[k] = { @@ -404,7 +405,7 @@ function dovecot_markdown(md, opts) { function handle_error(msg) { if (process.env.NODE_ENV !== 'development') { - throw new Error(msg) + logger.fatal(msg) } console.error(msg) } diff --git a/lib/utility.js b/lib/utility.js index 39b0a1e3f..1caf116e9 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -5,6 +5,7 @@ import fs from 'fs' import matter from 'gray-matter' import { dirname } from 'path' import { fileURLToPath } from 'url' +import { logger } from './logger.js' const __filename = fileURLToPath(import.meta.url) export const lib_dirname = dirname(__filename) @@ -35,8 +36,8 @@ export async function loadData(id) { try { dataOb[id] = await import(lib_dirname + '/' + path) } catch (e) { - throw new Error('Unable to import module (' + lib_dirname + '/' + - path + '):' + e) + logger.fatal('Unable to import module (' + lib_dirname + '/' + + path + '):' + e.message) } } @@ -45,7 +46,7 @@ export async function loadData(id) { function _dovecotSetting(name, setting) { if (setting === undefined) { - throw new Error("Missing '" + name + "' setting") + logger.fatal("Missing '" + name + "' setting") } return setting