zoro-cli
Version:
https://github.com/vuejs/vue-cli
192 lines (157 loc) • 5.08 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;