1 |
|
2 | import fs from 'fs';
|
3 | import path from 'path';
|
4 | import semver from 'semver';
|
5 | import osenv from 'osenv';
|
6 | import spawn from 'cross-spawn';
|
7 | import _ from 'lodash';
|
8 | import LockFileInstance from '../../shared/lockFile';
|
9 | import packageJson from '../../shared/packageJson';
|
10 | import { parseYaml } from '../../shared/yaml';
|
11 | import { setServerUrl } from '../../shared/git';
|
12 | import { UniversalPkg } from '../universal-pkg/dep/pkg';
|
13 | import {
|
14 | HEART_BEAT_COLLECTION,
|
15 | UPDATE_COLLECTION,
|
16 | BEAT_GAP,
|
17 | CHECK_UPDATE_GAP,
|
18 | FEFLOW_ROOT,
|
19 | UNIVERSAL_PKG_JSON,
|
20 | BEAT_KEY,
|
21 | BEAT_LOCK,
|
22 | UPDATE_KEY,
|
23 | UPDATE_LOCK
|
24 | } from '../../shared/constant';
|
25 | import loggerInstance from '../logger';
|
26 | import {
|
27 | getInstalledPlugins,
|
28 | getLatestVersion,
|
29 | getUniversalPluginVersion
|
30 | } from './utils';
|
31 | interface ErrorInstance {
|
32 | name: string;
|
33 | message: string;
|
34 | stack: string;
|
35 | }
|
36 |
|
37 | const pkg = require('../../../package.json');
|
38 | const { getPkgInfo } = require('../native/install');
|
39 | const version = pkg.version;
|
40 |
|
41 | const { debug, silent } = process.env;
|
42 | const root = path.join(osenv.home(), FEFLOW_ROOT);
|
43 | const configPath = path.join(root, '.feflowrc.yml');
|
44 | const universalPkgPath = path.join(root, UNIVERSAL_PKG_JSON);
|
45 | const dbFile = path.join(root, UPDATE_COLLECTION);
|
46 | const updateLock = path.join(root, UPDATE_LOCK);
|
47 | const updateFile = new LockFileInstance(dbFile, updateLock);
|
48 | const heartDBFile = path.join(root, HEART_BEAT_COLLECTION);
|
49 | const beatLock = path.join(root, BEAT_LOCK);
|
50 | const heartFile = new LockFileInstance(heartDBFile, beatLock);
|
51 | const logger = loggerInstance({
|
52 | debug: Boolean(debug),
|
53 | silent: Boolean(silent)
|
54 | });
|
55 |
|
56 |
|
57 | process.title = 'feflow-update-beat-proccess';
|
58 |
|
59 | const handleException = (e: ErrorInstance): void => {
|
60 | logger.error(`update_beat_exception: ${e.name}: ${e.message} => ${e.stack}`);
|
61 | };
|
62 |
|
63 | (process as NodeJS.EventEmitter).on('uncaughtException', handleException);
|
64 |
|
65 | (process as NodeJS.EventEmitter).on('unhandledRejection', handleException);
|
66 |
|
67 | const heartBeat = () => {
|
68 | heartFile.update(BEAT_KEY, String(new Date().getTime()));
|
69 | };
|
70 |
|
71 | const queryCliUpdate = async () => {
|
72 | const config = parseYaml(configPath);
|
73 |
|
74 | if (!config) {
|
75 | return;
|
76 | }
|
77 | if (
|
78 | config['lastUpdateCheck'] &&
|
79 | +new Date() - parseInt(config['lastUpdateCheck'], 10) <= 1000 * 3600 * 24
|
80 | ) {
|
81 | return;
|
82 | }
|
83 |
|
84 | if (config['autoUpdate'] !== 'true') {
|
85 | return;
|
86 | }
|
87 |
|
88 | const latestVersion: any = await getLatestVersion(
|
89 | '@feflow/cli',
|
90 | config['packageManager']
|
91 | );
|
92 | if (latestVersion && semver.gt(latestVersion, version)) {
|
93 | const updateData: any = await updateFile.read(UPDATE_KEY);
|
94 | if (updateData.latest_cli_version !== latestVersion) {
|
95 | const newUpdateData = {
|
96 | ...updateData,
|
97 | latest_cli_version: latestVersion
|
98 | };
|
99 | await updateFile.update(UPDATE_KEY, newUpdateData);
|
100 | }
|
101 | }
|
102 | };
|
103 |
|
104 | const queryPluginsUpdate = async () => {
|
105 | const config = parseYaml(configPath);
|
106 | if (!config) {
|
107 | return;
|
108 | }
|
109 |
|
110 | Promise.all(
|
111 | getInstalledPlugins().map(async (name: any) => {
|
112 | const pluginPath = path.join(root, 'node_modules', name, 'package.json');
|
113 | const content: any = fs.readFileSync(pluginPath);
|
114 | const pkg: any = JSON.parse(content);
|
115 | const localVersion = pkg.version;
|
116 | const registryUrl = spawn
|
117 | .sync(config['packageManager'], ['config', 'get', 'registry'], {
|
118 | windowsHide: true
|
119 | })
|
120 | .stdout.toString()
|
121 | .replace(/\n/, '')
|
122 | .replace(/\/$/, '');
|
123 | const latestVersion = await packageJson(name, registryUrl).catch(
|
124 | (err: any) => {
|
125 | logger.debug('Check plugin update error', err);
|
126 | }
|
127 | );
|
128 |
|
129 | if (latestVersion && semver.gt(latestVersion, localVersion)) {
|
130 | return {
|
131 | name,
|
132 | latestVersion,
|
133 | localVersion
|
134 | };
|
135 | } else {
|
136 | logger.debug('All plugins is in latest version');
|
137 | }
|
138 | })
|
139 | ).then(async (plugins: any) => {
|
140 | plugins = plugins.filter((plugin: any) => {
|
141 | return plugin && plugin.name;
|
142 | });
|
143 | logger.debug('tnpm plugins update infomation', plugins);
|
144 | if (plugins.length) {
|
145 | const updateData: any = await updateFile.read(UPDATE_KEY);
|
146 | if (!_.isEqual(updateData.latest_plugins, plugins)) {
|
147 | const newUpdateData = {
|
148 | ...updateData,
|
149 | latest_plugins: plugins
|
150 | };
|
151 | await updateFile.update(UPDATE_KEY, newUpdateData);
|
152 | }
|
153 | }
|
154 | });
|
155 | };
|
156 |
|
157 | const queryUniversalPluginsUpdate = async () => {
|
158 | const config = parseYaml(configPath);
|
159 | if (!config || !config['serverUrl']) {
|
160 | return;
|
161 | }
|
162 | setServerUrl(config['serverUrl']);
|
163 |
|
164 | const universalPkg = new UniversalPkg(universalPkgPath);
|
165 | const latestUniversalPlugins: any[] = [];
|
166 |
|
167 |
|
168 | for (const [pkg, version] of universalPkg.getInstalled()) {
|
169 | const pkgInfo = await getPkgInfo(
|
170 | { root, config, logger },
|
171 | `${pkg}@${version}`
|
172 | ).catch(async (e: string) => {
|
173 | logger.error(`update_error => pkg: ${pkg}@${version} => error: ${e}`);
|
174 | });
|
175 | if (!pkgInfo) {
|
176 | continue;
|
177 | }
|
178 | const versionObj = await getUniversalPluginVersion(pkgInfo, universalPkg);
|
179 | if (versionObj.latestVersion) {
|
180 | latestUniversalPlugins.push(versionObj);
|
181 | }
|
182 | }
|
183 |
|
184 | logger.debug('universal plugins update infomation', latestUniversalPlugins);
|
185 | if (latestUniversalPlugins.length) {
|
186 | const updateData: any = await updateFile.read(UPDATE_KEY);
|
187 | if (
|
188 | !_.isEqual(updateData.latest_universal_plugins, latestUniversalPlugins)
|
189 | ) {
|
190 | const newUpdateData = {
|
191 | ...updateData,
|
192 | latest_universal_plugins: latestUniversalPlugins
|
193 | };
|
194 | await updateFile.update(UPDATE_KEY, newUpdateData);
|
195 | }
|
196 | }
|
197 | };
|
198 |
|
199 |
|
200 | setInterval(heartBeat, BEAT_GAP);
|
201 |
|
202 |
|
203 | setInterval(() => {
|
204 | queryCliUpdate().catch(handleException);
|
205 | }, CHECK_UPDATE_GAP);
|
206 |
|
207 |
|
208 | setInterval(() => {
|
209 | queryPluginsUpdate().catch(handleException);
|
210 | }, CHECK_UPDATE_GAP);
|
211 |
|
212 |
|
213 | setInterval(() => {
|
214 | queryUniversalPluginsUpdate().catch(handleException);
|
215 | }, CHECK_UPDATE_GAP);
|