assume publisher to be in package.json

This commit is contained in:
Joao Moreno 2015-09-24 22:50:40 +02:00
parent 8e59746de8
commit 96aea30e00
7 changed files with 155 additions and 133 deletions

View file

@ -1,95 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import { Promise, nfcall, resolve, reject } from 'q';
import { home } from 'osenv';
import { read } from './util';
import { WebApi, getBasicHandler } from 'vso-node-api/WebApi';
const credentialsPath = path.join(home(), '.vsce');
export interface ICredentials {
account: string;
publisher: string;
pat: string;
}
export interface IGetCredentialsOptions {
promptToOverwrite?: boolean;
promptIfMissing?: boolean;
}
function readCredentials(): Promise<ICredentials> {
return nfcall<string>(fs.readFile, credentialsPath, 'utf8')
.catch<string>(err => err.code !== 'ENOENT' ? reject(err) : resolve('null'))
.then<ICredentials>(credentialsStr => {
try {
return resolve(JSON.parse(credentialsStr));
} catch (e) {
return reject(`Error parsing credentials: ${ credentialsPath }`);
}
});
}
function writeCredentials(credentials: ICredentials): Promise<ICredentials> {
return nfcall<void>(fs.writeFile, credentialsPath, JSON.stringify(credentials))
.then(() => credentials);
}
function clearCredentials(): Promise<any> {
return nfcall(fs.unlink, credentialsPath)
.catch(err => err.code !== 'ENOENT' ? reject(err) : resolve('null'));
}
function promptForCredentials(): Promise<ICredentials> {
return read('Account name:').then(account => {
if (!/^https?:\/\//.test(account)) {
account = `https://${ account }.visualstudio.com`;
console.log(`Assuming account name '${ account }'`);
}
return read('Publisher:').then(publisher => {
return read('Personal Access Token:', { silent: true, replace: '*' })
.then(pat => ({ account, publisher, pat }));
});
});
}
export function getCredentials(options: IGetCredentialsOptions = {}): Promise<ICredentials> {
return readCredentials()
.then(credentials => {
if (!credentials || !options.promptToOverwrite) {
return resolve(credentials);
}
console.log(`Existing credentials found: { account: ${ credentials.account }, publisher: ${ credentials.publisher } }`);
return read('Do you want to overwrite existing credentials? [y/N] ')
.then<ICredentials>(answer => /^y$/i.test(answer) ? null : credentials);
})
.then(credentials => {
if (credentials || !options.promptIfMissing) {
return resolve(credentials);
}
return promptForCredentials()
.then(writeCredentials);
});
}
const galleryUrl = 'https://app.market.visualstudio.com';
export function login(): Promise<ICredentials> {
return getCredentials({ promptIfMissing: true, promptToOverwrite: true }).then(credentials => {
const authHandler = getBasicHandler('oauth', credentials.pat);
const vsoapi = new WebApi(credentials.account, authHandler);
const api = vsoapi.getQGalleryApi(galleryUrl);
return api.getPublisher(credentials.publisher).then(publisher => {
console.log(`Authentication successful. Found publisher '${ publisher.displayName }'.`);
return credentials;
});
});
}
export function logout(): Promise<any> {
return clearCredentials();
}

View file

