#!/usr/bin/env node /* eslint-disable no-console */ const exists = require('fs').existsSync const Metalsmith = require('..') const program = require('commander') const { resolve, isAbsolute, dirname } = require('path') const { isString, isObject } = require('../lib/helpers') const color = { error: '\x1b[31m', warn: '\x1b[33m', info: '\x1b[36m', success: '\x1b[32m', log: '\x1b[0m' } /** * Usage. */ program .version(require('../package.json').version) .option('-c, --config ', 'configuration file location', 'metalsmith.json') /** * Examples. */ program.on('--help', function () { console.log(' Examples:') console.log() console.log(' # build from metalsmith.json:') console.log(' $ metalsmith') console.log() console.log(' # build from lib/config.json:') console.log(' $ metalsmith --config lib/config.json') console.log() }) /** * Parse. */ program.parse(process.argv) /** * Config. */ const dir = process.cwd() const config = program.config const path = isAbsolute(config) ? config : resolve(dir, config) // Important addition of 2.5.x. Given local plugins with a relative path are written with __dirname in mind, // having a config-relative dir path makes sure the CLI runs properly // when the command is executed from a subfolder or outside of the ms directory const confRelativeDir = dirname(path) if (!exists(path)) fatal(`could not find a ${config} configuration file.`) let json try { // requiring json is incompatible with ESM, however given the metalsmith CLI is not meant to be "imported" or used in an ESM flow, // it is ok to keep it here for now json = require(path) } catch (e) { fatal(`it seems like ${config} is malformed.`) } /** * Metalsmith. */ const metalsmith = new Metalsmith(confRelativeDir) if (json.source) metalsmith.source(json.source) if (json.destination) metalsmith.destination(json.destination) if (json.concurrency) metalsmith.concurrency(json.concurrency) if (json.metadata) metalsmith.metadata(json.metadata) if (json.clean != null) metalsmith.clean(json.clean) if (json.frontmatter != null) metalsmith.frontmatter(json.frontmatter) if (json.ignore != null) metalsmith.ignore(json.ignore) if (isObject(json.env)) metalsmith.env(expandEnvVars(json.env, process.env)) // set a flag plugins can check to target CLI-specific behavior metalsmith.env('CLI', true) /** * Plugins. */ normalize(json.plugins).forEach(function (plugin) { for (const name in plugin) { const opts = plugin[name] let mod try { const local = resolve(confRelativeDir, name) const npm = resolve(confRelativeDir, 'node_modules', name) if (exists(local) || exists(`${local}.js`)) { mod = require(local) } else if (exists(npm)) { mod = require(npm) } else { mod = require(name) } } catch (e) { fatal(`failed to require plugin "${name}".`) } try { metalsmith.use(mod(opts)) } catch (e) { fatal(`error using plugin "${name}"...`, `${e.message}\n\n${e.stack}`) } } }) /** * Build. */ metalsmith.build(function (err) { if (err) fatal(err.message, err.stack) log('success', `successfully built to ${metalsmith.destination()}`) }) /** * Log an error and then exit the process. * * @param {String} msg * @param {String} [stack] Optional stack trace to print. */ function fatal(msg, stack) { log('error', msg) if (stack) { log('error', stack) } // eslint-disable-next-line no-process-exit process.exit(1) } function log(type, msg) { if (!msg) { msg = type } const fn = console[type] || console.log let args = [`Metalsmith ยท ${msg}`,'\x1b[0m'] if (color[type]) args = [color[type], ...args] fn(...args) } /** * Normalize an `obj` of plugins. * * @param {Array or Object} obj * @return {Array} */ function normalize(obj) { if (obj instanceof Array) return obj const ret = [] for (const key in obj) { const plugin = {} plugin[key] = obj[key] ret.push(plugin) } return ret } /** * Expand env var values in env with values in expansionSource * @param {Object} env * @param {Object} expansionSource * @returns {Object} */ function expandEnvVars(env, expansionSource) { Object.entries(env).forEach(([name, value]) => { if (isString(value) && value.startsWith('$')) { env[name] = expansionSource[value.slice(1)] } }, env) return env }