UNPKG

@adpt/cli

Version:
227 lines 8.9 kB
"use strict"; /* * Copyright 2020 Unbounded Systems, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const utils_1 = require("@adpt/utils"); const boxen_1 = tslib_1.__importDefault(require("boxen")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const child_process_1 = require("child_process"); const debug_1 = tslib_1.__importDefault(require("debug")); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const node_fetch_1 = tslib_1.__importDefault(require("node-fetch")); const os_1 = tslib_1.__importDefault(require("os")); const path_1 = tslib_1.__importDefault(require("path")); const semver_1 = tslib_1.__importDefault(require("semver")); // tslint:disable-next-line: no-var-requires const pjson = require("../../../package.json"); const debug = debug_1.default("adapt:upgrade"); const checkerDefaults = { timeout: 30 * 1000, }; class UpgradeChecker { constructor(config, state) { this.state = state; this.config = Object.assign({}, checkerDefaults, config); } async check() { const interval = this.config.upgradeCheckInterval; if ((Date.now() - this.state.get("lastUpgradeCheck")) < interval) { debug(`Not time to check (interval=${interval})`); return; } await fs_extra_1.default.ensureDir(this.config.logDir); const logFile = path_1.default.join(this.config.logDir, "upgrade-check.log"); const logFd = await fs_extra_1.default.open(logFile, "a"); const childPath = path_1.default.join(__dirname, "check.js"); const args = [childPath, JSON.stringify(this.config)]; debug(`Spawning child to check for upgrade: ${args}`); const child = child_process_1.spawn(process.execPath, args, { detached: !isWindows(), stdio: ["ignore", logFd, logFd], }); child.unref(); } async notifyString(options = {}) { const upgradeInfo = this.upgrade; let current = this.state.get("version"); let latest = upgradeInfo && upgradeInfo.latest; const summary = upgradeInfo && upgradeInfo.summary; const latestIsNewer = latest && semver_1.default.gt(latest, current); const now = remindNow(this.state.get("lastUpgradeReminder"), this.config.upgradeRemindInterval); const ignoreVer = this.config.upgradeIgnore; if (!process.stdout.isTTY || !latest || !latestIsNewer || !now || latest === ignoreVer) { debug(`Not notifying: tty=${process.stdout.isTTY} ` + `current=${current} latest=${latest} now=${now} ignoreVer=${ignoreVer}`); return undefined; } const pkg = pjson.name; let cmd; switch (await utils_1.installType(__dirname)) { case utils_1.InstallType.npmGlobal: cmd = "npm install -g"; break; case utils_1.InstallType.yarnGlobal: cmd = "yarn global add"; break; case utils_1.InstallType.yarn: cmd = "yarn add"; break; case utils_1.InstallType.npm: default: cmd = "npm install"; break; } cmd += ` ${pkg}@${latest}`; let ignore = "Ignore:"; let ignoreCmd = `adapt config:set upgradeIgnore ${latest}`; if (options.fancy) { current = chalk_1.default.dim(current); latest = chalk_1.default.greenBright(latest); cmd = chalk_1.default.bold.cyanBright(cmd); ignore = chalk_1.default.dim(ignore); ignoreCmd = chalk_1.default.cyan(ignoreCmd); } let output = `Upgrade available: ${current}${latest}\n`; if (summary && (summary.description || summary.securityFixes)) { output += "\n"; if (summary.description) { output += `${latest}: ${summary.description}\n`; } if (summary.securityFixes) { output += `This upgrade contains security fixes\n`; } } output += `\nUpgrade: ${cmd}\n` + `${ignore} ${ignoreCmd}`; if (options.fancy) { output = boxen_1.default(output, { borderStyle: "bold" /* Bold */, float: "center", margin: 1, padding: 1, }); } this.state.set("lastUpgradeReminder", Date.now()); return output; } /** * This method should not be called directly. It is called when the * check method invokes a subprocess that executes check.ts, which calls * this method. */ async fetch() { const version = this.state.get("version"); // Understand how long users run a particular version before // updating so we can evaluate deprecation/support timelines. const q = `?x-installed=${this.state.get("installed")}`; const ua = await utils_1.userAgent({ name: pjson.name, version, docker: true, }); let resp; try { resp = await node_fetch_1.default(this.config.upgradeCheckUrl + q, { headers: { "User-Agent": ua }, timeout: this.config.timeout, }); } catch (err) { if (err.name === "FetchError") { throw new utils_1.UserError(`Error fetching version information: ${err.message}`); } throw err; } const body = await resp.text(); if (resp.status !== 200) { const trimmed = body.trim(); let msg = `Status ${resp.status} (${resp.statusText})`; if (trimmed) msg += " " + trimmed; throw new utils_1.UserError(msg); } debug("Upgrade check response:", body); let ret; try { ret = JSON.parse(body); } catch (err) { if (err.name === "SyntaxError") { throw new utils_1.UserError(`Invalid JSON response: ${err.message}`); } throw err; } if (!validateSummary(ret)) throw new Error(`Invalid upgrade check response`); const latest = ret.channelCurrent[this.config.channel]; if (!latest) { throw new utils_1.UserError(`No information available for channel '${this.config.channel}'`); } const verSummary = ret.versions[latest]; const summary = { channel: this.config.channel, description: verSummary && verSummary.description, securityFixes: hasSecurityFixes(version, latest, this.config.channel, ret), }; return { latest, summary, }; } get upgrade() { return this.state.get("upgrade"); } } exports.UpgradeChecker = UpgradeChecker; class SummaryError extends utils_1.UserError { constructor(msg) { super(`Invalid response: ${msg}`); } } function validateSummary(obj) { if (!utils_1.isObject(obj)) throw new SummaryError(`Response was not a valid object`); if (typeof obj.name !== "string") throw new SummaryError(`Invalid name property`); if (!obj.name.includes("cli")) throw new SummaryError(`Unrecognized package name`); if (!utils_1.isObject(obj.channelCurrent)) throw new SummaryError(`Invalid channelCurrent property`); if (!utils_1.isObject(obj.versions)) throw new SummaryError(`Invalid versions property`); return true; } const isWindows = () => os_1.default.platform() === "win32"; /** * Exported for testing */ function hasSecurityFixes(current, latest, channel, summary) { const range = `>${current} <=${latest}`; const versions = Object.entries(summary.versions) .filter(([ver, sum]) => sum.channel === channel && sum.securityFixes && semver_1.default.satisfies(ver, range)); return versions.length !== 0; } exports.hasSecurityFixes = hasSecurityFixes; function remindNow(lastReminder, interval) { if (interval <= 0) return false; return (Date.now() - (lastReminder || 0)) >= interval; } //# sourceMappingURL=upgrade_checker.js.map