zoro-cli
Version:
https://github.com/vuejs/vue-cli
285 lines (241 loc) • 7.76 kB
JavaScript
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const {
isString,
isObject,
isFunction
} = require('zoro-cli-util/is');
const {
renderArray,
renderDir,
renderObject
} = require('zoro-cli-util/renderFile');
const mergeScripts = require('zoro-cli-util/mergeScripts');
const path = require('path');
const {
union,
mergeWith
} = require('lodash');
const Types = require('./types');
function extractCallDir() {
// extract api.render() callsite file location using error stack
const obj = {};
Error.captureStackTrace(obj);
const callSite = obj.stack.split('\n')[3];
const fileName = callSite.match(/\s\((.*):\d+:\d+\)$/)[1];
return path.dirname(fileName);
}
class GeneratorAPI {
constructor({
id,
generator,
options = {},
rootOptions = {},
pkg = {}
} = {}) {
this.id = id;
this.generator = generator;
this.options = options;
this.rootOptions = rootOptions;
this.pkg = pkg;
}
/**
* Resolves the data when rendering templates.
*
* @private
*/
_resolveData(additionalData) {
return _objectSpread({
options: this.options,
rootOptions: this.rootOptions,
Types,
pkg: this.pkg
}, additionalData);
}
/**
* Inject a file processing middleware.
*
* @private
* @param {FileMiddleware} middleware - A middleware function that receives the
* virtual files tree object, and an ejs render function. Can be async.
*/
_injectFileAddMiddleware(middleware) {
this.generator.fileMiddlewares.push({
type: 'add',
middleware
});
}
_injectFileRemoveMiddleware(middleware) {
this.generator.fileMiddlewares.push({
type: 'remove',
middleware
});
}
/**
* Resolve path for a project.
*
* @param {string} _path - Relative path from project root
* @return {string} The resolved absolute path.
*/
resolve(_path) {
return path.resolve(this.generator.context, _path);
}
/**
* Extend the package.json of the project.
* Nested fields are deep-merged unless `{ merge: false }` is passed.
* Also resolves dependency conflicts between plugins.
* Tool configuration fields may be extracted into standalone files before
* files are written to disk.
*
* @param {object | () => object} fields - Fields to merge.
*/
extendPackage(fields, options = {}) {
const {
overrideObj,
overrideArr
} = options;
const pkg = this.generator.pkg;
const toMerge = isFunction(fields) ? fields(pkg) : fields;
Object.keys(toMerge).forEach(key => {
const value = toMerge[key];
const existing = pkg[key];
if (!existing) {
pkg[key] = value;
} else if (Array.isArray(existing) && Array.isArray(value)) {
if (overrideArr) {
pkg[key] = value;
} else {
pkg[key] = union(existing, value);
}
} else if (isObject(value)) {
if (overrideObj) {
pkg[key] = value;
} else if (key === 'scripts') {
pkg[key] = mergeScripts(existing, value);
} else {
pkg[key] = mergeWith({}, existing, value, (destValue, srcValue) => {
if (Array.isArray(destValue) || Array.isArray(srcValue)) {
if (overrideArr) {
return srcValue;
}
return union(destValue, srcValue);
}
if (destValue !== undefined && srcValue === undefined) {
// 如果是 ''/null/undefined, 不覆盖
return destValue;
}
return undefined;
});
}
} else {
pkg[key] = value;
}
});
}
removeDependencies(names = []) {
names.forEach(name => {
const {
pkg
} = this.generator;
if (pkg.dependencies) {
delete pkg.dependencies[name];
}
if (pkg.devDependencies) {
delete pkg.devDependencies[name];
}
});
}
/**
* Render template files into the virtual files tree object.
*
* @param {string | object | FileMiddleware} source -
* Can be one of:
* - relative path to caller directory;
* - Object of { dir: '', arr: [] };
* - dir is relative path to caller directory
* - arr is an array of paths relative to dir
* - Object hash of { sourceTemplate: targetFile } mappings;
* - a custom file middleware function.
* @param {object} [additionalData] - additional data available to templates.
* @param {object} [ejsOptions] - options for ejs.
*/
render(source, additionalData = {}, ejsOptions = {}) {
const baseDir = extractCallDir();
const data = this._resolveData(additionalData);
if (isString(source)) {
this._injectFileAddMiddleware(() => renderDir({
dir: path.join(baseDir, source),
data,
ejsOptions
}));
} else if (isObject(source)) {
if (Array.isArray(source.arr)) {
const {
arr,
dir = ''
} = source;
this._injectFileAddMiddleware(() => renderArray({
dir: path.join(baseDir, dir),
arr,
data,
ejsOptions
}));
} else {
this._injectFileAddMiddleware(() => renderObject({
dir: baseDir,
obj: source,
data,
ejsOptions
}));
}
} else if (isFunction(source === 'function')) {
this._injectFileAddMiddleware(source);
}
}
remove(source) {
this._injectFileRemoveMiddleware(source);
}
setScriptsKeyOrder(keyOrder) {
this.generator.scriptsKeyOrder = keyOrder;
}
setPkgKeyOrder(keyOrder) {
this.generator.pkgKeyOrder = keyOrder;
}
_spliceKeyOrder(property, keyOrderMap) {
const arr = this.generator[property] || [];
Object.keys(keyOrderMap).forEach(key => {
const index = arr.indexOf(key);
if (index !== -1) {
arr.splice(index + 1, 0, ...keyOrderMap[key]);
} else {
arr.push(...keyOrderMap[key]);
}
});
this.generator[property] = arr;
}
spliceScriptsKeyOrder(keyOrderMap) {
this._spliceKeyOrder('scriptsKeyOrder', keyOrderMap);
}
splicePkgKeyOrder(keyOrderMap) {
this._spliceKeyOrder('pkgKeyOrder', keyOrderMap);
}
/**
* Push a file middleware that will be applied after all normal file
* middelwares have been applied.
*
* @param {FileMiddleware} cb
*/
postProcessFiles(cb) {
this.generator.postProcessFilesCbs.push(cb);
}
/**
* Push a callback to be called when the files have been written to disk.
*
* @param {function} cb
*/
onCreateComplete(cb) {
this.generator.completeCbs.push(cb);
}
}
module.exports = GeneratorAPI;