@ -2,7 +2,7 @@ import * as minimist from 'minimist';
import { pack } from './package'; import { pack } from './package';
import { publish } from './publish'; import { publish } from './publish';
import { fatal } from './util'; import { fatal } from './util';
import { login, logout } from './login'; import { publisher } from './store';
const packagejson = require('../package.json'); const packagejson = require('../package.json');
function helpCommand(): void { function helpCommand(): void {
@ -11,8 +11,9 @@ function helpCommand(): void {
Commands: Commands:
package [vsix path] Packages the extension into a .vsix package package [vsix path] Packages the extension into a .vsix package
publish Publishes the extension publish Publishes the extension
login Logs in to the extension service publisher add [publisher] Add a publisher
logout Logs out of the extension service publisher rm [publisher] Remove a publisher
publisher list List all added publishers
Global options: Global options:
--help, -h Display help --help, -h Display help
@ -27,22 +28,26 @@ function versionCommand(): void {
} }
function command(args: minimist.ParsedArgs): boolean { function command(args: minimist.ParsedArgs): boolean {
const promise = (() => { try {
switch (args._[0]) { const promise = (() => {
case 'package': return pack(args._[1]).then(({ packagePath }) => console.log(`Package created: ${ packagePath }`)); switch (args._[0]) {
case 'login': return login(); case 'package': return pack(args._[1]).then(({ packagePath }) => console.log(`Package created: ${ packagePath }`));
case 'logout': return logout(); case 'publisher': return publisher(args._[1], args._[2]);
case 'publish': return publish(args._[1]); case 'publish': return publish(args._[1]);
default: return null; default: return null;
} }
})(); })();
if (promise) { if (promise) {
promise.catch(fatal); promise.catch(fatal);
return true;
}
return false;
} catch (e) {
fatal(e);
return true; return true;
} }
return false;
} }
module.exports = function (argv: string[]): void { module.exports = function (argv: string[]): void {

View file

@ -3,11 +3,9 @@ import * as path from 'path';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as yazl from 'yazl'; import * as yazl from 'yazl';
import { Manifest } from './manifest'; import { Manifest } from './manifest';
import { getCredentials } from './login';
import { nfcall, Promise, reject, resolve, all } from 'q'; import { nfcall, Promise, reject, resolve, all } from 'q';
import * as glob from 'glob'; import * as glob from 'glob';
import * as minimatch from 'minimatch'; import * as minimatch from 'minimatch';
import { read } from './util';
import { exec } from 'child_process'; import { exec } from 'child_process';
const resourcesPath = path.join(path.dirname(__dirname), 'resources'); const resourcesPath = path.join(path.dirname(__dirname), 'resources');

View file

@ -1,27 +1,21 @@
import { readFile } from 'fs'; import { readFile } from 'fs';
import { WebApi, getBasicHandler } from 'vso-node-api/WebApi';
import { IQGalleryApi } from 'vso-node-api/GalleryApi';
import { ExtensionQueryFlags, PublishedExtension } from 'vso-node-api/interfaces/GalleryInterfaces'; import { ExtensionQueryFlags, PublishedExtension } from 'vso-node-api/interfaces/GalleryInterfaces';
import { nfcall, Promise, reject, resolve, all } from 'q'; import { nfcall, Promise, reject } from 'q';
import { pack, IPackageResult } from './package'; import { pack } from './package';
import { tmpName } from 'tmp'; import { tmpName } from 'tmp';
import { getCredentials, ICredentials } from './login'; import { get } from './store';
import { getGalleryAPI } from './util';
const galleryUrl = 'https://app.market.visualstudio.com'; const galleryUrl = 'https://app.market.visualstudio.com';
function getGalleryAPI({ account, pat }: ICredentials): IQGalleryApi {
const authHandler = getBasicHandler('oauth', pat);
const vsoapi = new WebApi(account, authHandler);
return vsoapi.getQGalleryApi(galleryUrl);
}
export function publish(cwd = process.cwd()): Promise<any> { export function publish(cwd = process.cwd()): Promise<any> {
return getCredentials({ promptIfMissing: true }) return nfcall<string>(tmpName)
.then(getGalleryAPI) .then(packagePath => pack(packagePath, cwd))
.then(api => nfcall<string>(tmpName) .then(result => {
.then(packagePath => pack(packagePath, cwd)) const { manifest, packagePath } = result;
.then(({ manifest, packagePath }) => nfcall<string>(readFile, packagePath, 'base64')
.then(extensionManifest => { return get(manifest.publisher).then(getGalleryAPI).then(api => {
return nfcall<string>(readFile, packagePath, 'base64').then(extensionManifest => {
const fullName = `${ manifest.name }@${ manifest.version }`; const fullName = `${ manifest.name }@${ manifest.version }`;
console.log(`Publishing ${ fullName }...`); console.log(`Publishing ${ fullName }...`);
@ -40,7 +34,7 @@ export function publish(cwd = process.cwd()): Promise<any> {
.catch(err => reject(err.statusCode === 409 ? `${ fullName } already exists.` : err)) .catch(err => reject(err.statusCode === 409 ? `${ fullName } already exists.` : err))
.then(() => console.log(`Successfully published ${ fullName }!`)); .then(() => console.log(`Successfully published ${ fullName }!`));
}); });
}) });
) });
); });
}; };

103
src/store.ts Normal file
View file

@ -0,0 +1,103 @@
import * as fs from 'fs';
import * as path from 'path';
import { Promise, nfcall, resolve, reject } from 'q';
import { home } from 'osenv';
import { read, getGalleryAPI } from './util';
import { validatePublisher } from './validation';
const storePath = path.join(home(), '.vsce');
export interface IStore {
[publisher: string]: string;
}
export interface IGetOptions {
promptToOverwrite?: boolean;
promptIfMissing?: boolean;
}
function load(): Promise<IStore> {
return nfcall<string>(fs.readFile, storePath, 'utf8')
.catch<string>(err => err.code !== 'ENOENT' ? reject(err) : resolve('{}'))
.then<IStore>(rawStore => {
try {
return resolve(JSON.parse(rawStore));
} catch (e) {
return reject(`Error parsing store: ${ storePath }`);
}
});
}
function save(store: IStore): Promise<IStore> {
return nfcall<void>(fs.writeFile, storePath, JSON.stringify(store))
.then(() => store);
}
function requestPAT(store: IStore, publisher: string): Promise<string> {
return read(`Personal Access Token for publisher '${ publisher }':`, { silent: true, replace: '*' })
.then(pat => {
const api = getGalleryAPI(pat);
return api.getPublisher(publisher).then(p => {
console.log(`Authentication successful. Found publisher '${ p.displayName }'.`);
return pat;
});
})
.then(pat => {
store[publisher] = pat;
return save(store).then(() => pat);
});
}
export function get(publisher: string): Promise<string> {
validatePublisher(publisher);
return load().then(store => {
if (store[publisher]) {
return resolve(store[publisher]);
}
return requestPAT(store, publisher);
});
}
function add(publisher: string): Promise<string> {
validatePublisher(publisher);
return load()
.then<IStore>(store => {
if (store[publisher]) {
console.log(`Publisher '${ publisher }' is already known`);
return read('Do you want to overwrite its PAT? [y/N] ')
.then(answer => /^y$/i.test(answer) ? store : reject('Aborted'));
}
return resolve(store);
})
.then(store => requestPAT(store, publisher));
}
function rm(publisher: string): Promise<any> {
validatePublisher(publisher);
return load().then(store => {
if (!store[publisher]) {
return reject(`Unknown publisher '${ publisher }'`);
}
delete store[publisher];
return save(store);
});
}
function list(): Promise<string[]> {
return load().then(store => Object.keys(store));
}
export function publisher(action: string, publisher: string): Promise<any> {
switch (action) {
case 'add': return add(publisher);
case 'rm': return rm(publisher);
case 'list': default: return list().then(publishers => publishers.forEach(p => console.log(p)));
}
}

View file

@ -1,6 +1,8 @@
import { Promise, nfcall } from 'q'; import { Promise, nfcall } from 'q';
import { assign } from 'lodash'; import { assign } from 'lodash';
import _read = require('read'); import _read = require('read');
import { WebApi, getBasicHandler } from 'vso-node-api/WebApi';
import { IQGalleryApi } from 'vso-node-api/GalleryApi';
export function fatal(message: any, ...args: any[]) { export function fatal(message: any, ...args: any[]) {
if (message instanceof Error) { if (message instanceof Error) {
@ -19,3 +21,9 @@ export function read(prompt: string, options: _read.Options = {}): Promise<strin
return nfcall<string>(_read, assign({ prompt }, options)) return nfcall<string>(_read, assign({ prompt }, options))
.spread(r => r); .spread(r => r);
} }
export function getGalleryAPI(pat: string): IQGalleryApi {
const authHandler = getBasicHandler('oauth', pat);
const vsoapi = new WebApi('oauth', authHandler);
return vsoapi.getQGalleryApi('https://app.market.visualstudio.com');
}

9
src/validation.ts Normal file
View file

@ -0,0 +1,9 @@
export function validatePublisher(publisher: string): void {
if (!publisher) {
throw new Error(`Missing publisher name`);
}
if (!/^[a-z0-9\-]+$/i.test(publisher)) {
throw new Error(`Invalid publisher '${ publisher }'`);
}
}