UNPKG

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