188 lines
4.3 KiB
JavaScript
Executable File
188 lines
4.3 KiB
JavaScript
Executable File
#!/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 <path>', '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
|
|
} |