1 |
|
2 | import path from 'path';
|
3 | import { spawn } from 'child_process';
|
4 | import chalk from 'chalk';
|
5 | import inquirer from 'inquirer';
|
6 | import semver from 'semver';
|
7 | import Table from 'easy-table';
|
8 | import DBInstance from '../../shared/db';
|
9 | import {
|
10 | HEART_BEAT_COLLECTION,
|
11 | UPDATE_COLLECTION,
|
12 | BEAT_GAP,
|
13 | CHECK_UPDATE_GAP
|
14 | } from '../../shared/constant';
|
15 | import { safeDump } from '../../shared/yaml';
|
16 |
|
17 | const updateBeatProcess = path.join(__dirname, './updateBeat');
|
18 | const updateProcess = path.join(__dirname, './update');
|
19 | const isSilent = process.argv.slice(3).includes('--slient');
|
20 | const disableCheck = process.argv.slice(3).includes('--disable-check');
|
21 | let db: DBInstance;
|
22 | let heartDB: DBInstance;
|
23 | const table = new Table();
|
24 | const uTable = new Table();
|
25 |
|
26 | function startUpdateBeat(ctx: any) {
|
27 | const child = spawn(process.argv[0], [updateBeatProcess], {
|
28 | detached: true,
|
29 | stdio: 'ignore',
|
30 | env: {
|
31 | ...process.env,
|
32 | debug: ctx.args.debug,
|
33 | silent: ctx.args.silent
|
34 | },
|
35 | windowsHide: true
|
36 | });
|
37 |
|
38 |
|
39 | child.unref();
|
40 | }
|
41 |
|
42 | function startUpdate(ctx: any, cacheValidate: any, latestVersion: any) {
|
43 | const child = spawn(process.argv[0], [updateProcess], {
|
44 | detached: true,
|
45 | stdio: 'ignore',
|
46 | env: {
|
47 | ...process.env,
|
48 | debug: ctx.args.debug,
|
49 | silent: ctx.args.silent,
|
50 | cacheValidate,
|
51 | latestVersion
|
52 | },
|
53 | windowsHide: true
|
54 | });
|
55 |
|
56 |
|
57 | child.unref();
|
58 | }
|
59 |
|
60 | async function _checkUpdateMsg(ctx: any, updateData: any = {}) {
|
61 | const updateError = await db.read('update_error');
|
62 | const exception = await db.read('exception');
|
63 |
|
64 | const _showCliUpdateM = () => {
|
65 | const updateMsg = updateData['cli_update_msg'];
|
66 | if (updateMsg) {
|
67 | const { version, latestVersion } = updateMsg;
|
68 | ctx.logger.info(
|
69 | `@feflow/cil has been updated from ${version} to ${latestVersion}. Enjoy it.`
|
70 | );
|
71 | updateData['cli_update_msg'] = '';
|
72 | }
|
73 | };
|
74 |
|
75 | const _showPluginsUpdateM = () => {
|
76 | const updatePkg = updateData['plugins_update_msg'];
|
77 | if (updatePkg) {
|
78 | updatePkg.forEach((pkg: any) => {
|
79 | const { name, localVersion, latestVersion } = pkg;
|
80 | table.cell('Name', name);
|
81 | table.cell(
|
82 | 'Version',
|
83 | localVersion === latestVersion
|
84 | ? localVersion
|
85 | : localVersion + ' -> ' + latestVersion
|
86 | );
|
87 | table.cell('Tag', 'latest');
|
88 | table.cell('Update', localVersion === latestVersion ? 'N' : 'Y');
|
89 | table.newRow();
|
90 | });
|
91 |
|
92 | ctx.logger.info(
|
93 | 'Your local templates or plugins has been updated last time.'
|
94 | );
|
95 | if (!isSilent) console.log(table.toString());
|
96 |
|
97 | updateData['plugins_update_msg'] = '';
|
98 | }
|
99 | };
|
100 |
|
101 | const _showUniversalPluginsM = () => {
|
102 | const updatePkg = updateData['universal_plugins_update_msg'];
|
103 |
|
104 | if (updatePkg) {
|
105 | updatePkg.forEach((pkg: any) => {
|
106 | const { name, localVersion, latestVersion } = pkg;
|
107 | uTable.cell('Name', name);
|
108 | uTable.cell(
|
109 | 'Version',
|
110 | localVersion === latestVersion
|
111 | ? localVersion
|
112 | : localVersion + ' -> ' + latestVersion
|
113 | );
|
114 | uTable.cell('Tag', 'latest');
|
115 | uTable.cell('Update', localVersion === latestVersion ? 'N' : 'Y');
|
116 | uTable.newRow();
|
117 | });
|
118 |
|
119 | ctx.logger.info(
|
120 | 'Your local universal plugins has been updated last time.'
|
121 | );
|
122 | if (!isSilent) console.log(uTable.toString());
|
123 |
|
124 | updateData['universal_plugins_update_msg'] = '';
|
125 | }
|
126 | };
|
127 |
|
128 | const _showErrorM = () => {
|
129 | const errorMsg = updateError?.['value'] || {};
|
130 | const errorKeys = Object.keys(errorMsg);
|
131 |
|
132 | if (errorKeys.length) {
|
133 | ctx.logger.warn('Some problems occurred while auto-updating');
|
134 | errorKeys.forEach(key => {
|
135 | ctx.logger.error(`${key}: ${errorMsg[key]}`);
|
136 | });
|
137 | ctx.logger.warn(
|
138 | 'These templates or plugins need to be updated manually util problems fixed'
|
139 | );
|
140 | }
|
141 |
|
142 | const exceptionMsg = exception?.['value'];
|
143 | if (exceptionMsg) {
|
144 | ctx.logger.error('Excetion exists in auto-updating:');
|
145 | ctx.logger.error(exceptionMsg);
|
146 | ctx.logger.warn('Auto-updating will not work util exception fixed');
|
147 | }
|
148 | };
|
149 |
|
150 |
|
151 | _showCliUpdateM();
|
152 | _showPluginsUpdateM();
|
153 | _showUniversalPluginsM();
|
154 | _showErrorM();
|
155 |
|
156 | await db.update('update_data', updateData);
|
157 | await db.update('update_error', '');
|
158 | await db.update('exception', '');
|
159 | }
|
160 |
|
161 | async function _checkLock(updateData: any) {
|
162 | const updateLock = updateData?.['update_lock'];
|
163 | const nowTime = new Date().getTime();
|
164 | if (
|
165 | updateLock &&
|
166 | updateLock['time'] &&
|
167 | nowTime - updateLock['time'] < CHECK_UPDATE_GAP
|
168 | ) {
|
169 | return true;
|
170 | } else {
|
171 | updateData['update_lock'] = {
|
172 | time: String(nowTime),
|
173 | pid: process.pid
|
174 | };
|
175 | await db.update('update_data', updateData);
|
176 |
|
177 |
|
178 | let nowUpdateData = await db.read('update_data');
|
179 | nowUpdateData = nowUpdateData?.['value'];
|
180 | const nowUpdateLock = nowUpdateData?.['update_lock'];
|
181 | if (nowUpdateLock && nowUpdateLock['pid'] !== process.pid) {
|
182 | return true;
|
183 | }
|
184 | }
|
185 | return false;
|
186 | }
|
187 |
|
188 | export async function checkUpdate(ctx: any) {
|
189 | const dbFile = path.join(ctx.root, UPDATE_COLLECTION);
|
190 | const autoUpdate =
|
191 | ctx.args['auto-update'] || String(ctx.config.autoUpdate) === 'true';
|
192 | const nowTime = new Date().getTime();
|
193 | let latestVersion: any = '';
|
194 | let cacheValidate = false;
|
195 |
|
196 | if (!db) {
|
197 | db = new DBInstance(dbFile);
|
198 | }
|
199 |
|
200 | const heartDBFile = path.join(ctx.root, HEART_BEAT_COLLECTION);
|
201 | if (!heartDB) {
|
202 | heartDB = new DBInstance(heartDBFile);
|
203 | }
|
204 |
|
205 | let updateData = await db.read('update_data');
|
206 | updateData = updateData?.['value'];
|
207 | if (updateData) {
|
208 |
|
209 | const isLocked = await _checkLock(updateData);
|
210 | if (isLocked) return ctx.logger.debug('one updating process is running');
|
211 |
|
212 | await _checkUpdateMsg(ctx, updateData);
|
213 |
|
214 | const data = await heartDB.read('beat_time');
|
215 | if (data) {
|
216 | const lastBeatTime = parseInt(data['value'], 10);
|
217 |
|
218 | cacheValidate = nowTime - lastBeatTime <= BEAT_GAP;
|
219 | ctx.logger.debug(`heart-beat process cache validate ${cacheValidate}`);
|
220 |
|
221 | if (!cacheValidate) {
|
222 |
|
223 | startUpdateBeat(ctx);
|
224 | }
|
225 |
|
226 | latestVersion = updateData['latest_cli_version'];
|
227 | }
|
228 | } else {
|
229 |
|
230 | ctx.logger.debug('init heart-beat for update detective');
|
231 | await Promise.all([
|
232 |
|
233 | heartDB.create('beat_time', String(nowTime)),
|
234 | db.create('update_data', {
|
235 |
|
236 | latest_cli_version: '',
|
237 | latest_plugins: '',
|
238 | latest_universal_plugins: '',
|
239 |
|
240 | cli_update_msg: '',
|
241 | plugins_update_msg: '',
|
242 | universal_plugins_update_msg: '',
|
243 |
|
244 | update_lock: {
|
245 | time: String(nowTime),
|
246 | pid: process.pid
|
247 | }
|
248 | }),
|
249 | db.create('update_error', ''),
|
250 | db.create('exception', '')
|
251 | ]);
|
252 | startUpdateBeat(ctx);
|
253 | }
|
254 |
|
255 |
|
256 | if (!disableCheck && latestVersion && semver.gt(latestVersion, ctx.version)) {
|
257 | ctx.logger.debug(
|
258 | `Find new version, current version: ${ctx.version}, latest version: ${latestVersion}`
|
259 | );
|
260 | if (autoUpdate) {
|
261 | ctx.logger.debug(
|
262 | `Feflow will auto update version from ${ctx.version} to ${latestVersion}.`
|
263 | );
|
264 | ctx.logger.debug('Update message will be shown next time.');
|
265 | return startUpdate(ctx, cacheValidate, latestVersion);
|
266 | }
|
267 |
|
268 | const askIfUpdateCli = [
|
269 | {
|
270 | type: 'confirm',
|
271 | name: 'ifUpdate',
|
272 | message: `${chalk.yellow(
|
273 | `@feflow/cli's latest version is ${chalk.green(
|
274 | `${latestVersion}`
|
275 | )}, but your current version is ${chalk.red(
|
276 | `${ctx.version}`
|
277 | )}. Do you want to update it?`
|
278 | )}`,
|
279 | default: true
|
280 | }
|
281 | ];
|
282 | const answer = await inquirer.prompt(askIfUpdateCli);
|
283 | if (answer.ifUpdate) {
|
284 | ctx.logger.debug(
|
285 | `Feflow will update from version ${ctx.version} to ${latestVersion}.`
|
286 | );
|
287 | ctx.logger.debug('Update message will be shown next time.');
|
288 | return startUpdate(ctx, cacheValidate, latestVersion);
|
289 | } else {
|
290 | safeDump(
|
291 | {
|
292 | ...ctx.config,
|
293 | lastUpdateCheck: +new Date()
|
294 | },
|
295 | ctx.configPath
|
296 | );
|
297 | }
|
298 | } else {
|
299 | ctx.logger.debug(`Current cli version is already latest.`);
|
300 | return startUpdate(ctx, cacheValidate, '');
|
301 | }
|
302 | }
|