UNPKG

4.34 kBJavaScriptView Raw
1/**
2 * @file template 项目模板工具
3 * @author tracy (qiushidev@gmail.com) clark-t (clarktanglei@163.com)
4 */
5
6const cli = require('../cli')
7const path = require('path')
8const exists = require('fs').existsSync
9const ora = require('ora')
10const home = require('user-home')
11const rm = require('rimraf').sync
12const download = require('download-git-repo')
13
14const async = require('async')
15const ms = require('metalsmith')
16const inquirer = require('inquirer')
17const render = require('./render').render
18const getMeta = require('./meta')
19
20const OFFICIAL_VUE_TEMPLATE = 'mipengine/mip-cli-template#vue'
21const OFFICIAL_TEMPLATE = 'mipengine/mip-cli-template'
22
23const VUE_TEMPLATE_TMP = path.resolve(home, '.mip-vue-template')
24const TEMPLATE_TMP = path.resolve(home, '.mip-template')
25
26function downloadRepo (isVue, done) {
27 if (typeof isVue === 'function') {
28 done = isVue
29 isVue = false
30 }
31
32 let template
33 let tmp
34
35 if (isVue) {
36 template = OFFICIAL_VUE_TEMPLATE
37 // 本地临时目录
38 tmp = VUE_TEMPLATE_TMP
39 } else {
40 template = OFFICIAL_TEMPLATE
41 tmp = TEMPLATE_TMP
42 }
43
44 const spinner = ora('正在获取最新模板')
45 spinner.start()
46
47 // 先清空临时目录
48 if (exists(tmp)) {
49 rm(tmp)
50 }
51
52 // 下载默认模板到临时目录
53 download(template, tmp, {clone: false}, err => {
54 spinner.stop()
55 if (err) {
56 cli.error('Failed to download repo: ' + err.message.trim())
57 return
58 }
59
60 done()
61 })
62}
63
64/**
65 * 生成项目 or 组件结构
66 *
67 * @param {string} dir 渲染模板的目录
68 * @param {string} compName 组件名称,仅用于只渲染组件的情况,渲染整个项目不传即可
69 * @param {Function} done 回调函数
70 */
71function generate (dir, compName, isVue, done) {
72 if (typeof isVue === 'function') {
73 done = isVue
74 isVue = false
75 }
76
77 let tmp = isVue ? VUE_TEMPLATE_TMP : TEMPLATE_TMP
78 const metalsmith = ms(path.join(tmp, dir))
79 const templatePrompts = getMeta(tmp).prompts
80
81 metalsmith
82 .use(ask(templatePrompts))
83 .use(renderTemplateFiles())
84 .clean(false)
85 .source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
86 .build(err => {
87 done(err)
88 })
89
90 function ask (templatePrompts) {
91 return (files, metalsmith, done) => {
92 // 替换 components 目录组件名称
93 metalsmith.metadata().compName = compName || 'mip-example'
94
95 // 只渲染组件部分时(mip2 add),不需要走 ask 流程,且使用 component name 作为 dest 路径
96 if (compName) {
97 metalsmith.destination(path.resolve('./components', compName))
98 // 读取项目名称,用于渲染 README.md 的脚本地址
99 let siteName = process.cwd().split(path.sep).pop()
100 metalsmith.metadata().name = siteName
101 return done()
102 }
103
104 async.eachSeries(Object.keys(templatePrompts), run, () => {
105 // After all inquirer finished, set destination directory with input project name
106 metalsmith.destination(path.resolve('./', metalsmith.metadata().name))
107 done()
108 })
109
110 function run (key, done) {
111 let prompt = templatePrompts[key]
112
113 inquirer.prompt([{
114 'type': prompt.type,
115 'name': key,
116 'message': prompt.message || prompt.label || key,
117 'default': prompt.default,
118 'validate': prompt.validate || (() => true)
119 }]).then(answers => {
120 if (typeof answers[key] === 'string') {
121 metalsmith.metadata()[key] = answers[key].replace(/"/g, '\\"')
122 } else {
123 metalsmith.metadata()[key] = answers[key]
124 }
125 done()
126 }).catch(done)
127 }
128 }
129 }
130
131 function renderTemplateFiles () {
132 return (files, metalsmith, done) => {
133 let keys = Object.keys(files)
134 async.each(keys, run, done)
135
136 function run (file, done) {
137 let str = files[file].contents.toString()
138
139 // do not attempt to render files that do not have mustaches
140 if (!/{{([^{}]+)}}/g.test(str)) {
141 return done()
142 }
143
144 let res
145 try {
146 res = render(str, metalsmith.metadata())
147 } catch (err) {
148 return done(err)
149 }
150
151 files[file].contents = Buffer.from(res)
152 done()
153 }
154 }
155 }
156}
157
158module.exports = {
159 downloadRepo,
160 generate
161}