Files
vue-cli/packages/@vue/cli/lib/GeneratorAPI.js

127 lines
4.0 KiB
JavaScript

const fs = require('fs')
const ejs = require('ejs')
const path = require('path')
const globby = require('globby')
const isBinary = require('isbinaryfile')
const mergeDeps = require('./util/mergeDeps')
const isString = val => typeof val === 'string'
const isFunction = val => typeof val === 'function'
const isObject = val => val && typeof val === 'object'
module.exports = class GeneratorAPI {
constructor (id, generator, options, rootOptions) {
this.id = id
this.generator = generator
this.options = options
this.rootOptions = rootOptions
}
injectFileMiddleware (middleware) {
this.generator.fileMiddlewares.push(middleware)
}
extendPackage (fields, options = { merge: true }) {
const pkg = this.generator.pkg
const toMerge = isFunction(fields) ? fields(pkg) : fields
for (const key in toMerge) {
const value = toMerge[key]
const existing = pkg[key]
if (isObject(value) && (key === 'dependencies' || key === 'devDependencies')) {
// use special version resolution merge
pkg[key] = mergeDeps(
this.id,
existing || {},
value,
this.generator.depSources
)
} else if (!options.merge || !(key in pkg)) {
pkg[key] = value
} else if (Array.isArray(value) && Array.isArray(existing)) {
pkg[key] = existing.concat(value)
} else if (isObject(value) && isObject(existing)) {
pkg[key] = Object.assign({}, existing, value)
} else {
pkg[key] = value
}
}
}
render (fileDir, additionalData = {}, ejsOptions = {}) {
const baseDir = extractCallDir()
if (isString(fileDir)) {
fileDir = path.resolve(baseDir, fileDir)
this.injectFileMiddleware(async (files) => {
const data = Object.assign({
options: this.options,
rootOptions: this.rootOptions
}, additionalData)
const _files = await globby(['**/*'], { cwd: fileDir })
for (const rawPath of _files) {
let filename = path.basename(rawPath)
// dotfiles are ignored when published to npm, therefore in templates
// we need to use underscore instead (e.g. "_gitignore")
if (filename.charAt(0) === '_') {
filename = `.${filename.slice(1)}`
}
const targetPath = path.join(path.dirname(rawPath), filename)
const sourcePath = path.resolve(fileDir, rawPath)
const content = renderFile(sourcePath, data, ejsOptions)
// only set file if it's not all whitespace, or is a Buffer (binary files)
if (Buffer.isBuffer(content) || /[^\s]/.test(content)) {
files[targetPath] = content
}
}
})
} else if (isObject(fileDir)) {
this.injectFileMiddleware(files => {
const data = Object.assign({
options: this.options,
rootOptions: this.rootOptions
}, additionalData)
for (const targetPath in fileDir) {
const sourcePath = path.resolve(baseDir, fileDir[targetPath])
const content = renderFile(sourcePath, data, ejsOptions)
if (Buffer.isBuffer(content) || content.trim()) {
files[targetPath] = content
}
}
})
} else if (isFunction(fileDir)) {
this.injectFileMiddleware(fileDir)
}
}
postProcessFiles (cb) {
this.generator.postProcessFilesCbs.push(cb)
}
onCreateComplete (cb) {
this.generator.completeCbs.push(cb)
}
resolve (_path) {
return path.resolve(this.generator.context, _path)
}
hasPlugin (id) {
return this.generator.hasPlugin(id)
}
}
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)
}
function renderFile (name, data, ejsOptions) {
if (isBinary.sync(name)) {
return fs.readFileSync(name) // return buffer
}
return ejs.render(fs.readFileSync(name, 'utf-8'), data, ejsOptions)
}