UNPKG

6.77 kBJavaScriptView Raw
1const fs = require('fs-extra')
2const path = require('path')
3const { exec, spawn } = require('child_process')
4const cwd = process.cwd()
5
6const program = require('commander')
7const inquirer = require('inquirer')
8const ora = require('ora')
9const semver = require('semver')
10const klaw = require('klaw-sync')
11
12const { log } = require('../lib/log')
13const registerLogger = require('../lib/register-logger')
14const merge = require('../lib/merge')
15const pacote = require('pacote')
16const getConfig = require('../lib/get-config')
17const { resolveCwd } = require('../lib/utils')
18
19program
20 .option('-a, --all', '更新全部')
21 .parse(process.argv)
22
23if (program.all) {
24 updateAll()
25} else {
26 updateFiles()
27}
28
29registerLogger('update', process)
30process.on('exit', () => {
31 fs.removeSync(resolveCwd('temp'))
32})
33
34async function updateAll () {
35 updateFiles()
36 await updateDeps('--save', 'i-tofu')
37 await updateDeps('-g', 'tofu-cli')
38}
39
40async function updateFiles () {
41 const spinner = ora('正在做更新前检查...').start()
42
43 await checkGit()
44
45 try {
46 await pacote.extract(
47 'pitaya',
48 './temp',
49 { registry: 'http://registry.npm.taobao.org' }
50 )
51 } catch (err) {
52 console.error('下载模板出错,请重试')
53 console.error(err)
54 }
55
56 // Load need update list
57 let needUpdateFiles
58 needUpdateFiles = loadList()
59 if (!needUpdateFiles.length) {
60 spinner.stop()
61 console.info('没有需要更新的文件')
62 return
63 }
64
65 spinner.stop()
66 const { ignoreFiles } = await inquirer.prompt([
67 {
68 type: 'checkbox',
69 message: '确认需要更新的文件(勾选取消更新)',
70 name: 'ignoreFiles',
71 choices: needUpdateFiles.map(file => {
72 return { name: file }
73 }),
74 pageSize: 20
75 }
76 ]).catch(err => {
77 console.err(err)
78 })
79
80 needUpdateFiles = reduceList(needUpdateFiles, ignoreFiles)
81 let needDeleteFiles = []
82 let fatalList = []
83 needUpdateFiles = needUpdateFiles.reduce((rst, file) => {
84 // 处理需要删除的文件
85 if (/^\!/.test(file)) {
86 needDeleteFiles.push(file)
87 return rst
88 }
89
90 const target = resolveCwd(file)
91 const src = resolveCwd('temp', file)
92
93 try {
94 fs.lstatSync(src).isFile()
95 ? rst.push(file)
96 : (rst = rst.concat(klaw(target).map(obj => {
97 return path.relative(cwd, obj.path).replace(/^temp\//, '')
98 })))
99 } catch (err) {
100 fatalList.push(target)
101 }
102
103 return rst
104 }, [])
105
106 // traverse, merge and detect conflict
107 spinner.start('正在更新文件...')
108 let conflictFiles = []
109 needUpdateFiles.forEach(file => {
110 try {
111 const src = resolveCwd('temp', file)
112 const target = resolveCwd(file)
113 const empty = resolveCwd('temp', 'any')
114 fs.ensureFileSync(empty)
115 const result = merge(target, empty, src)
116 if (!result) conflictFiles.push(file)
117 } catch (err) {
118 fatalList.push(file)
119 }
120 })
121 needDeleteFiles.forEach(file => {
122 fs.removeSync(resolveCwd(file))
123 })
124 spinner.succeed('更新文件完成').stop()
125 if (conflictFiles.length)
126 log('\n以下文件有冲突,请手动修复:', 'red')
127 conflictFiles.forEach(f => log(` ${f}`, 'red'))
128
129 if (fatalList.length)
130 log('\n以下文件更新失败,请手动更新:', 'red')
131 fatalList.forEach(f => log(` ${f}`, 'red'))
132
133 /**
134 * Check working tree if is clear
135 */
136 function checkGit () {
137 return new Promise((resolve, reject) => {
138 const child = spawn('git', ['status'])
139
140 child.stdout.on('data', data => {
141 if (!~data.toString().indexOf('working tree clean')) {
142 throw new Error('请先提交尚未提交的修改')
143 }
144 })
145
146 child.stderr.on('data', data => {
147 console.error(data)
148 })
149
150 child.on('close', code => {
151 resolve()
152 })
153 })
154 }
155
156 /**
157 * Load needed files list.
158 */
159 function loadList () {
160 let rst
161 let tofurc
162
163 try {
164 tofurc = require(resolveCwd('temp/.tofurc.js'))
165 } catch (err) {
166 console.error('读取更新文件列表出错,请重试')
167 console.error(err)
168 }
169
170 const config = getConfig()
171 if (!config) return tofurc.updateList
172
173 const newUpdateList = tofurc.updateList
174 const oldUpdateList = config.updateList
175
176 if (oldUpdateList && !Array.isArray(oldUpdateList))
177 throw new Error('.tofurc 中的 ignore 选项必须是数组类型')
178
179 if (tofurc && newUpdateList && Array.isArray(newUpdateList)) {
180 if (tofurc._meta && config._meta) {
181 if (tofurc._meta.type !== config._meta.type) return []
182
183 const newVersion = semver.valid(tofurc._meta.version)
184 const oldVersion = semver.valid(config._meta.version)
185 if (!newVersion || !oldVersion) throw new Error('配置文件版本信息无效')
186
187 if (semver.gt(newVersion, oldVersion)) {
188 if (semver.minor(newVersion) > semver.minor(oldVersion)
189 || semver.major(newVersion) > semver.major(oldVersion)
190 ) {
191 return newUpdateList
192 } else {
193 return oldUpdateList
194 }
195 } else {
196 return []
197 }
198 } else {
199 throw new Error('配置文件缺少 _meta')
200 }
201 } else {
202 return []
203 }
204 }
205
206 function reduceList (origin, ignore) {
207 console.assert(Array.isArray(origin) && Array.isArray(ignore),
208 'It must be array type'
209 )
210
211 return origin.reduce((pv, cv) => {
212 if (!~ignore.indexOf(cv)) pv.push(cv)
213 return pv
214 }, [])
215 }
216}
217
218function updateDeps (dep, option) {
219 const spinner = ora(`正在更新 ${dep} ...`).start()
220 return Promise((resolve, reject) => {
221 const child = spawn(`npm install ${option} ${dep}`)
222
223 child.stdout.on('data', data => {
224 console.log(`stdout: ${data}`)
225 })
226
227 child.stderr.on('data', data => {
228 console.error(data)
229 })
230
231 child.on('close', code => {
232 spinner.succeed(`更新 ${dep} 成功`).stop()
233 resolve()
234 })
235 })
236}