update notifications
This commit is contained in:
parent
99240bdca2
commit
8b7e796e27
3 changed files with 113 additions and 35 deletions
64
src/main.ts
64
src/main.ts
|
@ -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('*')
|
||||
|
|
31
src/npm.ts
31
src/npm.ts
|
@ -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]);
|
||||
}
|
53
src/util.ts
53
src/util.ts
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue