cyb-cli
Version:
塞伯坦CYB-CLI是面向前端模块化工程的构建工具。主要目的是帮助开发者统一前端开发模式和项目开发结构,提高功能扩展和降低维护成本,自动化前端工作流,提高开发效率和开发质量。
193 lines (175 loc) • 4.91 kB
JavaScript
/**
* =================================
* @2018 塞伯坦-CYB前端模块化工程构建工具
* https://github.com/jd-cyb/cyb-cli
* =================================
*/
const async = require('async')
const path = require('path')
const del = require('del')
const Metalsmith = require('metalsmith')
const rename = require('metalsmith-rename')
const Handlebars = require('handlebars')
const render = require('consolidate').handlebars.render
const ora = require('ora')
const chalk = require('chalk')
const spawn = require('child_process').spawn
const isWin = /^win/.test(process.platform)
// register handlebars helper
Handlebars.registerHelper('if_eq', function(a, b, opts) {
return a === b ?
opts.fn(this) :
opts.inverse(this)
})
Handlebars.registerHelper('if_contain', function(a, b, opts) {
return (a = a || []).includes(b) ?
opts.fn(this) :
opts.inverse(this)
})
/**
* Spawns a child process and runs the specified command
* By default, runs in the CWD and inherits stdio
* Options are the same as node's child_process.spawn
* @param {string} cmd
* @param {array<string>} args
* @param {object} options
*/
const runCommand = (cmd, args, options) => {
return new Promise((resolve, reject) => {
const spwan = spawn(
cmd,
args,
Object.assign({
cwd: process.cwd(),
stdio: 'inherit',
shell: true,
},
options
)
)
spwan.on('error', function(err) {
console.log(err)
reject(err)
});
spwan.on('exit', () => {
resolve()
})
})
}
/**
* Template in place plugin.
*
* @param {Object} files
* @param {Metalsmith} metalsmith
* @param {Function} done
*/
const renderTemplateFiles = () => {
return (files, metalsmith, done) => {
const keys = Object.keys(files)
const metalsmithMetadata = metalsmith.metadata()
async.each(keys, (file, next) => {
// skipping files with skipInterpolation option
const str = files[file].contents.toString()
// do not attempt to render files that do not have mustaches
if (!/{{([^{}]+)}}/g.test(str)) {
return next()
}
render(str, metalsmithMetadata, (err, res) => {
if (err) {
err.message = `[${file}] ${err.message}`
return next(err)
}
files[file].contents = new Buffer(res)
next()
})
}, done)
}
}
/**
* filters in place plugin.
*
* @param {Object} files
* @param {Metalsmith} metalsmith
* @param {Function} done
*/
const metalsmithFilters = () => {
return (files, metalsmith, done) => {
const metalsmithMetadata = metalsmith.metadata()
const fileNames = Object.keys(files)
for (let item of metalsmithMetadata.delateFiles) {
if (isWin) {
item = item.replace(/\//g, path.sep) //兼容window
}
fileNames.forEach(file => {
if (file.indexOf(item) > -1) {
delete files[file]
}
})
}
done()
}
}
const filterFiles = (prompt, filters) => {
const evaluate = (exp, data) => {
/* eslint-disable no-new-func */
const fn = new Function('data', 'with (data) { return ' + exp + '}')
try {
return fn(data)
} catch (e) {
console.error(chalk.red('Error when evaluating filter condition: ' + exp))
}
}
let filesTemp = []
Object.keys(filters).forEach(glob => {
if (!evaluate(filters[glob], prompt)) {
filesTemp.push(glob)
}
})
return filesTemp
}
module.exports = (prompt, tempPath) => {
const delateFiles = []
const spinner = ora('Create porject...').start()
const projectPath = path.join(process.cwd(), prompt.projectName)
const metalsmith = Metalsmith(path.join(tempPath, 'template'))
Object.assign(metalsmith.metadata(), {
name: prompt.projectName,
lint: prompt.lint,
pagetype: 'project',
style: prompt.style,
lintConfig: prompt.lintConfig,
userModel: prompt.userModel,
modelConfig: prompt.modelConfig,
delateFiles: filterFiles(prompt, require(path.join(tempPath, './config')).filters)
})
metalsmith
.use(renderTemplateFiles())
.use(metalsmithFilters())
metalsmith
.use(
rename([
[/\.scss$/, `.${prompt.style}`]
])
)
metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(projectPath)
.build((err, files) => {
spinner.stop()
if (err) {
console.log(err)
} else {
del(`${projectPath}/**/.gitkeep`)
if (prompt.autoInstall) {
runCommand(prompt.autoInstall, ['install'], {
cwd: projectPath
})
.then(() => {
spinner.succeed(`成功创建项目:${prompt.projectName}`)
})
} else {
spinner.succeed(`成功创建项目:${prompt.projectName}`)
}
}
})
}