UNPKG

4.26 kBJavaScriptView Raw
1// Adapted from Vue CLI v2 "init" command
2
3const chalk = require('chalk')
4const Metalsmith = require('metalsmith')
5const Handlebars = require('handlebars')
6const async = require('async')
7const render = require('consolidate').handlebars.render
8const path = require('path')
9const multimatch = require('multimatch')
10
11const getOptions = require('./options')
12const ask = require('./ask')
13const filter = require('./filter')
14const logger = require('./logger')
15
16// register handlebars helper
17Handlebars.registerHelper('if_eq', function (a, b, opts) {
18 return a === b
19 ? opts.fn(this)
20 : opts.inverse(this)
21})
22
23Handlebars.registerHelper('unless_eq', function (a, b, opts) {
24 return a === b
25 ? opts.inverse(this)
26 : opts.fn(this)
27})
28
29/**
30 * Generate a template given a `src` and `dest`.
31 *
32 * @param {String} name
33 * @param {String} src
34 * @param {String} dest
35 * @param {Function} done
36 */
37
38module.exports = function generate (name, src, dest, done) {
39 const opts = getOptions(name, src)
40 const metalsmith = Metalsmith(path.join(src, 'template'))
41 const data = Object.assign(metalsmith.metadata(), {
42 destDirName: name,
43 inPlace: dest === process.cwd(),
44 noEscape: true
45 })
46 opts.helpers && Object.keys(opts.helpers).map(key => {
47 Handlebars.registerHelper(key, opts.helpers[key])
48 })
49
50 const helpers = { chalk, logger }
51
52 if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
53 opts.metalsmith.before(metalsmith, opts, helpers)
54 }
55
56 metalsmith.use(askQuestions(opts.prompts))
57 .use(filterFiles(opts.filters))
58 .use(renderTemplateFiles(opts.skipInterpolation))
59
60 if (typeof opts.metalsmith === 'function') {
61 opts.metalsmith(metalsmith, opts, helpers)
62 }
63 else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
64 opts.metalsmith.after(metalsmith, opts, helpers)
65 }
66
67 metalsmith.clean(false)
68 .source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
69 .destination(dest)
70 .build((err, files) => {
71 done(err)
72 if (typeof opts.complete === 'function') {
73 const helpers = { chalk, logger, files }
74 opts.complete(data, helpers)
75 }
76 else {
77 logMessage(opts.completeMessage, data)
78 }
79 })
80
81 return data
82}
83
84/**
85 * Create a middleware for asking questions.
86 *
87 * @param {Object} prompts
88 * @return {Function}
89 */
90
91function askQuestions (prompts) {
92 return (files, metalsmith, done) => {
93 ask(prompts, metalsmith.metadata(), done)
94 }
95}
96
97/**
98 * Create a middleware for filtering files.
99 *
100 * @param {Object} filters
101 * @return {Function}
102 */
103
104function filterFiles (filters) {
105 return (files, metalsmith, done) => {
106 filter(files, filters, metalsmith.metadata(), done)
107 }
108}
109
110/**
111 * Template in place plugin.
112 *
113 * @param {Object} files
114 * @param {Metalsmith} metalsmith
115 * @param {Function} done
116 */
117
118function renderTemplateFiles (skipInterpolation) {
119 skipInterpolation = typeof skipInterpolation === 'string'
120 ? [skipInterpolation]
121 : skipInterpolation
122 return (files, metalsmith, done) => {
123 const keys = Object.keys(files)
124 const metalsmithMetadata = metalsmith.metadata()
125 async.each(keys, (file, next) => {
126 // skipping files with skipInterpolation option
127 if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
128 return next()
129 }
130 const str = files[file].contents.toString()
131 // do not attempt to render files that do not have mustaches
132 if (!/{{([^{}]+)}}/g.test(str)) {
133 return next()
134 }
135 render(str, metalsmithMetadata, (err, res) => {
136 if (err) {
137 err.message = `[${file}] ${err.message}`
138 return next(err)
139 }
140 files[file].contents = Buffer.from(res)
141 next()
142 })
143 }, done)
144 }
145}
146
147/**
148 * Display template complete message.
149 *
150 * @param {String} message
151 * @param {Object} data
152 */
153
154function logMessage (message, data) {
155 if (!message) return
156 render(message, data, (err, res) => {
157 if (err) {
158 console.error('\n Error when rendering template complete message: ' + err.message.trim())
159 }
160 else {
161 console.log('\n' + res.split(/\r?\n/g).map(line => ' ' + line).join('\n'))
162 }
163 })
164}