update notifications

This commit is contained in:
Joao Moreno 2016-06-15 17:02:01 +02:00
parent 99240bdca2
commit 8b7e796e27
3 changed files with 113 additions and 35 deletions

View file

@ -1,18 +1,56 @@
import * as program from 'commander';
import { packageCommand, ls } from './package';
import { publish, list, unpublish } from './publish';
import { catchFatal } from './util';
import { listPublishers, createPublisher, deletePublisher, loginPublisher, logoutPublisher } from './store';
const packagejson = require('../package.json');
import { getLatestVersion } from './npm';
import { CancellationToken, isCancelledError } from './util';
import * as semver from 'semver';
import { isatty } from 'tty';
const pkg = require('../package.json');
function fatal<T>(message: any, ...args: any[]): void {
if (message instanceof Error) {
if (/^cancell?ed$/i.test(message.message)) {
return;
}
message = message.message;
}
console.error('Error:', message, ...args);
process.exit(1);
}
function main<T>(task: Promise<any>): void {
let latestVersion: string = null;
const token = new CancellationToken();
if (isatty(1)) {
getLatestVersion(pkg.name, token)
.then(version => latestVersion = version)
.catch(err => !isCancelledError(err) && console.error(err));
}
task
.catch(fatal)
.then(() => {
if (latestVersion && semver.gt(latestVersion, pkg.version)) {
console.log(`\nThe latest version of ${ pkg.name } is ${ latestVersion } and you have ${ pkg.version }.\nUpdate it now: npm install -g ${ pkg.name }`);
} else {
token.cancel();
}
});
}
module.exports = function (argv: string[]): void {
program
.version(packagejson.version);
.version(pkg.version);
program
.command('ls')
.description('Lists all the files that will be published')
.action(() => catchFatal(ls()));
.action(() => main(ls()));
program
.command('package')
@ -20,7 +58,7 @@ module.exports = function (argv: string[]): void {
.option('-o, --out [path]', 'Location of the package')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md will with this url.')
.action(({ out, baseContentUrl, baseImagesUrl }) => catchFatal(packageCommand({ packagePath: out, baseContentUrl, baseImagesUrl })));
.action(({ out, baseContentUrl, baseImagesUrl }) => main(packageCommand({ packagePath: out, baseContentUrl, baseImagesUrl })));
program
.command('publish [<version>]')
@ -29,43 +67,43 @@ module.exports = function (argv: string[]): void {
.option('--packagePath [path]', 'Publish the VSIX package located at the specified path.')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md will with this url.')
.action((version, { pat, packagePath, baseContentUrl, baseImagesUrl }) => catchFatal(publish({ pat, version, packagePath, baseContentUrl, baseImagesUrl })));
.action((version, { pat, packagePath, baseContentUrl, baseImagesUrl }) => main(publish({ pat, version, packagePath, baseContentUrl, baseImagesUrl })));
program
.command('unpublish [<extensionid>]')
.description('Unpublishes an extension. Example extension id: microsoft.csharp.')
.option('-p, --pat <token>', 'Personal Access Token')
.action((id, { pat }) => catchFatal(unpublish({ id, pat })));
.action((id, { pat }) => main(unpublish({ id, pat })));
program
.command('list <publisher>')
.description('Lists all extensions published by the given publisher')
.action(publisher => catchFatal(list(publisher)));
.action(publisher => main(list(publisher)));
program
.command('ls-publishers')
.description('List all known publishers')
.action(() => catchFatal(listPublishers()));
.action(() => main(listPublishers()));
program
.command('create-publisher <publisher>')
.description('Creates a new publisher')
.action(publisher => catchFatal(createPublisher(publisher)));
.action(publisher => main(createPublisher(publisher)));
program
.command('delete-publisher <publisher>')
.description('Deletes a publisher')
.action(publisher => catchFatal(deletePublisher(publisher)));
.action(publisher => main(deletePublisher(publisher)));
program
.command('login <publisher>')
.description('Add a publisher to the known publishers list')
.action(name => catchFatal(loginPublisher(name)));
.action(name => main(loginPublisher(name)));
program
.command('logout <publisher>')
.description('Remove a publisher from the known publishers list')
.action(name => catchFatal(logoutPublisher(name)));
.action(name => main(logoutPublisher(name)));
program
.command('*')

View file

@ -1,5 +1,6 @@
import * as path from 'path';
import * as cp from 'child_process';
import { CancellationToken } from './util';
import { assign } from 'lodash';
interface IOptions {
@ -12,17 +13,31 @@ interface IOptions {
killSignal?: string;
}
function exec(command: string, options: IOptions = {}): Promise<{ stdout: string; stderr: string; }> {
function exec(command: string, options: IOptions = {}, cancellationToken?: CancellationToken): Promise<{ stdout: string; stderr: string; }> {
return new Promise((c, e) => {
cp.exec(command, assign(options, { encoding: 'utf8' }), (err, stdout: string, stderr: string) => {
let disposeCancellationListener: Function = null;
const child = cp.exec(command, assign(options, { encoding: 'utf8' }), (err, stdout: string, stderr: string) => {
if (disposeCancellationListener) {
disposeCancellationListener();
disposeCancellationListener = null;
}
if (err) { return e(err); }
c({ stdout, stderr });
});
if (cancellationToken) {
disposeCancellationListener = cancellationToken.subscribe(err => {
child.kill();
e(err);
});
}
});
}
function checkNPM(): Promise<void> {
return exec('npm -v').then(({ stdout }) => {
function checkNPM(cancellationToken?: CancellationToken): Promise<void> {
return exec('npm -v', {}, cancellationToken).then(({ stdout }) => {
const version = stdout.trim();
if (/^3\.7\.[0123]$/.test(version)) {
@ -37,4 +52,12 @@ export function getDependencies(cwd: string): Promise<string[]> {
.then(({ stdout }) => stdout
.split(/[\r\n]/)
.filter(dir => path.isAbsolute(dir)));
}
export function getLatestVersion(name: string, cancellationToken?: CancellationToken): Promise<string> {
return checkNPM(cancellationToken)
.then(() => exec(`npm show ${ name } version`, {}, cancellationToken))
.then(({ stdout }) => stdout
.split(/[\r\n]/)
.filter(line => !!line)[0]);
}

View file

@ -4,24 +4,6 @@ import { WebApi, getBasicHandler } from 'vso-node-api/WebApi';
import { IGalleryApi, IQGalleryApi } from 'vso-node-api/GalleryApi';
import * as denodeify from 'denodeify';
export function fatal<T>(message: any, ...args: any[]): Promise<T> {
if (message instanceof Error) {
if (/^cancell?ed$/i.test(message.message)) {
return;
}
message = message.message;
}
console.error('Error:', message, ...args);
process.exit(1);
return Promise.resolve<T>(null);
}
export function catchFatal<T>(promise: Promise<T>): Promise<T> {
return promise.catch<T>(fatal);
}
const __read = denodeify<_read.Options,string>(_read);
export function read(prompt: string, options: _read.Options = {}): Promise<string> {
return __read(assign({ prompt }, options));
@ -57,4 +39,39 @@ export function chain<T,P>(initial: T, processors: P[], process: (a: T, b: P)=>P
export function flatten<T>(arr: T[][]): T[] {
return [].concat.apply([], arr) as T[];
}
const CancelledError = 'Cancelled';
export function isCancelledError(error: any) {
return error === CancelledError;
}
export class CancellationToken {
private listeners: Function[] = [];
private _cancelled: boolean = false;
get isCancelled(): boolean { return this._cancelled; }
subscribe(fn: Function): Function {
this.listeners.push(fn);
return () => {
const index = this.listeners.indexOf(fn);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
cancel(): void {
const emit = !this._cancelled;
this._cancelled = true;
if (emit) {
this.listeners.forEach(l => l(CancelledError));
this.listeners = [];
}
}
}