zoro-cli
Version:
https://github.com/vuejs/vue-cli
171 lines (157 loc) • 5.02 kB
JavaScript
const debug = require('zoro-cli-util/debug')('cli-generator')
const debugVerbose = require('zoro-cli-util/debug')(
'cliverbose-generator-files'
)
const { removeFiles, writeFiles } = require('zoro-cli-util/fs')
const { log, startSpinner, stopSpinner } = require('zoro-cli-util/logger')
const sortObject = require('zoro-cli-util/sortObject')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
const GeneratorAPI = require('./GeneratorAPI')
const Types = require('./types')
class Generator {
constructor({
context = process.cwd(),
pkg = {},
files = {},
plugins = [],
rootOptions = {},
args = {},
}) {
this.context = context
// this.originalPkg = Object.assign({}, pkg)
this.pkg = pkg
this.plugins = plugins
this.rootOptions = rootOptions
debug.key('rootOptions')
debug(rootOptions)
this.fileMiddlewares = []
this.files = files
this.filesToRemove = []
this.pkgKeyOrder = []
this.scriptsKeyOrder = []
this.postProcessFilesCbs = []
this.completeCbs = []
this.args = args
}
async generate() {
await this.applyPlugins()
// wait for file resolve
await this.resolveFiles()
// write file tree to disk
await this.writeFiles()
// run complete cbs if any, for example lint
log('⚓ Running completion hooks...')
/* eslint-disable no-restricted-syntax, no-await-in-loop */
for (const cb of this.completeCbs) {
await cb()
}
/* eslint-enable no-restricted-syntax, no-await-in-loop */
}
async applyPlugins() {
const { rootOptions, pkg, args } = this
/* eslint-disable no-restricted-syntax, no-await-in-loop */
for (const { id, apply, options = {} } of this.plugins) {
if (apply) {
const api = new GeneratorAPI({
id,
generator: this,
options,
rootOptions,
pkg,
})
await apply({ api, options, rootOptions, Types, pkg, args })
}
}
/* eslint-enable no-restricted-syntax, no-await-in-loop */
}
async resolveFiles() {
/* eslint-disable no-restricted-syntax, no-await-in-loop */
for (const { type, middleware } of this.fileMiddlewares) {
if (type === 'add') {
const files = await middleware({
render: ejs.render,
files: this.files,
})
Object.assign(this.files, files)
} else if (type === 'remove') {
if (Array.isArray(middleware)) {
this.filesToRemove.push(...middleware)
} else {
this.filesToRemove.push(middleware)
}
}
}
for (const postProcess of this.postProcessFilesCbs) {
await postProcess({ files: this.files })
}
/* eslint-enable no-restricted-syntax, no-await-in-loop */
// set package.json
this.sortPkg()
this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
debug.key('filenames')
debug(Object.keys(this.files))
debugVerbose(this.files)
}
sortPkg() {
// ensure package.json keys has readable order
this.pkg.dependencies = sortObject(this.pkg.dependencies)
this.pkg.devDependencies = sortObject(this.pkg.devDependencies)
this.pkg.scripts = sortObject(this.pkg.scripts, this.scriptsKeyOrder, ':')
this.pkg = sortObject(this.pkg, this.pkgKeyOrder)
}
async writeFiles() {
startSpinner('generating files')
const belongToFolder = (folder, filepath) => {
// 如果是 . 文件, 那么要覆盖
const firstChar = path.basename(filepath).charAt(0)
if (firstChar === '_' || firstChar === '.') return false
// 特殊情况要覆盖
const specialCases = [
'src/assets/template',
'src/style/layout/html-body.css',
'src/style/layout/html-body-mobile.css',
]
const isSpecial = specialCases.some(
sepcialCase => filepath.indexOf(sepcialCase) !== -1
)
if (isSpecial) return false
// 属于要忽略的文件夹
return (
filepath.indexOf(folder) === 0 || filepath.indexOf(`./${folder}`) === 0
)
}
/* eslint-disable no-restricted-syntax, no-await-in-loop */
// skip the following folders
for (const folder of [
'src',
'code',
'pages',
'components',
'common',
'tests',
'test',
'types',
]) {
if (fs.existsSync(`./${folder}`)) {
log()
log(`skip override ${folder}`)
this.filesToRemove = this.filesToRemove.filter(
filepath => !belongToFolder(folder, filepath)
)
Object.keys(this.files).forEach(filepath => {
if (belongToFolder(folder, filepath)) {
delete this.files[filepath]
}
})
}
}
/* eslint-enable no-restricted-syntax, no-await-in-loop */
await removeFiles(this.context, this.filesToRemove)
await writeFiles(this.context, this.files)
stopSpinner(false)
log(`files generated to ${this.context}`)
}
}
module.exports = Generator