1 | const fs = require('fs-extra')
|
2 | const path = require('path')
|
3 | const { exec, spawn } = require('child_process')
|
4 | const cwd = process.cwd()
|
5 |
|
6 | const program = require('commander')
|
7 | const inquirer = require('inquirer')
|
8 | const ora = require('ora')
|
9 | const semver = require('semver')
|
10 | const klaw = require('klaw-sync')
|
11 |
|
12 | const { log } = require('../lib/log')
|
13 | const registerLogger = require('../lib/register-logger')
|
14 | const merge = require('../lib/merge')
|
15 | const pacote = require('pacote')
|
16 | const getConfig = require('../lib/get-config')
|
17 | const { resolveCwd } = require('../lib/utils')
|
18 |
|
19 | program
|
20 | .option('-a, --all', '更新全部')
|
21 | .parse(process.argv)
|
22 |
|
23 | if (program.all) {
|
24 | updateAll()
|
25 | } else {
|
26 | updateFiles()
|
27 | }
|
28 |
|
29 | registerLogger('update', process)
|
30 | process.on('exit', () => {
|
31 | fs.removeSync(resolveCwd('temp'))
|
32 | })
|
33 |
|
34 | async function updateAll () {
|
35 | updateFiles()
|
36 | await updateDeps('--save', 'i-tofu')
|
37 | await updateDeps('-g', 'tofu-cli')
|
38 | }
|
39 |
|
40 | async 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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
218 | function 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 | }
|