diff --git a/.gitignore b/.gitignore index ad245d8445..7c3e1d7620 100644 --- a/.gitignore +++ b/.gitignore @@ -332,4 +332,5 @@ ASALocalRun/ out node_modules -media \ No newline at end of file +media +.DS_Store \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 294e675879..58311e1051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,11 @@ "integrity": "sha512-h6+VEw2Vr3ORiFCyyJmcho2zALnUq9cvdB/IO8Xs9itrJVCenC7o26A6+m7D0ihTTr65eS259H5/Ghl/VjYs6g==", "dev": true }, + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" + }, "@types/lodash": { "version": "4.14.109", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.109.tgz", @@ -71,8 +76,16 @@ "@types/node": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.2.tgz", - "integrity": "sha512-9NfEUDp3tgRhmoxzTpTo+lq+KIVFxZahuRX0LHF/9IzKHaWuoWsIrrJ61zw5cnnlGINX8lqJzXYfQTOICS5Q+A==", - "dev": true + "integrity": "sha512-9NfEUDp3tgRhmoxzTpTo+lq+KIVFxZahuRX0LHF/9IzKHaWuoWsIrrJ61zw5cnnlGINX8lqJzXYfQTOICS5Q+A==" + }, + "@types/ws": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz", + "integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==", + "requires": { + "@types/events": "1.2.0", + "@types/node": "10.3.2" + } }, "@webassemblyjs/ast": { "version": "1.5.12", @@ -535,6 +548,11 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -11104,6 +11122,14 @@ "slide": "1.1.6" } }, + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", + "requires": { + "async-limiter": "1.0.0" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 15bf946f58..2da5b09a59 100644 --- a/package.json +++ b/package.json @@ -28,26 +28,27 @@ "type": "object", "title": "GitHub configuration", "properties": { - "github.username": { - "type": [ - "string", - "null" - ], - "default": null, - "description": "The username to use when accessing GitHub. The default is to consult the Git credential manager." - }, - "github.host": { - "type": "string", - "default": "github.com", - "description": "The host name to access GitHub. Change this to your GitHub Enterprise host." - }, - "github.accessToken": { - "type": [ - "string", - "null" - ], - "default": null, - "description": "GitHub access token." + "github.hosts": { + "type": "array", + "default": [], + "description": "Host tokens", + "items": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host name of the GitHub server (for eg., 'https://github.com')" + }, + "username": { + "type": "string", + "description": "The username to access GitHub" + }, + "token": { + "type": "string", + "description": "GitHub access token with the following scopes: read:user, user:email, repo, write:discussion" + } + } + } } } }, @@ -239,9 +240,11 @@ "iconv-lite": "0.4.23", "vscode": "^1.1.18", "@octokit/rest": "^15.9.5", + "@types/ws": "^5.1.2", "markdown-it": "^8.4.0", "git-credential-node": "^1.1.0", "tmp": "^0.0.31", - "moment": "^2.22.1" + "moment": "^2.22.1", + "ws": "^6.0.0" } } diff --git a/src/authentication/configuration.ts b/src/authentication/configuration.ts new file mode 100644 index 0000000000..ab66e7bdce --- /dev/null +++ b/src/authentication/configuration.ts @@ -0,0 +1,53 @@ +import * as vscode from 'vscode'; + +export interface IHostConfiguration { + host: string; + username: string | undefined; + token: string | undefined; +} + +export const HostHelper = class { + public static getApiHost(host: IHostConfiguration | vscode.Uri): vscode.Uri { + const hostUri: vscode.Uri = host instanceof vscode.Uri ? host : vscode.Uri.parse(host.host); + if (hostUri.authority === 'github.com') { + return vscode.Uri.parse('https://api.github.com'); + } else { + return vscode.Uri.parse(`${hostUri.scheme}://${hostUri.authority}`); + } + } + + public static getApiPath(host: IHostConfiguration | vscode.Uri, path: string): string { + const hostUri: vscode.Uri = host instanceof vscode.Uri ? host : vscode.Uri.parse(host.host); + if (hostUri.authority === 'github.com') { + return path; + } else { + return `/api/v3${path}`; + } + } +}; + +export interface IConfiguration extends IHostConfiguration { + onDidChange: vscode.Event; +} + +export class Configuration implements IConfiguration { + public username: string | undefined; + public token: string | undefined; + public onDidChange: vscode.Event; + private _emitter: vscode.EventEmitter; + + constructor(public host: string) { + this._emitter = new vscode.EventEmitter(); + this.onDidChange = this._emitter.event; + } + + public update(username: string | undefined, token: string | undefined, raiseEvent: boolean = true): void { + if (username !== this.username || token !== this.token) { + this.username = username; + this.token = token; + if (raiseEvent) { + this._emitter.fire(this); + } + } + } +} diff --git a/src/authentication/githubServer.ts b/src/authentication/githubServer.ts new file mode 100644 index 0000000000..f324846531 --- /dev/null +++ b/src/authentication/githubServer.ts @@ -0,0 +1,229 @@ +import * as vscode from 'vscode'; +import { IHostConfiguration, HostHelper } from './configuration'; +import * as ws from 'ws'; +import * as https from 'https'; +import Logger from '../common/logger'; + +const SCOPES: string = 'read:user user:email repo write:discussion'; +const HOST: string = 'github-editor-auth.herokuapp.com'; +const HTTP_PROTOCOL: string = 'https'; +const WS_PROTOCOL: string = 'wss'; + +enum MessageType { + Host = 0x2, + Token = 0x8, +} + +interface IMessage { + type: MessageType; + guid: string; + host?: string; + token?: string; + scopes?: string; +} + +class Client { + private _guid?: string; + + constructor(private host: string, private scopes: string) {} + + public start(): Promise { + return new Promise((resolve, reject) => { + const socket = new ws(`${WS_PROTOCOL}://${HOST}`); + socket.on('message', data => { + const message: IMessage = JSON.parse(data.toString()); + + switch (message.type) { + case MessageType.Host: + { + this._guid = message.guid; + + socket.send( + JSON.stringify({ + type: MessageType.Host, + guid: this._guid, + host: this.host, + scopes: this.scopes, + }) + ); + vscode.commands.executeCommand( + 'vscode.open', + vscode.Uri.parse(`${HTTP_PROTOCOL}://${HOST}?state=action:login;guid:${this._guid}`) + ); + } + break; + case MessageType.Token: + { + socket.close(); + resolve(message.token); + } + break; + default: { + socket.close(); + } + } + socket.on('close', (code, reason) => { + if (code !== 1000) { + reject(reason); + } + }); + }); + }); + } +} + +export class GitHubManager { + private servers: Map; + + private static GitHubScopesTable: { [key: string] : string[] } = { + 'repo': ['repo:status', 'repo_deployment', 'public_repo', 'repo:invite'], + 'admin:org': ['write:org', 'read:org'], + 'admin:public_key': ['write:public_key', 'read:public_key'], + 'admin:org_hook': [], + 'gist': [], + 'notifications': [], + 'user': ['read:user', 'user:email', 'user:follow'], + 'delete_repo': [], + 'write:discussion': ['read:discussion'], + 'admin:gpg_key': ['write:gpg_key', 'read:gpg_key'] + }; + + public static AppScopes: string[] = SCOPES.split(' '); + + constructor() { + this.servers = new Map().set('github.com', true); + } + + public async isGitHub(host: vscode.Uri): Promise { + if (this.servers.has(host.authority)) { + return this.servers.get(host.authority); + } + + const options = GitHubManager.getOptions(host, 'HEAD'); + return new Promise((resolve, _) => { + const get = https.request(options, res => { + const ret = res.headers['x-github-request-id']; + resolve(ret !== undefined); + }); + + get.end(); + get.on('error', err => { + resolve(false); + }); + }).then(isGitHub => { + this.servers.set(host.authority, isGitHub); + return isGitHub; + }); + } + + public static getOptions(hostUri: vscode.Uri, method: string = 'GET', token?: string) { + const headers: { + 'user-agent': string; + authorization?: string; + } = { + 'user-agent': 'GitHub VSCode Pull Requests', + }; + if (token) { + headers.authorization = `token ${token}`; + } + return { + host: HostHelper.getApiHost(hostUri).authority, + port: 443, + method, + path: HostHelper.getApiPath(hostUri, '/rate_limit'), + headers, + }; + } + + public static validateScopes(scopes: string): boolean { + if (!scopes) { + return false; + } + const tokenScopes = scopes.split(', '); + return (this.AppScopes.every(x => tokenScopes.indexOf(x) >= 0 || tokenScopes.indexOf(this.getScopeSuperset(x)) >= 0)) + } + + private static getScopeSuperset(scope: string): string + { + for (let key in this.GitHubScopesTable) { + if (this.GitHubScopesTable[key].indexOf(scope) >= 0) + return key; + } + return scope; + } + +} + +export class GitHubServer { + public hostConfiguration: IHostConfiguration; + private hostUri: vscode.Uri; + + public constructor(host: string) { + host = host.toLocaleLowerCase(); + this.hostConfiguration = { host, username: 'oauth', token: undefined }; + this.hostUri = vscode.Uri.parse(host); + } + + public async login(): Promise { + return new Promise((resolve, reject) => { + new Client(this.hostConfiguration.host, SCOPES) + .start() + .then(token => { + this.hostConfiguration.token = token; + resolve(this.hostConfiguration); + }) + .catch(reason => { + resolve(undefined); + }); + }); + } + + public async checkAnonymousAccess(): Promise { + const options = GitHubManager.getOptions(this.hostUri); + return new Promise((resolve, _) => { + const get = https.request(options, res => { + resolve(res.statusCode === 200); + }); + + get.end(); + get.on('error', err => { + resolve(false); + }); + }); + } + + public async validate(username?: string, token?: string): Promise { + if (!username) { + username = this.hostConfiguration.username; + } + if (!token) { + token = this.hostConfiguration.token; + } + + const options = GitHubManager.getOptions(this.hostUri, 'GET', token); + + return new Promise((resolve, _) => { + const get = https.request(options, res => { + let hostConfig: IHostConfiguration | undefined; + try { + if (res.statusCode === 200) { + const scopes = res.headers['x-oauth-scopes'] as string; + if (GitHubManager.validateScopes(scopes)) { + this.hostConfiguration.username = username; + this.hostConfiguration.token = token; + hostConfig = this.hostConfiguration; + } + } + } catch(e) { + Logger.appendLine(`validate() error ${e}`); + } + resolve(hostConfig); + }); + + get.end(); + get.on('error', err => { + resolve(undefined); + }); + }); + } +} diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts new file mode 100644 index 0000000000..a964d609db --- /dev/null +++ b/src/authentication/vsConfiguration.ts @@ -0,0 +1,95 @@ +import * as vscode from 'vscode'; +import { Configuration, IHostConfiguration } from './configuration'; + +const SETTINGS_NAMESPACE = 'github'; +const HOSTS_KEY = 'hosts'; + +export class VSCodeConfiguration extends Configuration { + private _hosts: Map; + + constructor() { + super(undefined); + this.loadConfiguration(); + } + + public listenForVSCodeChanges(): vscode.Disposable { + return vscode.workspace.onDidChangeConfiguration(() => { + this.loadConfiguration(); + const config = this.getHost(this.host); + super.update(config.username, config.token, true); + }); + } + + public setHost(host: string): IHostConfiguration { + host = host.toLocaleLowerCase(); + if (host && host.substr(host.length - 2, 1) === '/') { + host = host.slice(0, -1); + } + + if (this.host === host) { + return this; + } + + if (host === undefined) { + this.host = host; + this.username = undefined; + this.token = undefined; + return this; + } + + this.host = host; + this.username = undefined; + this.token = undefined; + if (this.host && !this._hosts.has(this.host)) { + this._hosts.set(this.host, this); + } else { + const config = this.getHost(host); + super.update(config.username, config.token); + } + return this; + } + + public getHost(host: string): IHostConfiguration { + return this._hosts.get(host.toLocaleLowerCase()); + } + + public update(username: string | undefined, token: string | undefined, raiseEvent: boolean = true): void { + super.update(username, token, raiseEvent); + this.saveConfiguration(); + } + + private reset(): void { + this._hosts = new Map(); + } + + private loadConfiguration(): void { + this.reset(); + + const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); + let defaultEntry: IHostConfiguration[] = []; + let configHosts = config.get(HOSTS_KEY, defaultEntry); + + configHosts.forEach(c => c.host = c.host.toLocaleLowerCase()); + configHosts.map(c => this._hosts.set(c.host, c)); + + if (this.host && !this._hosts.has(this.host)) { + this._hosts.set(this.host, { + host: this.host, + username: this.username, + token: this.token, + }); + } + } + + private saveConfiguration(): void { + if (this.host) { + this._hosts.set(this.host, { + host: this.host, + username: this.username, + token: this.token, + }); + } + const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); + config.update(HOSTS_KEY, Array.from(this._hosts.values()), true); + } +} diff --git a/src/common/protocol.ts b/src/common/protocol.ts index e53a09b797..8caa1c8a2d 100644 --- a/src/common/protocol.ts +++ b/src/common/protocol.ts @@ -120,7 +120,7 @@ export class Protocol { } try { - return vscode.Uri.parse(`${scheme}://${this.host}/${this.nameWithOwner}`); + return vscode.Uri.parse(`${scheme}://${this.host.toLocaleLowerCase()}/${this.nameWithOwner.toLocaleLowerCase()}`); } catch (e) { return null; } diff --git a/src/configuration.ts b/src/configuration.ts deleted file mode 100644 index 637f43f665..0000000000 --- a/src/configuration.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; - -export interface IConfiguration { - username: string | undefined; - host: string; - accessToken: string | undefined; - onDidChange: vscode.Event; -} - -export class Configuration implements IConfiguration { - onDidChange: vscode.Event; - - private emitter: vscode.EventEmitter; - - constructor( - public username: string | undefined, - public host: string = 'github.com', - public accessToken: string - ) { - this.emitter = new vscode.EventEmitter(); - this.onDidChange = this.emitter.event; - } - - update(username, host = 'github.com', accessToken) { - if ( - username !== this.username || - host !== this.host || - accessToken !== this.accessToken - ) { - this.username = username; - this.host = host; - this.accessToken = accessToken; - this.emitter.fire(this); - } - } -} diff --git a/src/extension.ts b/src/extension.ts index aaefeee19e..9f52575503 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { Repository } from './common/repository'; -import { Configuration } from './configuration'; +import { VSCodeConfiguration } from './authentication/vsConfiguration'; import { Resource } from './common/resources'; import { ReviewManager } from './view/reviewManager'; import { registerCommands } from './commands'; @@ -20,38 +20,6 @@ export async function activate(context: vscode.ExtensionContext) { Resource.initialize(context); const rootPath = vscode.workspace.rootPath; - - const config = vscode.workspace.getConfiguration('github'); - const configuration = new Configuration( - config.get('username'), - config.get('host'), - config.get('accessToken') - ); - context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration(() => { - const config = vscode.workspace.getConfiguration('github'); - configuration.update( - config.get('username'), - config.get('host'), - config.get('accessToken') - ); - }) - ); - - configuration.onDidChange(async _ => { - if (prManager) { - try { - await prManager.clearCredentialCache(); - if (repository) { - repository.status(); - } - } catch (e) { - vscode.window.showErrorMessage(formatError(e)); - } - - } - }); - let gitExt = vscode.extensions.getExtension('vscode.git'); let importedGitApi = gitExt.exports; let gitPath = await importedGitApi.getGitPath(); @@ -61,12 +29,29 @@ export async function activate(context: vscode.ExtensionContext) { const repository = new Repository(rootPath); let repositoryInitialized = false; let prManager: PullRequestManager; + repository.onDidRunGitStatus(async e => { if (repositoryInitialized) { return; } Logger.appendLine('Git repository found, initializing review manager and pr tree view.'); + + const configuration = new VSCodeConfiguration(); + configuration.onDidChange(async _ => { + if (prManager) { + try { + await prManager.clearCredentialCache(); + if (repository) { + repository.status(); + } + } catch (e) { + vscode.window.showErrorMessage(formatError(e)); + } + } + }); + context.subscriptions.push(configuration.listenForVSCodeChanges()); + repositoryInitialized = true; prManager = new PullRequestManager(configuration, repository); await prManager.updateRepositories(); diff --git a/src/github/credentials.ts b/src/github/credentials.ts index c089785ca0..d710a748ca 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -3,48 +3,127 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Configuration } from '../configuration'; -import { Remote } from '../common/remote'; +import * as Octokit from '@octokit/rest'; import { fill } from 'git-credential-node'; -const Octokit = require('@octokit/rest'); +import * as vscode from 'vscode'; +import { IHostConfiguration, HostHelper } from '../authentication/configuration'; +import { GitHubServer } from '../authentication/githubServer'; +import { Remote } from '../common/remote'; +import { VSCodeConfiguration } from '../authentication/vsConfiguration'; +import Logger from '../common/logger'; + +const SIGNIN_COMMAND = 'Sign in'; export class CredentialStore { - private _octokits: { [key: string]: any }; - private _configuration: Configuration; - constructor(configuration: Configuration) { + private _octokits: Map; + private _configuration: VSCodeConfiguration; + constructor(configuration: any) { this._configuration = configuration; - this._octokits = []; + this._octokits = new Map(); } - reset() { - this._octokits = []; + public reset() { + this._octokits = new Map(); } - async getOctokit(remote: Remote) { - if (this._octokits[remote.url]) { - return this._octokits[remote.url]; + public async getOctokit(remote: Remote): Promise { + // the remote url might be http[s]/git/ssh but we always go through https for the api + // so use a normalized http[s] url regardless of the original protocol + const normalizedUri = remote.gitProtocol.normalizeUri(); + const host = `${normalizedUri.scheme}://${normalizedUri.authority}`; + + // for authentication purposes only the host portion matters + if (this._octokits.has(host)) { + return this._octokits.get(host); } - if (this._configuration.host === remote.host && this._configuration.accessToken) { - this._octokits[remote.url] = Octokit({}); - this._octokits[remote.url].authenticate({ - type: 'token', - token: this._configuration.accessToken - }); - return this._octokits[remote.url]; + this._configuration.setHost(host); + + let octokit: Octokit; + const creds: IHostConfiguration = this._configuration; + const server = new GitHubServer(host); + let error: string; + + if (creds.token && await server.validate(creds.username, creds.token)) { + octokit = this.createOctokit('token', creds); } else { - const data = await fill(remote.url); - if (!data) { - return null; + + // see if the system keychain has something we can use + const data = await fill(host); + if (data) { + const login = await server.validate(data.username, data.password); + if (login) { + octokit = this.createOctokit('token', login) + this._configuration.update(login.username, login.token, false); + } + } + + const result = await vscode.window.showInformationMessage( + `In order to use the Pull Requests functionality, you need to sign in to ${normalizedUri.authority}`, + SIGNIN_COMMAND); + + if (result === SIGNIN_COMMAND) { + try { + const login = await server.login(); + if (login) { + octokit = this.createOctokit('token', login) + this._configuration.update(login.username, login.token, false); + vscode.window.showInformationMessage(`You are now signed in to ${normalizedUri.authority}`); + } + } catch (e) { + error = e; + } + } + } + + if (!octokit) { + + Logger.appendLine(`Error signing in to ${normalizedUri.authority}: ${error}`); + + // anonymous access, not guaranteed to work for everything, and rate limited + if (await server.checkAnonymousAccess()) { + octokit = this.createOctokit('token', creds); + if (error) { + vscode.window.showWarningMessage(`Error signing in to ${normalizedUri.authority}: ${error}. Pull Requests functionality might not work correctly for this server.`) + } else { + vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Pull Requests functionality might not work correctly for this server.`) + } + + // the server does not support anonymous access, disable everything + } else { + if (error) { + vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${error}`); + } else { + vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Pull Requests functionality is disabled for this server.`) + } + } + } + + this._octokits.set(host, octokit); + return octokit; + } + + private createOctokit(type: string, creds: IHostConfiguration): Octokit { + const octokit = new Octokit({ + baseUrl: `${HostHelper.getApiHost(creds).toString().slice(0, -1)}${HostHelper.getApiPath(creds, '')}`, + headers: { 'user-agent': 'GitHub VSCode Pull Requests' } + }); + + if (creds.token) { + if (type === 'token') { + octokit.authenticate({ + type: 'token', + token: creds.token, + }); + } + else { + octokit.authenticate({ + type: 'basic', + username: creds.username, + password: creds.token, + }); } - this._octokits[remote.url] = Octokit({}); - this._octokits[remote.url].authenticate({ - type: 'basic', - username: data.username, - password: data.password - }); - - return this._octokits[remote.url]; } + return octokit; } } diff --git a/src/github/githubRepository.ts b/src/github/githubRepository.ts index d2c672bf74..9c8ec20d83 100644 --- a/src/github/githubRepository.ts +++ b/src/github/githubRepository.ts @@ -8,6 +8,7 @@ import Logger from "../common/logger"; import { Remote } from "../common/remote"; import { PRType } from "./interface"; import { PullRequestModel } from "./pullRequestModel"; +import { CredentialStore } from './credentials'; export const PULL_REQUEST_PAGE_SIZE = 20; @@ -16,8 +17,20 @@ export interface PullRequestData { hasMorePages: boolean; } export class GitHubRepository { + private _octokit: Octokit; + public get octokit(): Octokit { + if (this._octokit === undefined) { + throw new Error("Call ensure() before accessing this property."); + } + return this._octokit; + } + + constructor(public readonly remote: Remote, private readonly _credentialStore: CredentialStore) { + } - constructor(public readonly remote: Remote, public readonly octokit: Octokit) { + async ensure(): Promise { + this._octokit = await this._credentialStore.getOctokit(this.remote); + return this; } async getPullRequests(prType: PRType, page?: number): Promise { @@ -26,9 +39,10 @@ export class GitHubRepository { private async getAllPullRequests(page?: number): Promise { try { - const result = await this.octokit.pullRequests.getAll({ - owner: this.remote.owner, - repo: this.remote.repositoryName, + const { octokit, remote } = await this.ensure(); + const result = await octokit.pullRequests.getAll({ + owner: remote.owner, + repo: remote.repositoryName, per_page: PULL_REQUEST_PAGE_SIZE, page: page || 1 }); @@ -54,10 +68,11 @@ export class GitHubRepository { private async getPullRequestsForCategory(prType: PRType, page: number): Promise { try { - const user = await this.octokit.users.get({}); + const { octokit, remote } = await this.ensure(); + const user = await octokit.users.get({}); // Search api will not try to resolve repo that redirects, so get full name first - const repo = await this.octokit.repos.get({owner: this.remote.owner, repo: this.remote.repositoryName}); - const { data, headers } = await this.octokit.search.issues({ + const repo = await octokit.repos.get({owner: this.remote.owner, repo: this.remote.repositoryName}); + const { data, headers } = await octokit.search.issues({ q: this.getPRFetchQuery(repo.data.full_name, user.data.login, prType), per_page: PULL_REQUEST_PAGE_SIZE, page: page || 1 @@ -65,9 +80,9 @@ export class GitHubRepository { let promises = []; data.items.forEach(item => { promises.push(new Promise(async (resolve, reject) => { - let prData = await this.octokit.pullRequests.get({ - owner: this.remote.owner, - repo: this.remote.repositoryName, + let prData = await octokit.pullRequests.get({ + owner: remote.owner, + repo: remote.repositoryName, number: item.number }); resolve(prData); @@ -97,9 +112,10 @@ export class GitHubRepository { async getPullRequest(id: number): Promise { try { - let { data } = await this.octokit.pullRequests.get({ - owner: this.remote.owner, - repo: this.remote.repositoryName, + const { octokit, remote } = await this.ensure(); + let { data } = await octokit.pullRequests.get({ + owner: remote.owner, + repo: remote.repositoryName, number: id }); @@ -108,7 +124,7 @@ export class GitHubRepository { return null; } - return new PullRequestModel(this, this.remote, data); + return new PullRequestModel(this, remote, data); } catch (e) { Logger.appendLine(`GithubRepository> Unable to fetch PR: ${e}`); return null; diff --git a/src/github/pullRequestGitHelper.ts b/src/github/pullRequestGitHelper.ts index 3c53611d7f..1f523777d8 100644 --- a/src/github/pullRequestGitHelper.ts +++ b/src/github/pullRequestGitHelper.ts @@ -31,7 +31,7 @@ export class PullRequestGitHelper { let existing = await repository.getBranch(localBranchName); if (existing) { // already exist but the metadata is missing. - Logger.appendLine(`GitHelper> branch ${localBranchName} exits locally but metadata is missing.`) + Logger.appendLine(`GitHelper> branch ${localBranchName} exists locally but metadata is missing.`) await repository.checkout(localBranchName); } else { // the branch is from a fork diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 61698c08ec..e1285b1c75 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -14,7 +14,8 @@ import { IPullRequestManager, IPullRequestModel, IPullRequestsPagingOptions, PRT import { PullRequestGitHelper } from "./pullRequestGitHelper"; import { PullRequestModel } from "./pullRequestModel"; import { parserCommentDiffHunk } from "../common/diffHunk"; -import { Configuration } from "../configuration"; +import { Configuration } from '../authentication/configuration'; +import { GitHubManager } from '../authentication/githubServer'; import { formatError, uniqBy } from '../common/utils'; interface PageInformation { @@ -26,6 +27,7 @@ export class PullRequestManager implements IPullRequestManager { private _activePullRequest?: IPullRequestModel; private _credentialStore: CredentialStore; private _githubRepositories: GitHubRepository[]; + private _githubManager: GitHubManager; private _repositoryPageInformation: Map = new Map(); private _onDidChangeActivePullRequest = new vscode.EventEmitter(); @@ -34,6 +36,7 @@ export class PullRequestManager implements IPullRequestManager { constructor(private _configuration: Configuration, private _repository: Repository) { this._githubRepositories = []; this._credentialStore = new CredentialStore(this._configuration); + this._githubManager = new GitHubManager(); } get activePullRequest() { @@ -50,8 +53,11 @@ export class PullRequestManager implements IPullRequestManager { } async updateRepositories(): Promise { - let gitHubRemotes = this._repository.remotes.filter(remote => remote.host && remote.host.toLowerCase() === "github.com"); - gitHubRemotes = uniqBy(gitHubRemotes, remote => `${remote.host}:${remote.owner}/${remote.repositoryName}`); + const potentialRemotes = this._repository.remotes.filter(remote => remote.host); + let gitHubRemotes = await Promise.all(potentialRemotes.map(remote => this._githubManager.isGitHub(remote.gitProtocol.normalizeUri()))) + .then(results => potentialRemotes.filter((_, index, __) => results[index])); + gitHubRemotes = uniqBy(gitHubRemotes, remote => remote.gitProtocol.normalizeUri().toString()); + if (gitHubRemotes.length) { await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true); } else { @@ -62,10 +68,7 @@ export class PullRequestManager implements IPullRequestManager { for (let remote of gitHubRemotes) { const isRemoteForPR = await PullRequestGitHelper.isRemoteCreatedForPullRequest(this._repository, remote.remoteName); if (!isRemoteForPR) { - const octokit = await this._credentialStore.getOctokit(remote); - if (octokit) { - repositories.push(new GitHubRepository(remote, octokit)); - } + repositories.push(new GitHubRepository(remote, this._credentialStore)); } } @@ -198,13 +201,11 @@ export class PullRequestManager implements IPullRequestManager { } public mayHaveMorePages(): boolean { - return this._githubRepositories.some(repo => this._repositoryPageInformation.get(repo.remote.url.toString()).hasMorePages !== false); + return this._githubRepositories.some(repo => this._repositoryPageInformation.get(repo.remote.url.toString()).hasMorePages !== false); } async getPullRequestComments(pullRequest: IPullRequestModel): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const {remote, octokit } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const reviewData = await octokit.pullRequests.getComments({ owner: remote.owner, @@ -218,11 +219,11 @@ export class PullRequestManager implements IPullRequestManager { async getPullRequestCommits(pullRequest: IPullRequestModel): Promise { try { - const { octokit, remote } = (pullRequest as PullRequestModel).githubRepository; + const {remote, octokit } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const commitData = await octokit.pullRequests.getCommits({ - number: pullRequest.prNumber, - owner: remote.owner, - repo: remote.repositoryName + number: pullRequest.prNumber, + owner: remote.owner, + repo: remote.repositoryName }); return commitData.data; @@ -234,7 +235,7 @@ export class PullRequestManager implements IPullRequestManager { async getCommitChangedFiles(pullRequest: IPullRequestModel, commit: Commit): Promise { try { - const { octokit, remote } = (pullRequest as PullRequestModel).githubRepository; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const fullCommit = await octokit.repos.getCommit({ owner: remote.owner, repo: remote.repositoryName, @@ -249,9 +250,7 @@ export class PullRequestManager implements IPullRequestManager { } async getReviewComments(pullRequest: IPullRequestModel, reviewId: string): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const reviewData = await octokit.pullRequests.getReviewComments({ owner: remote.owner, @@ -265,9 +264,7 @@ export class PullRequestManager implements IPullRequestManager { } async getTimelineEvents(pullRequest: IPullRequestModel): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); let ret = await octokit.issues.getEventsTimeline({ owner: remote.owner, @@ -280,9 +277,7 @@ export class PullRequestManager implements IPullRequestManager { } async getIssueComments(pullRequest: IPullRequestModel): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const promise = await octokit.issues.getComments({ owner: remote.owner, @@ -295,9 +290,7 @@ export class PullRequestManager implements IPullRequestManager { } async createIssueComment(pullRequest: IPullRequestModel, text: string): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const promise = await octokit.issues.createComment({ body: text, @@ -310,9 +303,7 @@ export class PullRequestManager implements IPullRequestManager { } async createCommentReply(pullRequest: IPullRequestModel, body: string, reply_to: string) { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); let ret = await octokit.pullRequests.createCommentReply({ owner: remote.owner, @@ -326,9 +317,7 @@ export class PullRequestManager implements IPullRequestManager { } async createComment(pullRequest: IPullRequestModel, body: string, path: string, position: number) { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); let ret = await octokit.pullRequests.createComment({ owner: remote.owner, @@ -344,9 +333,7 @@ export class PullRequestManager implements IPullRequestManager { } async closePullRequest(pullRequest: IPullRequestModel): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); let ret = await octokit.pullRequests.update({ owner: remote.owner, @@ -359,9 +346,7 @@ export class PullRequestManager implements IPullRequestManager { } async getPullRequestChangedFiles(pullRequest: IPullRequestModel): Promise { - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const { data } = await octokit.pullRequests.getFiles({ owner: remote.owner, @@ -375,9 +360,7 @@ export class PullRequestManager implements IPullRequestManager { async fullfillPullRequestCommitInfo(pullRequest: IPullRequestModel): Promise { if (!pullRequest.base) { // this one is from search results, which is not complete. - let githubRepository = (pullRequest as PullRequestModel).githubRepository; - let octokit = githubRepository.octokit; - let remote = githubRepository.remote; + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); const { data } = await octokit.pullRequests.get({ owner: remote.owner, @@ -413,7 +396,7 @@ export class PullRequestManager implements IPullRequestManager { } async getBranchForPullRequestFromExistingRemotes(pullRequest: IPullRequestModel) { - return await PullRequestGitHelper.getBranchForPullRequestFromExistingRemotes(this._repository, this._githubRepositories,pullRequest); + return await PullRequestGitHelper.getBranchForPullRequestFromExistingRemotes(this._repository, this._githubRepositories, pullRequest); } async checkout(remote: Remote, branchName: string, pullRequest: IPullRequestModel): Promise { diff --git a/src/view/prsTreeDataProvider.ts b/src/view/prsTreeDataProvider.ts index 0f52933a13..efa2a5af66 100644 --- a/src/view/prsTreeDataProvider.ts +++ b/src/view/prsTreeDataProvider.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; -import { Configuration } from '../configuration'; +import { IConfiguration } from '../authentication/configuration'; import { Repository } from '../common/repository'; import { TreeNode } from './treeNodes/treeNode'; import { PRCategoryActionNode, CategoryTreeNode, PRCategoryActionType } from './treeNodes/categoryNode'; @@ -20,7 +20,7 @@ export class PullRequestsTreeDataProvider implements vscode.TreeDataProvider= 2.1.2 < 3" +iconv-lite@^0.4.4: + version "0.4.21" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798" + dependencies: + safer-buffer "^2.1.0" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -3112,7 +3147,7 @@ icss-utils@^2.1.0: dependencies: postcss "^6.0.1" -ieee754@^1.1.4: +ieee754@^1.1.11, ieee754@^1.1.4: version "1.1.11" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" @@ -3170,7 +3205,7 @@ inherits@1: version "1.0.2" resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3182,7 +3217,7 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^5.1.0, inquirer@^5.2.0: +inquirer@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" dependencies: @@ -3200,7 +3235,7 @@ inquirer@^5.1.0, inquirer@^5.2.0: strip-ansi "^4.0.0" through "^2.3.6" -interpret@^1.0.0, interpret@^1.0.4: +interpret@^1.0.0, interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -3382,11 +3417,11 @@ is-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" -is-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" +is-observable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" dependencies: - symbol-observable "^0.2.2" + symbol-observable "^1.1.0" is-odd@^2.0.0: version "2.0.0" @@ -3573,14 +3608,14 @@ jscodeshift@^0.4.0: write-file-atomic "^1.2.0" jscodeshift@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.5.0.tgz#bdb7b6cc20dd62c16aa728c3fa2d2fe66ca7c748" + version "0.5.1" + resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.5.1.tgz#4af6a721648be8638ae1464a190342da52960c33" dependencies: babel-plugin-transform-flow-strip-types "^6.8.0" babel-preset-es2015 "^6.9.0" babel-preset-stage-1 "^6.5.0" babel-register "^6.9.0" - babylon "^7.0.0-beta.30" + babylon "^7.0.0-beta.47" colors "^1.1.2" flow-parser "^0.*" lodash "^4.13.1" @@ -3588,7 +3623,7 @@ jscodeshift@^0.5.0: neo-async "^2.5.0" node-dir "0.1.8" nomnom "^1.8.1" - recast "^0.14.1" + recast "^0.15.0" temp "^0.8.1" write-file-atomic "^1.2.0" @@ -3604,7 +3639,7 @@ json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" -json-parse-better-errors@^1.0.1: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -3740,15 +3775,15 @@ listr-verbose-renderer@^0.4.0: date-fns "^1.27.2" figures "^1.7.0" -listr@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d" +listr@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.1.tgz#8a7afa4a7135cee4c921d128e0b7dfc6e522d43d" dependencies: - chalk "^1.1.3" + "@samverschueren/stream-to-observable" "^0.3.0" cli-truncate "^0.2.1" figures "^1.7.0" indent-string "^2.1.0" - is-observable "^0.2.0" + is-observable "^1.1.0" is-promise "^2.1.0" is-stream "^1.1.0" listr-silent-renderer "^1.1.1" @@ -3758,8 +3793,7 @@ listr@^0.13.0: log-update "^1.0.2" ora "^0.2.3" p-map "^1.1.1" - rxjs "^5.4.2" - stream-to-observable "^0.2.0" + rxjs "^6.1.0" strip-ansi "^3.0.1" load-json-file@^4.0.0: @@ -3889,14 +3923,10 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.5, lodash@^4.3.0: +lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.3.0: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" -lodash@^4.17.4: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -3942,14 +3972,7 @@ lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" -lru-cache@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^4.1.1: +lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: @@ -3968,6 +3991,10 @@ make-iterator@^1.0.0: dependencies: kind-of "^6.0.2" +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -4231,8 +4258,8 @@ mocha@^5.2.0: supports-color "5.4.0" moment@^2.22.1: - version "2.22.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" move-concurrently@^1.0.1: version "1.0.1" @@ -4294,8 +4321,8 @@ natives@^1.1.0: resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.4.tgz#2f0f224fc9a7dd53407c7667c84cf8dbe773de58" needle@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" + version "2.2.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.0.tgz#f14efc69cee1024b72c8b21c7bdf94a731dc12fa" dependencies: debug "^2.1.2" iconv-lite "^0.4.4" @@ -4314,8 +4341,8 @@ node-dir@0.1.8: resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d" node-fetch@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" node-libs-browser@^2.0.0: version "2.1.0" @@ -4634,8 +4661,8 @@ p-lazy@^1.0.0: resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835" p-limit@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" dependencies: p-try "^1.0.0" @@ -5076,8 +5103,8 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.1: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + version "6.0.22" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" dependencies: chalk "^2.4.1" source-map "^0.6.1" @@ -5095,9 +5122,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.5.3: - version "1.12.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" +prettier@^1.12.1: + version "1.13.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.5.tgz#7ae2076998c8edce79d63834e9b7b09fead6bfd0" pretty-bytes@^4.0.2: version "4.0.2" @@ -5165,16 +5192,16 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" punycode@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" query-string@^4.1.0: version "4.3.4" @@ -5316,11 +5343,11 @@ recast@^0.12.5: private "~0.1.5" source-map "~0.6.1" -recast@^0.14.1: - version "0.14.7" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d" +recast@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.15.0.tgz#b8c8bfdda245e1580c0a4d9fc25d4e820bf57208" dependencies: - ast-types "0.11.3" + ast-types "0.11.5" esprima "~4.0.0" private "~0.1.5" source-map "~0.6.1" @@ -5505,18 +5532,12 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@^1.1.6: +resolve@^1.1.6, resolve@^1.1.7: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" dependencies: path-parse "^1.0.5" -resolve@^1.1.7: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - dependencies: - path-parse "^1.0.5" - responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -5570,17 +5591,19 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^5.4.2, rxjs@^5.5.2: - version "5.5.10" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045" +rxjs@^5.5.2: + version "5.5.11" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87" dependencies: symbol-observable "1.0.1" -safe-buffer@^5.0.1, safe-buffer@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +rxjs@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.0.tgz#e024d0e180b72756a83c2aaea8f25423751ba978" + dependencies: + tslib "^1.9.0" -safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -5590,7 +5613,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -5825,13 +5848,14 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" dashdash "^1.12.0" getpass "^0.1.1" + safer-buffer "^2.0.2" optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" @@ -5880,8 +5904,8 @@ stream-each@^1.1.0: stream-shift "^1.0.0" stream-http@^2.7.2: - version "2.8.2" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.2.tgz#4126e8c6b107004465918aa2fc35549e77402c87" + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" @@ -5893,12 +5917,6 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -stream-to-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10" - dependencies: - any-observable "^0.2.0" - streamfilter@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9" @@ -6042,9 +6060,9 @@ symbol-observable@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" -symbol-observable@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" +symbol-observable@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" tapable@^1.0.0: version "1.0.0" @@ -6059,15 +6077,15 @@ tar@^2.2.1: inherits "2" tar@^4: - version "4.4.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.2.tgz#60685211ba46b38847b1ae7ee1a24d744a2cd462" + version "4.4.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.1.tgz#b25d5a8470c976fd7a9a8a350f42c59e9fa81749" dependencies: chownr "^1.0.1" fs-minipass "^1.2.5" minipass "^2.2.4" minizlib "^1.1.0" mkdirp "^0.5.0" - safe-buffer "^5.1.2" + safe-buffer "^5.1.1" yallist "^3.0.2" temp@^0.8.1: @@ -6202,8 +6220,8 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" ts-loader@^4.0.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.3.0.tgz#4e3ba172783d1256d3a23bdfadde011a795fae9e" + version "4.4.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.4.1.tgz#c93a46eea430ebce1f790dfe438caefb8670d365" dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -6211,6 +6229,10 @@ ts-loader@^4.0.1: micromatch "^3.1.4" semver "^5.0.1" +tslib@^1.9.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.2.tgz#8be0cc9a1f6dc7727c38deb16c2ebd1a2892988e" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -6230,8 +6252,8 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" typescript@^2.1.4: - version "2.8.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" + version "2.9.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961" uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -6313,16 +6335,16 @@ unset-value@^1.0.0: isobject "^3.0.0" untildify@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" + version "3.0.3" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" upath@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" uri-js@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.1.tgz#4595a80a51f356164e22970df64c7abd6ade9850" + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" dependencies: punycode "^2.1.0" @@ -6378,19 +6400,25 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, util@^0.10.3: +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: inherits "2.0.1" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + dependencies: + inherits "2.0.3" + uuid@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" -v8-compile-cache@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4" +v8-compile-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz#526492e35fc616864284700b7043e01baee09f0a" v8flags@^2.0.2: version "2.1.1" @@ -6535,7 +6563,7 @@ vinyl@^1.0.0, vinyl@^1.1.0, vinyl@^1.2.0: clone-stats "^0.0.1" replace-ext "0.0.1" -vinyl@^2.0.0, vinyl@^2.0.2, vinyl@^2.1.0: +vinyl@^2.0.0, vinyl@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" dependencies: @@ -6546,7 +6574,7 @@ vinyl@^2.0.0, vinyl@^2.0.2, vinyl@^2.1.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vinyl@^2.0.1: +vinyl@^2.0.1, vinyl@^2.0.2: version "2.1.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" dependencies: @@ -6590,16 +6618,6 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" -webassemblyjs@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webassemblyjs/-/webassemblyjs-1.4.3.tgz#0591893efb8fbde74498251cbe4b2d83df9239cb" - dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/validation" "1.4.3" - "@webassemblyjs/wasm-parser" "1.4.3" - "@webassemblyjs/wast-parser" "1.4.3" - long "^3.2.0" - webpack-addons@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/webpack-addons/-/webpack-addons-1.1.5.tgz#2b178dfe873fb6e75e40a819fa5c26e4a9bc837a" @@ -6607,35 +6625,35 @@ webpack-addons@^1.1.5: jscodeshift "^0.4.0" webpack-cli@^2.0.10: - version "2.1.3" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.1.3.tgz#65d166851abaa56067ef3f716b02a97ba6bbe84d" + version "2.1.5" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.1.5.tgz#3081fdeb2f205f0a54aa397986880b0c20a71f7a" dependencies: - chalk "^2.3.2" + chalk "^2.4.1" cross-spawn "^6.0.5" diff "^3.5.0" enhanced-resolve "^4.0.0" - envinfo "^4.4.2" + envinfo "^5.7.0" glob-all "^3.1.0" global-modules "^1.0.0" - got "^8.2.0" + got "^8.3.1" import-local "^1.0.0" - inquirer "^5.1.0" - interpret "^1.0.4" + inquirer "^5.2.0" + interpret "^1.1.0" jscodeshift "^0.5.0" - listr "^0.13.0" + listr "^0.14.1" loader-utils "^1.1.0" - lodash "^4.17.5" + lodash "^4.17.10" log-symbols "^2.2.0" mkdirp "^0.5.1" p-each-series "^1.0.0" p-lazy "^1.0.0" - prettier "^1.5.3" - supports-color "^5.3.0" - v8-compile-cache "^1.1.2" + prettier "^1.12.1" + supports-color "^5.4.0" + v8-compile-cache "^2.0.0" webpack-addons "^1.1.5" yargs "^11.1.0" - yeoman-environment "^2.0.0" - yeoman-generator "^2.0.4" + yeoman-environment "^2.1.1" + yeoman-generator "^2.0.5" webpack-sources@^1.0.1, webpack-sources@^1.1.0: version "1.1.0" @@ -6645,19 +6663,22 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0: source-map "~0.6.1" webpack@^4.1.0: - version "4.8.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.8.3.tgz#957c8e80000f9e5cc03d775e78b472d8954f4eeb" - dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/wasm-edit" "1.4.3" - "@webassemblyjs/wasm-parser" "1.4.3" - acorn "^5.0.0" + version "4.12.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.12.0.tgz#14758e035ae69747f68dd0edf3c5a572a82bdee9" + dependencies: + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-module-context" "1.5.12" + "@webassemblyjs/wasm-edit" "1.5.12" + "@webassemblyjs/wasm-opt" "1.5.12" + "@webassemblyjs/wasm-parser" "1.5.12" + acorn "^5.6.2" acorn-dynamic-import "^3.0.0" ajv "^6.1.0" ajv-keywords "^3.1.0" - chrome-trace-event "^0.1.1" + chrome-trace-event "^1.0.0" enhanced-resolve "^4.0.0" eslint-scope "^3.7.1" + json-parse-better-errors "^1.0.2" loader-runner "^2.3.0" loader-utils "^1.1.0" memory-fs "~0.4.1" @@ -6680,8 +6701,8 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" which@^1.2.14, which@^1.2.9: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" @@ -6716,6 +6737,12 @@ write-file-atomic@^1.2.0: imurmurhash "^0.1.4" slide "^1.1.5" +ws@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.0.0.tgz#eaa494aded00ac4289d455bac8d84c7c651cef35" + dependencies: + async-limiter "~1.0.0" + "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6778,9 +6805,9 @@ yazl@^2.2.1: dependencies: buffer-crc32 "~0.2.3" -yeoman-environment@^2.0.0, yeoman-environment@^2.0.5: - version "2.1.1" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.1.1.tgz#10a045f7fc4397873764882eae055a33e56ee1c5" +yeoman-environment@^2.0.5, yeoman-environment@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.2.0.tgz#6c0ee93a8d962a9f6dbc5ad4e90ae7ab34875393" dependencies: chalk "^2.1.0" cross-spawn "^6.0.5" @@ -6798,7 +6825,7 @@ yeoman-environment@^2.0.0, yeoman-environment@^2.0.5: text-table "^0.2.0" untildify "^3.0.2" -yeoman-generator@^2.0.4: +yeoman-generator@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-2.0.5.tgz#57b0b3474701293cc9ec965288f3400b00887c81" dependencies: