UNPKG

zoro-cli

Version:

https://github.com/vuejs/vue-cli

245 lines (224 loc) 6.57 kB
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 { 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