UNPKG

@feflow/cli

Version:
401 lines 22 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkUpdate = void 0; /* eslint-disable no-param-reassign */ var path_1 = __importDefault(require("path")); var child_process_1 = require("child_process"); var chalk_1 = __importDefault(require("chalk")); var lockfile_1 = __importDefault(require("lockfile")); var inquirer_1 = __importDefault(require("inquirer")); var semver_1 = __importDefault(require("semver")); var easy_table_1 = __importDefault(require("easy-table")); var lock_file_1 = __importDefault(require("../../shared/lock-file")); var constant_1 = require("../../shared/constant"); var process_1 = require("../../shared/process"); var yaml_1 = require("../../shared/yaml"); var file_1 = require("../../shared/file"); var pm2_1 = require("./pm2"); var fs_1 = require("../../shared/fs"); var updateBeatScriptPath = path_1.default.join(__dirname, './update-beat.js'); var updateScriptPath = path_1.default.join(__dirname, './update'); var isSilent = process.argv.slice(3).includes(constant_1.SILENT_ARG); var disableCheck = process.argv.slice(3).includes(constant_1.DISABLE_ARG); var updateFile; var heartFile; var table = new easy_table_1.default(); var uTable = new easy_table_1.default(); /** * 使用pm2创建异步心跳子进程 * * @param ctx Feflow实例 */ function startUpdateBeat(ctx) { /** * pm2 启动参数 */ var options = { script: updateBeatScriptPath, name: 'feflow-update-beat-process', env: __assign(__assign({}, process.env), { debug: ctx.args.debug, silent: ctx.args.silent }), // 由于心跳进程会不断写日志导致pm2日志文件过大,而且对于用户来说并关心心跳进程的日志,对于开发同学可以通过pm2 log来查看心跳进程的日志 error_file: '/dev/null', out_file: '/dev/null', pid_file: "".concat(constant_1.FEFLOW_HOME, "/.pm2/pid/app-pm_id.pid"), }; /** * pm2 启动回调 */ var pm2StartCallback = function (pm2) { return function (err) { if (err) { ctx.logger.error('launch update beat pm2 process failed', err); } return pm2.disconnect(); }; }; (0, pm2_1.createPm2Process)(ctx, options, pm2StartCallback); } /** * 利用spawn创建异步更新子进程 * * 不使用pm2的原因:更新子进程并不是常驻子进程,在运行fef命令时如果有更新才会去创建进程进行更新 * * @param ctx Feflow实例 * @param cacheValidate 缓存是否有效 * @param latestVersion 最新版本 */ function startUpdate(ctx, cacheValidate, latestVersion) { var child = (0, child_process_1.spawn)(process.argv[0], [updateScriptPath], { detached: true, stdio: 'ignore', env: __assign(__assign({}, process.env), { debug: ctx.args.debug, silent: ctx.args.silent, cacheValidate: String(cacheValidate), latestVersion: latestVersion }), windowsHide: true, }); // 父进程不会等待子进程 child.unref(); } function checkUpdateMsg(ctx, updateData) { return __awaiter(this, void 0, void 0, function () { var showCliUpdateM, showPluginsUpdateM, showUniversalPluginsM; return __generator(this, function (_a) { showCliUpdateM = function () { var updateMsg = updateData.cli_update_msg; if (updateMsg) { var version = updateMsg.version, latestVersion = updateMsg.latestVersion; ctx.logger.info("@feflow/cil has been updated from ".concat(version, " to ").concat(latestVersion, ". Enjoy it.")); updateData.cli_update_msg = undefined; } }; showPluginsUpdateM = function () { var updatePkg = updateData.plugins_update_msg; if (updatePkg) { updatePkg.forEach(function (pkg) { var name = pkg.name, localVersion = pkg.localVersion, latestVersion = pkg.latestVersion; table.cell('Name', name); table.cell('Version', localVersion === latestVersion ? localVersion : "".concat(localVersion, " -> ").concat(latestVersion)); table.cell('Tag', 'latest'); table.cell('Update', localVersion === latestVersion ? 'N' : 'Y'); table.newRow(); }); ctx.logger.info('Your local templates or plugins has been updated last time. This will not affect your work at hand, just enjoy it.'); if (!isSilent) console.log(table.toString()); updateData.plugins_update_msg = undefined; } }; showUniversalPluginsM = function () { var updatePkg = updateData.universal_plugins_update_msg; if (updatePkg) { updatePkg.forEach(function (pkg) { var name = pkg.name, localVersion = pkg.localVersion, latestVersion = pkg.latestVersion; uTable.cell('Name', name); uTable.cell('Version', localVersion === latestVersion ? localVersion : "".concat(localVersion, " -> ").concat(latestVersion)); uTable.cell('Tag', 'latest'); uTable.cell('Update', localVersion === latestVersion ? 'N' : 'Y'); uTable.newRow(); }); ctx.logger.info('Your local universal plugins has been updated last time. This will not affect your work at hand, just enjoy it.'); if (!isSilent) console.log(uTable.toString()); updateData.universal_plugins_update_msg = undefined; } }; // cli -> tnpm -> universal showCliUpdateM(); showPluginsUpdateM(); showUniversalPluginsM(); updateFile.update(constant_1.UPDATE_KEY, updateData); return [2 /*return*/]; }); }); } function checkLock(updateData) { var _a; return __awaiter(this, void 0, void 0, function () { var updateLock, nowTime, currUpdateData; return __generator(this, function (_b) { switch (_b.label) { case 0: updateLock = updateData === null || updateData === void 0 ? void 0 : updateData.update_lock; nowTime = new Date().getTime(); if ((updateLock === null || updateLock === void 0 ? void 0 : updateLock.time) && nowTime - Number(updateLock.time) < constant_1.CHECK_UPDATE_GAP) { return [2 /*return*/, true]; } updateData.update_lock = { time: nowTime, pid: process.pid, }; return [4 /*yield*/, updateFile.update(constant_1.UPDATE_KEY, updateData)]; case 1: _b.sent(); return [4 /*yield*/, updateFile.read(constant_1.UPDATE_KEY)]; case 2: currUpdateData = _b.sent(); return [2 /*return*/, isUpdateData(currUpdateData) && ((_a = currUpdateData.update_lock) === null || _a === void 0 ? void 0 : _a.pid) !== process.pid]; } }); }); } /** * 如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁 * * 当心跳进程意外退出unlock没有被调用时会存在心跳和更新两个unlock文件 * 当这两个unlock文件存在并且没有心跳进程正常运行时会导致主流程报file read timeout错误 * 这个函数的作用是为了解决这个问题,如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁 * * @param ctx Feflow */ function ensureFilesUnlocked(ctx) { return __awaiter(this, void 0, void 0, function () { var beatLockPath, updateLockPath, heartBeatPidPath, heartBeatPid, isPsExist, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: beatLockPath = path_1.default.join(constant_1.FEFLOW_HOME, constant_1.BEAT_LOCK); updateLockPath = path_1.default.join(constant_1.FEFLOW_HOME, constant_1.UPDATE_LOCK); heartBeatPidPath = path_1.default.join(constant_1.FEFLOW_HOME, constant_1.HEART_BEAT_PID); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); if (!(0, fs_1.isFileExist)(heartBeatPidPath)) return [2 /*return*/]; heartBeatPid = (0, file_1.readFileSync)(heartBeatPidPath); ctx.logger.debug('heartBeatPid:', heartBeatPid); return [4 /*yield*/, (0, process_1.checkProcessExistByPid)(heartBeatPid)]; case 2: isPsExist = _a.sent(); ctx.logger.debug('fefelow-update-beat-process is exist:', isPsExist); if (lockfile_1.default.checkSync(beatLockPath) && !isPsExist) { ctx.logger.debug('beat file unlock'); lockfile_1.default.unlockSync(beatLockPath); } if (lockfile_1.default.checkSync(updateLockPath) && !isPsExist) { ctx.logger.debug('update file unlock'); lockfile_1.default.unlockSync(updateLockPath); } return [3 /*break*/, 4]; case 3: e_1 = _a.sent(); ctx.logger.error('unlock beat or update file fail', e_1); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); } function checkUpdate(ctx) { var _a; return __awaiter(this, void 0, void 0, function () { var dbFilePath, autoUpdate, nowTime, latestVersion, cacheValidate, updateLockPath, heartDBFilePath, beatLockPath, needToRestartUpdateBeatProcess, updateData, isLocked, cliUpdateMsg, pluginsUpdateMsg, universalPluginsUpdateMsg, heartBeatData, lastBeatTime, askIfUpdateCli, answer; return __generator(this, function (_b) { switch (_b.label) { case 0: dbFilePath = path_1.default.join(ctx.root, constant_1.UPDATE_COLLECTION); autoUpdate = ctx.args['auto-update'] || String((_a = ctx.config) === null || _a === void 0 ? void 0 : _a.autoUpdate) === 'true'; nowTime = new Date().getTime(); latestVersion = ''; cacheValidate = false; // 如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁 return [4 /*yield*/, ensureFilesUnlocked(ctx)]; case 1: // 如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁 _b.sent(); if (!updateFile) { updateLockPath = path_1.default.join(ctx.root, constant_1.UPDATE_LOCK); updateFile = new lock_file_1.default(dbFilePath, updateLockPath, ctx.logger); } heartDBFilePath = path_1.default.join(ctx.root, constant_1.HEART_BEAT_COLLECTION); if (!heartFile) { beatLockPath = path_1.default.join(ctx.root, constant_1.BEAT_LOCK); heartFile = new lock_file_1.default(heartDBFilePath, beatLockPath, ctx.logger); } needToRestartUpdateBeatProcess = false; return [4 /*yield*/, updateFile.read(constant_1.UPDATE_KEY)]; case 2: updateData = _b.sent(); if (!isUpdateData(updateData)) return [3 /*break*/, 6]; return [4 /*yield*/, checkLock(updateData)]; case 3: isLocked = _b.sent(); if (isLocked) return [2 /*return*/, ctx.logger.debug('one updating process is running')]; cliUpdateMsg = updateData.cli_update_msg, pluginsUpdateMsg = updateData.plugins_update_msg, universalPluginsUpdateMsg = updateData.universal_plugins_update_msg; if (!(cliUpdateMsg || pluginsUpdateMsg || universalPluginsUpdateMsg)) return [3 /*break*/, 5]; return [4 /*yield*/, checkUpdateMsg(ctx, updateData)]; case 4: _b.sent(); _b.label = 5; case 5: return [3 /*break*/, 8]; case 6: // init ctx.logger.debug("".concat(constant_1.UPDATE_COLLECTION, " is illegal, init ").concat(constant_1.UPDATE_COLLECTION)); // 这里维持原来的写法不做改动,在更新文件内容不合法时同时初始化心跳和更新文件 return [4 /*yield*/, Promise.all([ // 初始化心跳数据 heartFile.update(constant_1.BEAT_KEY, String(nowTime)), updateFile.update(constant_1.UPDATE_KEY, { // 初始化自动更新任务数据 latest_cli_version: '', latest_plugins: '', latest_universal_plugins: '', // 初始化更新信息 cli_update_msg: '', plugins_update_msg: '', universal_plugins_update_msg: '', // 初始化更新锁,保持只有一个进程在更新 update_lock: { time: String(nowTime), pid: process.pid, }, }), ])]; case 7: // 这里维持原来的写法不做改动,在更新文件内容不合法时同时初始化心跳和更新文件 _b.sent(); // 需要重启心跳进程 needToRestartUpdateBeatProcess = true; _b.label = 8; case 8: return [4 /*yield*/, heartFile.read(constant_1.BEAT_KEY)]; case 9: heartBeatData = _b.sent(); return [4 /*yield*/, updateFile.read(constant_1.UPDATE_KEY)]; case 10: updateData = (_b.sent()); if (!isHeartBeatData(heartBeatData)) return [3 /*break*/, 11]; lastBeatTime = parseInt(heartBeatData, 10); cacheValidate = nowTime - lastBeatTime <= constant_1.BEAT_GAP; ctx.logger.debug("heart-beat process cache validate ".concat(cacheValidate, ", ").concat(cacheValidate ? 'not' : '', " launch update-beat-process")); // 子进程心跳停止了 if (!cacheValidate) { // todo:进程检测,清理一下僵死的进程(兼容不同系统) // 需要重启心跳进程 needToRestartUpdateBeatProcess = true; } // 即便 心跳 停止了,latest_cli_version 也应该是之前检测到的最新值 updateData.latest_cli_version && (latestVersion = updateData.latest_cli_version); return [3 /*break*/, 13]; case 11: ctx.logger.debug("".concat(constant_1.HEART_BEAT_COLLECTION, " is illegal, init ").concat(constant_1.HEART_BEAT_COLLECTION)); // 初始化心跳数据 return [4 /*yield*/, heartFile.update(constant_1.BEAT_KEY, String(nowTime))]; case 12: // 初始化心跳数据 _b.sent(); // 需要重启心跳进程 needToRestartUpdateBeatProcess = true; _b.label = 13; case 13: // 根据needToRestartUpdateBeat状态决定是否重启心跳进程 if (needToRestartUpdateBeatProcess) { ctx.logger.debug('launch update-beat-process'); startUpdateBeat(ctx); } if (!(!disableCheck && latestVersion && semver_1.default.gt(latestVersion, ctx.version))) return [3 /*break*/, 15]; ctx.logger.debug("Find new version, current version: ".concat(ctx.version, ", latest version: ").concat(latestVersion)); if (autoUpdate) { ctx.logger.debug("Feflow will auto update version from ".concat(ctx.version, " to ").concat(latestVersion, ".")); ctx.logger.debug('Update message will be shown next time.'); return [2 /*return*/, startUpdate(ctx, cacheValidate, latestVersion)]; } askIfUpdateCli = [ { type: 'confirm', name: 'ifUpdate', message: chalk_1.default.yellow("@feflow/cli's latest version is ".concat(chalk_1.default.green(latestVersion), ", but your current version is ").concat(chalk_1.default.red(ctx.version), ". Do you want to update it?")), default: true, }, ]; return [4 /*yield*/, inquirer_1.default.prompt(askIfUpdateCli)]; case 14: answer = _b.sent(); if (answer.ifUpdate) { ctx.logger.debug("Feflow will update from version ".concat(ctx.version, " to ").concat(latestVersion, ".")); ctx.logger.debug('Update message will be shown next time.'); return [2 /*return*/, startUpdate(ctx, cacheValidate, latestVersion)]; } (0, yaml_1.safeDump)(__assign(__assign({}, ctx.config), { lastUpdateCheck: +new Date() }), ctx.configPath); return [3 /*break*/, 16]; case 15: ctx.logger.debug('Current cli version is already latest.'); return [2 /*return*/, startUpdate(ctx, cacheValidate, '')]; case 16: return [2 /*return*/]; } }); }); } exports.checkUpdate = checkUpdate; function isUpdateData(data) { return !!(data && typeof data === 'object'); } function isHeartBeatData(data) { return typeof data === 'string'; } //# sourceMappingURL=index.js.map