@oclif/plugin-update
Advanced tools
Comparing version 1.1.0 to 1.1.1
@@ -0,1 +1,12 @@ | ||
<a name="1.1.1"></a> | ||
## [1.1.1](https://github.com/oclif/plugin-update/compare/v1.1.0...v1.1.1) (2018-04-09) | ||
### Bug Fixes | ||
* add targets ([68f6fa8](https://github.com/oclif/plugin-update/commit/68f6fa8)) | ||
* npm pack on circle ([9ca2c9c](https://github.com/oclif/plugin-update/commit/9ca2c9c)) | ||
* updater fixes ([c65c556](https://github.com/oclif/plugin-update/commit/c65c556)) | ||
* updater seems to be working ([9b3e33b](https://github.com/oclif/plugin-update/commit/9b3e33b)) | ||
<a name="1.1.0"></a> | ||
@@ -2,0 +13,0 @@ # [1.1.0](https://github.com/oclif/plugin-update/compare/v1.0.5...v1.1.0) (2018-04-07) |
import Command from '@oclif/command'; | ||
import { Updater } from '..'; | ||
export default class UpdateCommand extends Command { | ||
@@ -23,8 +22,18 @@ static description: string; | ||
}; | ||
updater: Updater; | ||
autoupdate: boolean; | ||
private autoupdate; | ||
private channel; | ||
private readonly clientRoot; | ||
private readonly clientBin; | ||
private readonly s3Host; | ||
run(): Promise<void>; | ||
logChop(): Promise<void>; | ||
private s3url(p); | ||
private fetchManifest(); | ||
private update(manifest); | ||
private needsUpdate(); | ||
private logChop(); | ||
private mtime(f); | ||
private debounce(); | ||
private tidy(); | ||
private reexec(); | ||
private createBin(version); | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const color_1 = require("@heroku-cli/color"); | ||
const command_1 = require("@oclif/command"); | ||
const cli_ux_1 = require("cli-ux"); | ||
const dateAddHours = require("date-fns/add_hours"); | ||
const dateIsAfter = require("date-fns/is_after"); | ||
const spawn = require("cross-spawn"); | ||
const fs = require("fs-extra"); | ||
const path = require("path"); | ||
const __1 = require(".."); | ||
const tar_1 = require("../tar"); | ||
const util_1 = require("../util"); | ||
@@ -14,3 +14,5 @@ class UpdateCommand extends command_1.default { | ||
super(...arguments); | ||
this.updater = __1.fetchUpdater(this.config); | ||
this.clientRoot = path.join(this.config.dataDir, 'client'); | ||
this.clientBin = path.join(this.clientRoot, 'bin', this.config.windows ? `${this.config.bin}.cmd` : this.config.bin); | ||
this.s3Host = this.config.pjson.oclif.update.s3.host; | ||
} | ||
@@ -20,13 +22,9 @@ async run() { | ||
this.autoupdate = !!flags.autoupdate; | ||
if (this.autoupdate) { | ||
if (this.autoupdate) | ||
await this.debounce(); | ||
} | ||
else { | ||
// on manual run, also log to file | ||
cli_ux_1.default.config.errlog = path.join(this.config.cacheDir, 'autoupdate'); | ||
} | ||
cli_ux_1.default.action.start(`${this.config.name}: Updating CLI`); | ||
let channel = args.channel || this.updater.channel; | ||
if (!await this.updater.needsUpdate(channel)) { | ||
if (!process.env.OCLIF_HIDE_UPDATED_MESSAGE) { | ||
this.channel = args.channel || this.config.channel || 'stable'; | ||
const manifest = await this.fetchManifest(); | ||
if (!await this.needsUpdate()) { | ||
if (!this.config.scopedEnvVar('HIDE_UPDATED_MESSAGE')) { | ||
cli_ux_1.default.action.stop(`already on latest version: ${this.config.version}`); | ||
@@ -36,14 +34,65 @@ } | ||
else { | ||
await this.updater.update({ channel }); | ||
await this.update(manifest); | ||
} | ||
this.debug('log chop'); | ||
await this.logChop(); | ||
this.debug('tidy'); | ||
await this.updater.tidy(); | ||
await this.config.runHook('update', { channel }); | ||
await this.tidy(); | ||
await this.config.runHook('update', { channel: this.channel }); | ||
this.debug('done'); | ||
cli_ux_1.default.action.stop(); | ||
} | ||
s3url(p) { | ||
if (!this.s3Host) | ||
throw new Error('S3 host not defined'); | ||
// TODO: handle s3Prefix | ||
return `${this.s3Host}/${this.config.name}/channels/${this.channel}/${p}`; | ||
} | ||
async fetchManifest() { | ||
const http = require('http-call').HTTP; | ||
try { | ||
let { body } = await http.get(this.s3url(`${this.config.platform}-${this.config.arch}`)); | ||
return body; | ||
} | ||
catch (err) { | ||
if (err.statusCode === 403) | ||
throw new Error(`HTTP 403: Invalid channel ${this.channel}`); | ||
throw err; | ||
} | ||
} | ||
async update(manifest) { | ||
const { version, channel } = manifest; | ||
cli_ux_1.default.action.start(`${this.config.name}: Updating CLI from ${color_1.default.green(this.config.version)} to ${color_1.default.green(version)}${channel === 'stable' ? '' : ' (' + color_1.default.yellow(channel) + ')'}`); | ||
const _ = require('lodash'); | ||
const http = require('http-call').HTTP; | ||
const filesize = require('filesize'); | ||
const output = path.join(this.clientRoot, version); | ||
const { response: stream } = await http.stream(manifest.gz); | ||
let extraction = tar_1.extract(stream, this.config.bin, output, manifest.sha256gz); | ||
// TODO: use cli.action.type | ||
if (cli_ux_1.default.action.frames) { | ||
// if spinner action | ||
let total = stream.headers['content-length']; | ||
let current = 0; | ||
const updateStatus = _.throttle((newStatus) => { | ||
cli_ux_1.default.action.status = newStatus; | ||
}, 500, { leading: true, trailing: false }); | ||
stream.on('data', data => { | ||
current += data.length; | ||
updateStatus(`${filesize(current)}/${filesize(total)}`); | ||
}); | ||
} | ||
await extraction; | ||
await this.createBin(version); | ||
await this.reexec(); | ||
} | ||
async needsUpdate() { | ||
if (this.autoupdate && this.config.scopedEnvVar('DISABLE_AUTOUPDATE') === '1') | ||
return; | ||
if (this.channel !== this.config.channel) | ||
return true; | ||
let manifest = await this.fetchManifest(); | ||
return this.config.version !== manifest.version; | ||
} | ||
async logChop() { | ||
try { | ||
this.debug('log chop'); | ||
const logChopper = require('log-chopper').default; | ||
@@ -60,8 +109,9 @@ await logChopper.chop(this.config.errlog); | ||
} | ||
// when autoupdating, wait until the CLI isn't active | ||
async debounce() { | ||
const lastrunfile = path.join(this.config.cacheDir, 'lastrun'); | ||
const m = await this.mtime(lastrunfile); | ||
const waitUntil = dateAddHours(m, 1); | ||
if (dateIsAfter(waitUntil, new Date())) { | ||
await cli_ux_1.default.log(`waiting until ${waitUntil.toISOString()} to update`); | ||
m.setHours(m.getHours() + 1); | ||
if (m < new Date()) { | ||
await cli_ux_1.default.log(`waiting until ${m.toISOString()} to update`); | ||
await util_1.wait(60 * 1000); // wait 1 minute | ||
@@ -72,3 +122,81 @@ return this.debounce(); | ||
} | ||
// removes any unused CLIs | ||
async tidy() { | ||
try { | ||
if (!this.config.binPath) | ||
return; | ||
if (!this.config.binPath.includes(this.config.version)) | ||
return; | ||
let root = this.clientRoot; | ||
if (!await fs.pathExists(root)) | ||
return; | ||
let files = await util_1.ls(root); | ||
let promises = files.map(async (f) => { | ||
if (['bin', this.config.version].includes(path.basename(f.path))) | ||
return; | ||
const mtime = f.stat.mtime; | ||
mtime.setHours(mtime.getHours() + 7 * 24); | ||
if (mtime < new Date()) { | ||
await fs.remove(f.path); | ||
} | ||
}); | ||
for (let p of promises) | ||
await p; | ||
await this.logChop(); | ||
} | ||
catch (err) { | ||
cli_ux_1.default.warn(err); | ||
} | ||
} | ||
async reexec() { | ||
cli_ux_1.default.action.stop(); | ||
return new Promise((_, reject) => { | ||
this.debug('restarting CLI after update', this.clientBin); | ||
spawn(this.clientBin, ['update'], { | ||
stdio: 'inherit', | ||
env: Object.assign({}, process.env, { [this.config.scopedEnvVarKey('HIDE_UPDATED_MESSAGE')]: '1' }), | ||
}) | ||
.on('error', reject) | ||
.on('close', (status) => { | ||
try { | ||
this.exit(status); | ||
} | ||
catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
}); | ||
} | ||
async createBin(version) { | ||
let dst = this.clientBin; | ||
if (this.config.windows) { | ||
let body = `@echo off | ||
"%~dp0\\..\\${version}\\bin\\${this.config.bin}.cmd" %* | ||
`; | ||
await fs.outputFile(dst, body); | ||
} | ||
else { | ||
let body = `#!/usr/bin/env bash | ||
set -e | ||
get_script_dir () { | ||
SOURCE="\${BASH_SOURCE[0]}" | ||
# While $SOURCE is a symlink, resolve it | ||
while [ -h "$SOURCE" ]; do | ||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" | ||
SOURCE="$( readlink "$SOURCE" )" | ||
# If $SOURCE was a relative symlink (so no "/" as prefix, need to resolve it relative to the symlink base directory | ||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" | ||
done | ||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" | ||
echo "$DIR" | ||
} | ||
DIR=$(get_script_dir) | ||
HEROKU_CLI_REDIRECTED=1 "$DIR/../${version}/bin/${this.config.bin}" "$@" | ||
`; | ||
await fs.remove(dst); | ||
await fs.outputFile(dst, body); | ||
await fs.chmod(dst, 0o755); | ||
} | ||
} | ||
} | ||
UpdateCommand.description = 'update the <%= config.bin %> CLI'; | ||
@@ -75,0 +203,0 @@ UpdateCommand.args = [{ name: 'channel', optional: true }]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const http_call_1 = require("http-call"); | ||
const _1 = require("."); | ||
class GithubUpdater extends _1.Updater { | ||
async update() { | ||
const release = await this.fetchRelease(); | ||
const version = release.tag_name.split('v')[1]; | ||
const base = this.base(version); | ||
const asset = release.assets.find((a) => a.name === `${base}.tar.gz`); | ||
if (!asset) | ||
throw new Error('release not found'); | ||
return super.update({ url: asset.browser_download_url, version }); | ||
} | ||
async needsUpdate() { | ||
const version = (await this.fetchRelease()).tag_name.split('v')[1]; | ||
return this.config.version !== version; | ||
} | ||
async fetchRelease() { | ||
const [owner, repo] = this.config.pjson.repository.split('/'); | ||
const { body } = await http_call_1.default.get(`https://api.github.com/repos/${owner}/${repo}/releases/latest`); | ||
return this.release = body; | ||
} | ||
} | ||
exports.GithubUpdater = GithubUpdater; | ||
// import HTTP from 'http-call' | ||
// import {Updater} from '.' | ||
// export class GithubUpdater extends Updater { | ||
// release?: {tag_name: string} | ||
// async update() { | ||
// const release = await this.fetchRelease() | ||
// const version = release.tag_name.split('v')[1] | ||
// const base = this.base(version) | ||
// const asset = release.assets.find((a: any) => a.name === `${base}.tar.gz`) | ||
// if (!asset) throw new Error('release not found') | ||
// return super.update({url: asset.browser_download_url, version}) | ||
// } | ||
// async needsUpdate() { | ||
// const version = (await this.fetchRelease()).tag_name.split('v')[1] | ||
// return this.config.version !== version | ||
// } | ||
// private async fetchRelease() { | ||
// const [owner, repo] = this.config.pjson.repository.split('/') | ||
// const {body} = await HTTP.get(`https://api.github.com/repos/${owner}/${repo}/releases/latest`) | ||
// return this.release = body | ||
// } | ||
// } |
@@ -5,4 +5,2 @@ "use strict"; | ||
const spawn = require("cross-spawn"); | ||
const dateIsAfter = require("date-fns/is_after"); | ||
const dateSubHours = require("date-fns/sub_hours"); | ||
const fs = require("fs-extra"); | ||
@@ -23,3 +21,5 @@ const path = require("path"); | ||
cli_ux_1.default.config.errlog = opts.config.errlog; | ||
const binPath = this.config.scopedEnvVar('CLI_BINPATH') || this.config.bin; | ||
const binPath = this.config.binPath; | ||
if (!binPath) | ||
return this.debug('no binpath set'); | ||
const lastrunfile = path.join(this.config.cacheDir, 'lastrun'); | ||
@@ -33,3 +33,4 @@ const autoupdatefile = path.join(this.config.cacheDir, 'autoupdate'); | ||
const m = await mtime(autoupdatefile); | ||
return dateIsAfter(m, dateSubHours(new Date(), 5)); | ||
m.setHours(m.getHours() + 5); | ||
return m < new Date(); | ||
} | ||
@@ -47,5 +48,4 @@ catch (err) { | ||
const clientDir = path.join(clientRoot, this.config.version); | ||
if (await fs.pathExists(clientDir)) { | ||
if (await fs.pathExists(clientDir)) | ||
await util_1.touch(clientDir); | ||
} | ||
if (!await autoupdateNeeded()) | ||
@@ -52,0 +52,0 @@ return; |
{ | ||
"name": "@oclif/plugin-update", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"author": "Jeff Dickey @jdxcode", | ||
@@ -8,9 +8,8 @@ "bugs": "https://github.com/oclif/plugin-update/issues", | ||
"@heroku-cli/color": "^1.1.3", | ||
"@oclif/command": "^1.4.7", | ||
"@oclif/config": "^1.3.67", | ||
"@oclif/errors": "^1.0.3", | ||
"@oclif/command": "^1.4.10", | ||
"@oclif/config": "^1.4.7", | ||
"@oclif/errors": "^1.0.4", | ||
"@types/semver": "^5.5.0", | ||
"cli-ux": "^3.3.27", | ||
"cli-ux": "^3.3.28", | ||
"cross-spawn": "^6.0.5", | ||
"date-fns": "^1.29.0", | ||
"debug": "^3.1.0", | ||
@@ -26,3 +25,3 @@ "filesize": "^3.6.1", | ||
"devDependencies": { | ||
"@oclif/dev-cli": "^1.7.3", | ||
"@oclif/dev-cli": "^1.9.11", | ||
"@oclif/plugin-help": "^1.2.3", | ||
@@ -33,3 +32,5 @@ "@oclif/test": "^1.0.4", | ||
"@types/cross-spawn": "^6.0.0", | ||
"@types/execa": "^0.9.0", | ||
"@types/fs-extra": "^5.0.1", | ||
"@types/glob": "^5.0.35", | ||
"@types/lodash": "^4.14.106", | ||
@@ -39,5 +40,7 @@ "@types/mocha": "^5.0.0", | ||
"@types/supports-color": "^3.1.0", | ||
"@types/write-json-file": "^2.2.1", | ||
"chai": "^4.1.2", | ||
"globby": "^8.0.1", | ||
"mocha": "^5.0.5", | ||
"qqjs": "^0.3.6", | ||
"ts-node": "5", | ||
@@ -73,9 +76,5 @@ "tslib": "^1.9.0", | ||
"scripts": { | ||
"build": "rm -rf lib && tsc", | ||
"clean": "rm -f .oclif.manifest.json", | ||
"lint": "tsc -p test --noEmit && tslint -p test -t stylish", | ||
"postpublish": "yarn run clean", | ||
"postpack": "rm -f .oclif.manifest.json", | ||
"posttest": "tsc -p test --noEmit && tslint -p test -t stylish", | ||
"prepublishOnly": "yarn run build && oclif-dev manifest && oclif-dev readme", | ||
"preversion": "yarn run clean", | ||
"prepack": "rm -rf lib && tsc && oclif-dev manifest && oclif-dev readme", | ||
"test": "mocha --forbid-only \"test/**/*.test.ts\"", | ||
@@ -82,0 +81,0 @@ "version": "oclif-dev readme && git add README.md" |
@@ -24,3 +24,3 @@ @oclif/plugin-update | ||
$ oclif-example (-v|--version|version) | ||
@oclif/plugin-update/1.1.0 linux-x64 node-v9.11.1 | ||
@oclif/plugin-update/1.1.1 linux-x64 node-v9.11.1 | ||
$ oclif-example --help [COMMAND] | ||
@@ -45,3 +45,3 @@ USAGE | ||
_See code: [src/commands/update.ts](https://github.com/oclif/plugin-update/blob/v1.1.0/src/commands/update.ts)_ | ||
_See code: [src/commands/update.ts](https://github.com/oclif/plugin-update/blob/v1.1.1/src/commands/update.ts)_ | ||
<!-- commandsstop --> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
15
8
23109
22
14
448
- Removeddate-fns@^1.29.0
- Removeddate-fns@1.30.1(transitive)
Updated@oclif/command@^1.4.10
Updated@oclif/config@^1.4.7
Updated@oclif/errors@^1.0.4
Updatedcli-ux@^3.3.28