From 6108777c2164e0e7af51a0a8426f372cd6c8c70e Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 14:15:17 +0200 Subject: [PATCH 01/17] Hook up webflow auth during credential checks --- .gitignore | 3 +- src/authentication/configuration.ts | 40 +++++ src/authentication/vsConfiguration.ts | 97 +++++++++++ src/authentication/webflow.ts | 237 ++++++++++++++++++++++++++ src/extension.ts | 60 +++---- src/github/credentials.ts | 59 +++++-- src/github/pullRequestGitHelper.ts | 2 +- src/github/pullRequestManager.ts | 2 +- src/view/prsTreeDataProvider.ts | 2 +- 9 files changed, 448 insertions(+), 54 deletions(-) create mode 100644 src/authentication/configuration.ts create mode 100644 src/authentication/vsConfiguration.ts create mode 100644 src/authentication/webflow.ts 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/src/authentication/configuration.ts b/src/authentication/configuration.ts new file mode 100644 index 0000000000..7cfff5b2e6 --- /dev/null +++ b/src/authentication/configuration.ts @@ -0,0 +1,40 @@ +import * as vscode from 'vscode'; + +export interface IAppConfiguration { + clientId: string | undefined; + clientSecret: string | undefined; +} + +export class AppConfiguration implements IAppConfiguration { + constructor(public clientId: string | undefined, public clientSecret: string | undefined) {} +} + +export interface IHostConfiguration { + host: string; + username: string | undefined; + token: string | undefined; +} + +export interface IConfiguration extends IHostConfiguration { + onDidChange: vscode.Event; +} + +export class Configuration implements IConfiguration { + username: string | undefined; + token: string | undefined; + onDidChange: vscode.Event; + private emitter: vscode.EventEmitter; + + constructor(public host: string) { + this.emitter = new vscode.EventEmitter(); + this.onDidChange = this.emitter.event; + } + + update(username: string | undefined, token: string | undefined) { + if (username !== this.username || token !== this.token) { + this.username = username; + this.token = token; + this.emitter.fire(this); + } + } +} diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts new file mode 100644 index 0000000000..aba428164b --- /dev/null +++ b/src/authentication/vsConfiguration.ts @@ -0,0 +1,97 @@ +import * as vscode from 'vscode'; +import { AppConfiguration, Configuration, IHostConfiguration } from './configuration'; + +const SETTINGS_NAMESPACE = 'github'; +const CLIENT_ID_KEY = 'clientId'; +const CLIENT_SECRET_KEY = 'clientSecret'; +const HOSTS_KEY = 'hosts'; + +export class VSCodeAppConfiguration extends AppConfiguration { + constructor() { + const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); + // if clientID or clientSecret aren't passed in, look for them in the settings and the environment + const clientId: string | undefined = config.has(CLIENT_ID_KEY) + ? config.get(CLIENT_ID_KEY) + : process.env.GITHUB_VSCODE_CLIENT_ID; + + const clientSecret: string | undefined = config.has(CLIENT_SECRET_KEY) + ? config.get(CLIENT_SECRET_KEY) + : process.env.GITHUB_VSCODE_CLIENT_SECRET; + super(clientId, clientSecret); + } +} + +export class VSCodeConfiguration extends Configuration { + private hosts: { [key: string]: any }; + + constructor(public host: string) { + super(host); + this.hosts = []; + this.loadHosts(); + const config = this.getHost(this.host); + super.update(config.username, config.token); + } + + listenForVSCodeChanges() { + return vscode.workspace.onDidChangeConfiguration(() => { + this.loadHosts(); + const conf = this.getHost(this.host); + super.update(conf.username, conf.token); + }); + } + + update(username: string | undefined, token: string | undefined) { + super.update(username, token); + this.saveConfiguration(); + } + + getHost(host: string): IHostConfiguration { + if (this.hosts[host] === undefined) { + return; + } + let idx: number = this.hosts[host]; + return this.hosts[idx]; + } + + private reset() { + this.hosts = []; + } + + private loadHosts() { + this.reset(); + + const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); + let defaultEntry: IHostConfiguration[] = []; + let configHosts = config.get(HOSTS_KEY, defaultEntry); + + configHosts.map(c => this.setHost(c)); + + if (this.hosts[this.host] === undefined) { + this.setHost({ + host: this.host, + username: undefined, + token: undefined + }); + } + } + + private saveConfiguration() { + this.setHost({ + host: this.host, + username: this.username, + token: this.token + }); + const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); + config.update(HOSTS_KEY, this.hosts as [], true); + } + + private setHost(host: IHostConfiguration) { + if (this.hosts[host.host] === undefined) { + this.hosts.push(host); + this.hosts[host.host] = this.hosts.length - 1; + } else { + let idx: number = this.hosts[host.host]; + this.hosts[idx] = host; + } + } +} diff --git a/src/authentication/webflow.ts b/src/authentication/webflow.ts new file mode 100644 index 0000000000..2463ac5049 --- /dev/null +++ b/src/authentication/webflow.ts @@ -0,0 +1,237 @@ +import * as vscode from 'vscode'; +import { IHostConfiguration, IAppConfiguration } from './configuration'; +import * as url from 'url'; +import * as qs from 'querystring'; +import * as uuid from 'uuid/v4'; +import * as express from 'express'; +import * as http from 'http'; +import * as https from 'https'; +import { Request, Response } from '../../node_modules/@types/express-serve-static-core'; + +const OAUTH_STEP1 = '/login/oauth/authorize'; +const OAUTH_STEP2 = '/login/oauth/access_token'; +const SCOPES = 'read:user user:email repo write:discussion'; +const SERVER_PORT = 55555; +const SERVER_URI = `http://localhost:${SERVER_PORT}`; +const PATH_CONNECT = '/connect'; +const PATH_CANCEL = '/cancel'; +const PATH_AUTHENTICATE = '/authenticate'; + +export interface IWebFlow { + authenticated: boolean; + host: IHostConfiguration | undefined; +} + +export class WebFlow implements IWebFlow { + authenticated: boolean; + host: IHostConfiguration | undefined; + private loginPromise: Promise | undefined; + + private oauthStep1: url.URL; + private oauthStep2: url.URL; + private state: string; + private app: IAppConfiguration; + private listener: http.Server | undefined; + private failed: boolean; + + public constructor(app: IAppConfiguration, host: string) { + this.loginPromise = undefined; + this.authenticated = false; + this.failed = false; + this.state = uuid().toString(); + this.host = { host: host, username: 'oauth', token: undefined }; + this.app = app; + this.oauthStep1 = new url.URL(OAUTH_STEP1, 'https://' + this.host.host); + this.oauthStep1.search = qs.stringify(this.buildStep1Request()); + this.oauthStep2 = new url.URL(OAUTH_STEP2, 'https://' + this.host.host); + } + + public async login() { + const server = express(); + this.loginPromise = new Promise((resolve, reject) => { + server.get(PATH_CONNECT, this.serveConnectionPage()); + server.get(PATH_AUTHENTICATE, this.doAuthentication(resolve, reject)); + server.get(PATH_CANCEL, this.serveCancelPage(reject)); + this.listener = http.createServer(server); + this.listener.listen(SERVER_PORT); + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`${SERVER_URI}${PATH_CONNECT}`)); + }); + this.loginPromise.catch(err => { + this.stop(); + throw err; + }); + return this.loginPromise; + } + + public async validate(creds: IHostConfiguration) { + let options = { + host: `api.${creds.host}`, + port: 443, + method: 'GET', + path: '/rate_limit', + headers: { + 'User-Agent': 'VSCode Pull Requests', + Authorization: `token ${creds.token}` + } + }; + return new Promise((resolve, _) => { + const get = https.request(options, res => { + if (res.statusCode !== 200) { + resolve(false); + } + let scopes = res.headers['x-oauth-scopes'] as string; + if (!scopes) { + resolve(false); + } + let expected = SCOPES.split(' '); + let serverScopes = new Set(scopes.split(', ')); + var ret = expected.filter(x => { + return serverScopes.has(x); + }); + resolve(ret.length === expected.length); + }); + + get.end(); + get.on('error', err => { + resolve(false); + }); + }); + } + + private stop(): void { + if (!this.listener) { + return; + } + this.listener.close(); + this.listener = undefined; + } + + private serveConnectionPage(): any { + const _this = this; + return (req: Request, res: Response) => { + let url = _this.oauthStep1.toString(); + res.send(`

Authorizing Visual Studio Code to access GitHub.

+

If you initiated this authorization from Visual Studio Code, click continue to authorize access to GitHub.

+ + `); + }; + } + + private serveSuccessPage(res: Response): any { + res.send('

Login successful, you can go back to Visual Studio Code

'); + res.end(); + } + + private serveFailedPage(reason, res: Response): any { + res.send(`

Login failed: ${reason}

`); + res.end(); + } + + private serveCancelPage(reject: (reason?: any) => void): any { + const _this = this; + return (req: Request, res: Response) => { + res.send('

Login cancelled

'); + reject(new Error('User cancelled')); + _this.stop(); + }; + } + + private doAuthentication( + resolve: (value?: IWebFlow | PromiseLike | undefined) => void, + reject: (reason?: any) => void + ): any { + const _this = this; + return (req: Request, res: Response) => { + const reqState = req.param('state'); + const reqCode = req.param('code'); + if (!_this.verifyState(reqState)) { + let reason = 'Invalid state'; + this.serveFailedPage(reason, res); + reject(new Error(reason)); + return; + } + + const step2Data = _this.buildStep2Request(reqCode); + + const post = https.request(step2Data.headers, postResponse => { + if (postResponse.statusCode !== 200) { + let reason = `Error ${postResponse.statusCode}`; + this.serveFailedPage(reason, res); + reject(new Error(reason)); + return; + } + + let body = ''; + postResponse.on('data', chunk => { + body += chunk; + }); + + postResponse.on('end', () => { + if (_this.failed) { + return; + } + const json = JSON.parse(body); + _this.host.token = json['access_token'] as string; + _this.authenticated = true; + this.serveSuccessPage(res); + resolve(_this); + }); + + postResponse.on('error', err => { + _this.failed = true; + let reason = `Error ${err}`; + this.serveFailedPage(reason, res); + reject(err); + }); + }); + + post.on('error', err => { + _this.failed = true; + let reason = `Error ${err}`; + this.serveFailedPage(reason, res); + reject(err); + }); + + post.write(qs.stringify(step2Data.data)); + post.end(); + }; + } + + private verifyState(state: string): boolean { + return this.state === state; + } + + private buildStep1Request(): object { + return { + client_id: this.app.clientId, + scope: SCOPES, + state: this.state, + redirect_uri: `${SERVER_URI}${PATH_AUTHENTICATE}` + }; + } + + private buildStep2Request(code: string): { headers: object; data: object } { + const args = { + client_id: this.app.clientId, + client_secret: this.app.clientSecret, + code: code, + state: this.state, + redirect_uri: `${SERVER_URI}/done` + }; + return { + headers: { + hostname: this.oauthStep2.hostname, + port: 443, + method: 'POST', + path: this.oauthStep2.toString(), + headers: { + 'User-Agent': 'VSCode Pull Requests', + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(qs.stringify(args)) + } + }, + data: args + }; + } +} diff --git a/src/extension.ts b/src/extension.ts index aaefeee19e..614b82531e 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,11 +29,37 @@ 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; } + let remotes = repository.remotes.filter(remote => remote.host); + let remote = remotes.find(remote => remote.remoteName === 'origin'); + if (!remote && remotes.length > 0) { + remote = remotes[0]; + } + + if (!remote) { + return; + } + + const configuration = new VSCodeConfiguration(remote.host); + configuration.onDidChange(async _ => { + if (prManager) { + try { + await prManager.clearCredentialCache(); + if (repository) { + repository.status(); + } + } catch (e) { + vscode.window.showErrorMessage(formatError(e)); + } + + } + }); + Logger.appendLine('Git repository found, initializing review manager and pr tree view.'); repositoryInitialized = true; prManager = new PullRequestManager(configuration, repository); diff --git a/src/github/credentials.ts b/src/github/credentials.ts index c089785ca0..2a12d85b25 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Configuration } from '../configuration'; +import { Configuration, IHostConfiguration } from '../authentication/configuration'; +import { WebFlow } from '../authentication/webflow'; import { Remote } from '../common/remote'; import { fill } from 'git-credential-node'; +import { VSCodeAppConfiguration } from '../authentication/vsConfiguration'; const Octokit = require('@octokit/rest'); export class CredentialStore { @@ -25,26 +27,49 @@ export class CredentialStore { return this._octokits[remote.url]; } - 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]; + const webflow = new WebFlow(new VSCodeAppConfiguration(), remote.host); + let creds = this._configuration as IHostConfiguration; + if (creds.host === remote.host && creds.token && await webflow.validate(creds)) { + return this.authenticate('token', remote.url, creds); } else { - const data = await fill(remote.url); - if (!data) { - return null; + let data = await fill(remote.url); + if (data) { + creds.username = data.username; + creds.token = data.password; + if (await webflow.validate(creds)) { + return this.authenticate('basic', remote.url, creds); + } } - this._octokits[remote.url] = Octokit({}); - this._octokits[remote.url].authenticate({ + + const login = await webflow.login(); + + if (login.authenticated) + { + creds = login.host; + if (creds.host === remote.host) { + this._configuration.update(creds.username, creds.token); + } + return this.authenticate('token', remote.url, creds); + } + return null; + } + } + + private authenticate(type: string, url: string, creds: IHostConfiguration) { + this._octokits[url] = Octokit({}); + if (type === 'token') { + this._octokits[url].authenticate({ + type: 'token', + token: creds.token, + }); + } + else { + this._octokits[url].authenticate({ type: 'basic', - username: data.username, - password: data.password + username: creds.username, + password: creds.token, }); - - return this._octokits[remote.url]; } + return this._octokits[url]; } } diff --git a/src/github/pullRequestGitHelper.ts b/src/github/pullRequestGitHelper.ts index 1048842f99..abbdd065dc 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 74e2172a4b..723a810a17 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -14,7 +14,7 @@ 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 { formatError } from '../common/utils'; interface PageInformation { diff --git a/src/view/prsTreeDataProvider.ts b/src/view/prsTreeDataProvider.ts index 5b1ec19a27..fdf3c70bd3 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 { Configuration } from '../authentication/configuration'; import { Repository } from '../common/repository'; import { TreeNode } from './treeNodes/treeNode'; import { PRCategoryActionNode, CategoryTreeNode, PRCategoryActionType } from './treeNodes/categoryNode'; From db06fca08de5ce9bb128ca2e00ccf2c949371658 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 14:16:19 +0200 Subject: [PATCH 02/17] Update yarn dependencies --- package.json | 21 +++++++++++---------- yarn.lock | 10 ++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f5d54aa908..e02aa49c7b 100644 --- a/package.json +++ b/package.json @@ -188,25 +188,26 @@ "@types/mocha": "^5.2.2", "@types/node": "*", "css-loader": "^0.28.11", + "del": "^3.0.0", + "gulp": "3.9.1", + "gulp-mocha": "^6.0.0", + "gulp-typescript": "^5.0.0-alpha.2", + "gulp-util": "^3.0.8", "mocha": "^5.2.0", "style-loader": "^0.21.0", "ts-loader": "^4.0.1", "typescript": "^2.1.4", "webpack": "^4.1.0", - "webpack-cli": "^2.0.10", - "gulp": "3.9.1", - "gulp-typescript": "^5.0.0-alpha.2", - "del": "^3.0.0", - "gulp-mocha": "^6.0.0", - "gulp-util": "^3.0.8" + "webpack-cli": "^2.0.10" }, "dependencies": { - "iconv-lite": "0.4.23", - "vscode": "^1.1.18", "@octokit/rest": "^15.8.0", - "markdown-it": "^8.4.0", "git-credential-node": "^1.1.0", + "iconv-lite": "0.4.23", + "markdown-it": "^8.4.0", + "moment": "^2.22.1", "tmp": "^0.0.31", - "moment": "^2.22.1" + "vscode": "^1.1.18", + "ws": "^6.0.0" } } diff --git a/yarn.lock b/yarn.lock index 582a51bcee..6d21f89dde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -413,6 +413,10 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + async@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -6716,6 +6720,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" From 65903517a1363559c635be2e19354510a20b2f03 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 14:23:21 +0200 Subject: [PATCH 03/17] Update package-lock.json --- package-lock.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 3bd3b88cf0..6729007bcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-pull-request-github", - "version": "0.0.1", + "version": "0.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -535,6 +535,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 +11109,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", From 5a1a70886de383e04df520cc07b0bfda564a1808 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 13:39:47 +0200 Subject: [PATCH 04/17] Add missing hosts property --- package.json | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index e02aa49c7b..4fe319534c 100644 --- a/package.json +++ b/package.json @@ -28,26 +28,13 @@ "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" + } } } }, From 33d5ff5651c1fb44766d974806017c68b799f2b5 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 15:38:29 +0200 Subject: [PATCH 05/17] Add ws --- package-lock.json | 17 +++++++++++++++-- package.json | 3 ++- yarn.lock | 11 +++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6729007bcd..d121311d38 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", diff --git a/package.json b/package.json index 4fe319534c..a95ac426e4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "description": "Host tokens", "items": { "type": "object" - } + } } } }, @@ -189,6 +189,7 @@ }, "dependencies": { "@octokit/rest": "^15.8.0", + "@types/ws": "^5.1.2", "git-credential-node": "^1.1.0", "iconv-lite": "0.4.23", "markdown-it": "^8.4.0", diff --git a/yarn.lock b/yarn.lock index 6d21f89dde..86c715b8f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,10 @@ version "4.1.4" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.4.tgz#5ca073b330d90b4066d6ce18f60d57f2084ce8ca" +"@types/events@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + "@types/lodash@^4.14.106": version "4.14.106" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd" @@ -46,6 +50,13 @@ version "9.6.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.2.tgz#e49ac1adb458835e95ca6487bc20f916b37aff23" +"@types/ws@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" + dependencies: + "@types/events" "*" + "@types/node" "*" + "@webassemblyjs/ast@1.4.3": version "1.4.3" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.4.3.tgz#3b3f6fced944d8660273347533e6d4d315b5934a" From d605f653bea319fa3ada8463c2026b0454599bce Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 31 Jul 2018 19:01:26 +0200 Subject: [PATCH 06/17] Implementing oauth webflow via external webapp --- src/authentication/configuration.ts | 47 ++--- src/authentication/vsConfiguration.ts | 64 ++---- src/authentication/webflow.ts | 278 ++++++++------------------ src/configuration.ts | 41 ---- src/extension.ts | 2 +- src/github/credentials.ts | 33 ++- src/view/prsTreeDataProvider.ts | 4 +- src/view/reviewManager.ts | 4 +- 8 files changed, 144 insertions(+), 329 deletions(-) delete mode 100644 src/configuration.ts diff --git a/src/authentication/configuration.ts b/src/authentication/configuration.ts index 7cfff5b2e6..509690b045 100644 --- a/src/authentication/configuration.ts +++ b/src/authentication/configuration.ts @@ -1,40 +1,31 @@ import * as vscode from 'vscode'; -export interface IAppConfiguration { - clientId: string | undefined; - clientSecret: string | undefined; -} - -export class AppConfiguration implements IAppConfiguration { - constructor(public clientId: string | undefined, public clientSecret: string | undefined) {} -} - export interface IHostConfiguration { - host: string; - username: string | undefined; - token: string | undefined; + host: string; + username: string | undefined; + token: string | undefined; } export interface IConfiguration extends IHostConfiguration { - onDidChange: vscode.Event; + onDidChange: vscode.Event; } export class Configuration implements IConfiguration { - username: string | undefined; - token: string | undefined; - onDidChange: vscode.Event; - private emitter: vscode.EventEmitter; + username: string | undefined; + token: string | undefined; + onDidChange: vscode.Event; + private _emitter: vscode.EventEmitter; - constructor(public host: string) { - this.emitter = new vscode.EventEmitter(); - this.onDidChange = this.emitter.event; - } + constructor(public host: string) { + this._emitter = new vscode.EventEmitter(); + this.onDidChange = this._emitter.event; + } - update(username: string | undefined, token: string | undefined) { - if (username !== this.username || token !== this.token) { - this.username = username; - this.token = token; - this.emitter.fire(this); - } - } + public update(username: string | undefined, token: string | undefined): void { + if (username !== this.username || token !== this.token) { + this.username = username; + this.token = token; + this._emitter.fire(this); + } + } } diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts index aba428164b..3782738d7b 100644 --- a/src/authentication/vsConfiguration.ts +++ b/src/authentication/vsConfiguration.ts @@ -1,63 +1,41 @@ import * as vscode from 'vscode'; -import { AppConfiguration, Configuration, IHostConfiguration } from './configuration'; +import { Configuration, IHostConfiguration } from './configuration'; const SETTINGS_NAMESPACE = 'github'; -const CLIENT_ID_KEY = 'clientId'; -const CLIENT_SECRET_KEY = 'clientSecret'; const HOSTS_KEY = 'hosts'; -export class VSCodeAppConfiguration extends AppConfiguration { - constructor() { - const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); - // if clientID or clientSecret aren't passed in, look for them in the settings and the environment - const clientId: string | undefined = config.has(CLIENT_ID_KEY) - ? config.get(CLIENT_ID_KEY) - : process.env.GITHUB_VSCODE_CLIENT_ID; - - const clientSecret: string | undefined = config.has(CLIENT_SECRET_KEY) - ? config.get(CLIENT_SECRET_KEY) - : process.env.GITHUB_VSCODE_CLIENT_SECRET; - super(clientId, clientSecret); - } -} - export class VSCodeConfiguration extends Configuration { - private hosts: { [key: string]: any }; + private _hosts: Map; constructor(public host: string) { super(host); - this.hosts = []; this.loadHosts(); const config = this.getHost(this.host); super.update(config.username, config.token); } - listenForVSCodeChanges() { + public listenForVSCodeChanges(): vscode.Disposable { return vscode.workspace.onDidChangeConfiguration(() => { this.loadHosts(); - const conf = this.getHost(this.host); - super.update(conf.username, conf.token); + const config = this.getHost(this.host); + super.update(config.username, config.token); }); } - update(username: string | undefined, token: string | undefined) { + public update(username: string | undefined, token: string | undefined): void { super.update(username, token); this.saveConfiguration(); } - getHost(host: string): IHostConfiguration { - if (this.hosts[host] === undefined) { - return; - } - let idx: number = this.hosts[host]; - return this.hosts[idx]; + public getHost(host: string): IHostConfiguration { + return this._hosts.get(host); } - private reset() { - this.hosts = []; + private reset(): void { + this._hosts = new Map(); } - private loadHosts() { + private loadHosts(): void { this.reset(); const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); @@ -66,32 +44,26 @@ export class VSCodeConfiguration extends Configuration { configHosts.map(c => this.setHost(c)); - if (this.hosts[this.host] === undefined) { + if (!this._hosts.has(this.host)) { this.setHost({ host: this.host, username: undefined, - token: undefined + token: undefined, }); } } - private saveConfiguration() { + private saveConfiguration(): void { this.setHost({ host: this.host, username: this.username, - token: this.token + token: this.token, }); const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); - config.update(HOSTS_KEY, this.hosts as [], true); + config.update(HOSTS_KEY, Array.from(this._hosts.values()), true); } - private setHost(host: IHostConfiguration) { - if (this.hosts[host.host] === undefined) { - this.hosts.push(host); - this.hosts[host.host] = this.hosts.length - 1; - } else { - let idx: number = this.hosts[host.host]; - this.hosts[idx] = host; - } + private setHost(host: IHostConfiguration): void { + this._hosts.set(host.host, host); } } diff --git a/src/authentication/webflow.ts b/src/authentication/webflow.ts index 2463ac5049..2b89be7695 100644 --- a/src/authentication/webflow.ts +++ b/src/authentication/webflow.ts @@ -1,78 +1,112 @@ import * as vscode from 'vscode'; -import { IHostConfiguration, IAppConfiguration } from './configuration'; -import * as url from 'url'; -import * as qs from 'querystring'; -import * as uuid from 'uuid/v4'; -import * as express from 'express'; -import * as http from 'http'; +import { IHostConfiguration } from './configuration'; +import * as ws from 'ws'; import * as https from 'https'; -import { Request, Response } from '../../node_modules/@types/express-serve-static-core'; -const OAUTH_STEP1 = '/login/oauth/authorize'; -const OAUTH_STEP2 = '/login/oauth/access_token'; const SCOPES = 'read:user user:email repo write:discussion'; -const SERVER_PORT = 55555; -const SERVER_URI = `http://localhost:${SERVER_PORT}`; -const PATH_CONNECT = '/connect'; -const PATH_CANCEL = '/cancel'; -const PATH_AUTHENTICATE = '/authenticate'; +const HOST = 'github-editor-auth.herokuapp.com'; +const HTTP_PROTOCOL = 'https'; +const WS_PROTOCOL = '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('open', () => {}); + 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 interface IWebFlow { authenticated: boolean; - host: IHostConfiguration | undefined; + host: IHostConfiguration; } export class WebFlow implements IWebFlow { authenticated: boolean; - host: IHostConfiguration | undefined; - private loginPromise: Promise | undefined; - - private oauthStep1: url.URL; - private oauthStep2: url.URL; - private state: string; - private app: IAppConfiguration; - private listener: http.Server | undefined; - private failed: boolean; + host: IHostConfiguration; - public constructor(app: IAppConfiguration, host: string) { - this.loginPromise = undefined; - this.authenticated = false; - this.failed = false; - this.state = uuid().toString(); + public constructor(host: string) { this.host = { host: host, username: 'oauth', token: undefined }; - this.app = app; - this.oauthStep1 = new url.URL(OAUTH_STEP1, 'https://' + this.host.host); - this.oauthStep1.search = qs.stringify(this.buildStep1Request()); - this.oauthStep2 = new url.URL(OAUTH_STEP2, 'https://' + this.host.host); } - public async login() { - const server = express(); - this.loginPromise = new Promise((resolve, reject) => { - server.get(PATH_CONNECT, this.serveConnectionPage()); - server.get(PATH_AUTHENTICATE, this.doAuthentication(resolve, reject)); - server.get(PATH_CANCEL, this.serveCancelPage(reject)); - this.listener = http.createServer(server); - this.listener.listen(SERVER_PORT); - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`${SERVER_URI}${PATH_CONNECT}`)); - }); - this.loginPromise.catch(err => { - this.stop(); - throw err; + public async login(): Promise { + return new Promise((resolve, reject) => { + new Client(this.host.host, SCOPES) + .start() + .then(token => { + this.host.token = token; + resolve(this); + }) + .catch(reject); }); - return this.loginPromise; } - public async validate(creds: IHostConfiguration) { + public async validate(creds: IHostConfiguration): Promise { let options = { host: `api.${creds.host}`, port: 443, method: 'GET', path: '/rate_limit', headers: { - 'User-Agent': 'VSCode Pull Requests', - Authorization: `token ${creds.token}` - } + 'User-Agent': 'GitHub VSCode Pull Requests', + Authorization: `token ${creds.token}`, + }, }; return new Promise((resolve, _) => { const get = https.request(options, res => { @@ -85,10 +119,7 @@ export class WebFlow implements IWebFlow { } let expected = SCOPES.split(' '); let serverScopes = new Set(scopes.split(', ')); - var ret = expected.filter(x => { - return serverScopes.has(x); - }); - resolve(ret.length === expected.length); + resolve(expected.every(x => serverScopes.has(x))); }); get.end(); @@ -97,141 +128,4 @@ export class WebFlow implements IWebFlow { }); }); } - - private stop(): void { - if (!this.listener) { - return; - } - this.listener.close(); - this.listener = undefined; - } - - private serveConnectionPage(): any { - const _this = this; - return (req: Request, res: Response) => { - let url = _this.oauthStep1.toString(); - res.send(`

Authorizing Visual Studio Code to access GitHub.

-

If you initiated this authorization from Visual Studio Code, click continue to authorize access to GitHub.

- - `); - }; - } - - private serveSuccessPage(res: Response): any { - res.send('

Login successful, you can go back to Visual Studio Code

'); - res.end(); - } - - private serveFailedPage(reason, res: Response): any { - res.send(`

Login failed: ${reason}

`); - res.end(); - } - - private serveCancelPage(reject: (reason?: any) => void): any { - const _this = this; - return (req: Request, res: Response) => { - res.send('

Login cancelled

'); - reject(new Error('User cancelled')); - _this.stop(); - }; - } - - private doAuthentication( - resolve: (value?: IWebFlow | PromiseLike | undefined) => void, - reject: (reason?: any) => void - ): any { - const _this = this; - return (req: Request, res: Response) => { - const reqState = req.param('state'); - const reqCode = req.param('code'); - if (!_this.verifyState(reqState)) { - let reason = 'Invalid state'; - this.serveFailedPage(reason, res); - reject(new Error(reason)); - return; - } - - const step2Data = _this.buildStep2Request(reqCode); - - const post = https.request(step2Data.headers, postResponse => { - if (postResponse.statusCode !== 200) { - let reason = `Error ${postResponse.statusCode}`; - this.serveFailedPage(reason, res); - reject(new Error(reason)); - return; - } - - let body = ''; - postResponse.on('data', chunk => { - body += chunk; - }); - - postResponse.on('end', () => { - if (_this.failed) { - return; - } - const json = JSON.parse(body); - _this.host.token = json['access_token'] as string; - _this.authenticated = true; - this.serveSuccessPage(res); - resolve(_this); - }); - - postResponse.on('error', err => { - _this.failed = true; - let reason = `Error ${err}`; - this.serveFailedPage(reason, res); - reject(err); - }); - }); - - post.on('error', err => { - _this.failed = true; - let reason = `Error ${err}`; - this.serveFailedPage(reason, res); - reject(err); - }); - - post.write(qs.stringify(step2Data.data)); - post.end(); - }; - } - - private verifyState(state: string): boolean { - return this.state === state; - } - - private buildStep1Request(): object { - return { - client_id: this.app.clientId, - scope: SCOPES, - state: this.state, - redirect_uri: `${SERVER_URI}${PATH_AUTHENTICATE}` - }; - } - - private buildStep2Request(code: string): { headers: object; data: object } { - const args = { - client_id: this.app.clientId, - client_secret: this.app.clientSecret, - code: code, - state: this.state, - redirect_uri: `${SERVER_URI}/done` - }; - return { - headers: { - hostname: this.oauthStep2.hostname, - port: 443, - method: 'POST', - path: this.oauthStep2.toString(), - headers: { - 'User-Agent': 'VSCode Pull Requests', - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(qs.stringify(args)) - } - }, - data: args - }; - } } 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 614b82531e..74aee5394c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -56,9 +56,9 @@ export async function activate(context: vscode.ExtensionContext) { } catch (e) { vscode.window.showErrorMessage(formatError(e)); } - } }); + context.subscriptions.push(configuration.listenForVSCodeChanges()); Logger.appendLine('Git repository found, initializing review manager and pr tree view.'); repositoryInitialized = true; diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 2a12d85b25..428d7fc00b 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -7,8 +7,7 @@ import { Configuration, IHostConfiguration } from '../authentication/configurati import { WebFlow } from '../authentication/webflow'; import { Remote } from '../common/remote'; import { fill } from 'git-credential-node'; -import { VSCodeAppConfiguration } from '../authentication/vsConfiguration'; -const Octokit = require('@octokit/rest'); +import * as Octokit from '@octokit/rest'; export class CredentialStore { private _octokits: { [key: string]: any }; @@ -22,12 +21,12 @@ export class CredentialStore { this._octokits = []; } - async getOctokit(remote: Remote) { + async getOctokit(remote: Remote): Promise { if (this._octokits[remote.url]) { return this._octokits[remote.url]; } - const webflow = new WebFlow(new VSCodeAppConfiguration(), remote.host); + const webflow = new WebFlow(remote.host); let creds = this._configuration as IHostConfiguration; if (creds.host === remote.host && creds.token && await webflow.validate(creds)) { return this.authenticate('token', remote.url, creds); @@ -41,22 +40,22 @@ export class CredentialStore { } } - const login = await webflow.login(); - - if (login.authenticated) - { - creds = login.host; - if (creds.host === remote.host) { - this._configuration.update(creds.username, creds.token); - } - return this.authenticate('token', remote.url, creds); - } - return null; + return webflow.login() + .then(login => { + creds = login.host; + if (creds.host === remote.host) { + this._configuration.update(creds.username, creds.token); + } + return this.authenticate('token', remote.url, creds); + }) + .catch(reason => { + return undefined; + }); } } - private authenticate(type: string, url: string, creds: IHostConfiguration) { - this._octokits[url] = Octokit({}); + private authenticate(type: string, url: string, creds: IHostConfiguration): Octokit { + this._octokits[url] = new Octokit(); if (type === 'token') { this._octokits[url].authenticate({ type: 'token', diff --git a/src/view/prsTreeDataProvider.ts b/src/view/prsTreeDataProvider.ts index fdf3c70bd3..fd20b6d09a 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 '../authentication/configuration'; +import { IConfiguration } from '../authentication/configuration'; import { Repository } from '../common/repository'; import { TreeNode } from './treeNodes/treeNode'; import { PRCategoryActionNode, CategoryTreeNode, PRCategoryActionType } from './treeNodes/categoryNode'; @@ -19,7 +19,7 @@ export class PullRequestsTreeDataProvider implements vscode.TreeDataProvider Date: Wed, 1 Aug 2018 15:43:44 +0200 Subject: [PATCH 07/17] Add UI for initiating sign in. Try anonymous access if the server supports it Make sure we only try to sign in once to a given server. --- src/authentication/configuration.ts | 6 +- src/authentication/vsConfiguration.ts | 6 +- src/authentication/webflow.ts | 36 ++++++-- src/github/credentials.ts | 115 ++++++++++++++++---------- 4 files changed, 111 insertions(+), 52 deletions(-) diff --git a/src/authentication/configuration.ts b/src/authentication/configuration.ts index 509690b045..40346b68c9 100644 --- a/src/authentication/configuration.ts +++ b/src/authentication/configuration.ts @@ -21,11 +21,13 @@ export class Configuration implements IConfiguration { this.onDidChange = this._emitter.event; } - public update(username: string | undefined, token: string | undefined): void { + 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; - this._emitter.fire(this); + if (raiseEvent) { + this._emitter.fire(this); + } } } } diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts index 3782738d7b..5fbedf2426 100644 --- a/src/authentication/vsConfiguration.ts +++ b/src/authentication/vsConfiguration.ts @@ -18,12 +18,12 @@ export class VSCodeConfiguration extends Configuration { return vscode.workspace.onDidChangeConfiguration(() => { this.loadHosts(); const config = this.getHost(this.host); - super.update(config.username, config.token); + super.update(config.username, config.token, true); }); } - public update(username: string | undefined, token: string | undefined): void { - super.update(username, token); + public update(username: string | undefined, token: string | undefined, raiseEvent: boolean = true): void { + super.update(username, token, raiseEvent); this.saveConfiguration(); } diff --git a/src/authentication/webflow.ts b/src/authentication/webflow.ts index 2b89be7695..f2ff26bb6e 100644 --- a/src/authentication/webflow.ts +++ b/src/authentication/webflow.ts @@ -74,29 +74,55 @@ class Client { export interface IWebFlow { authenticated: boolean; - host: IHostConfiguration; + hostConfiguration: IHostConfiguration; } export class WebFlow implements IWebFlow { authenticated: boolean; - host: IHostConfiguration; + hostConfiguration: IHostConfiguration; public constructor(host: string) { - this.host = { host: host, username: 'oauth', token: undefined }; + this.hostConfiguration = { host: host, username: 'oauth', token: undefined }; } public async login(): Promise { return new Promise((resolve, reject) => { - new Client(this.host.host, SCOPES) + new Client(this.hostConfiguration.host, SCOPES) .start() .then(token => { - this.host.token = token; + this.hostConfiguration.token = token; resolve(this); }) .catch(reject); }); } + public async checkAnonymousAccess(): Promise { + let options = { + host: `api.${this.hostConfiguration.host}`, + port: 443, + method: 'GET', + path: '/rate_limit', + headers: { + 'User-Agent': 'GitHub VSCode Pull Requests', + }, + }; + return new Promise((resolve, _) => { + const get = https.request(options, res => { + if (res.statusCode !== 200) { + resolve(false); + } else { + resolve(true); + } + }); + + get.end(); + get.on('error', err => { + resolve(false); + }); + }); + } + public async validate(creds: IHostConfiguration): Promise { let options = { host: `api.${creds.host}`, diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 428d7fc00b..883c555bc3 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -3,72 +3,103 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as Octokit from '@octokit/rest'; +import { fill } from 'git-credential-node'; +import * as vscode from 'vscode'; import { Configuration, IHostConfiguration } from '../authentication/configuration'; import { WebFlow } from '../authentication/webflow'; import { Remote } from '../common/remote'; -import { fill } from 'git-credential-node'; -import * as Octokit from '@octokit/rest'; + +const SIGNIN_COMMAND = 'Sign in'; export class CredentialStore { - private _octokits: { [key: string]: any }; + //private _octokits: { [key: string]: Octokit | Promise }; + private _octokits: Map; private _configuration: Configuration; constructor(configuration: Configuration) { this._configuration = configuration; - this._octokits = []; + this._octokits = new Map(); } reset() { - this._octokits = []; + this._octokits = new Map(); } async getOctokit(remote: Remote): Promise { - if (this._octokits[remote.url]) { - return this._octokits[remote.url]; + // 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 = vscode.Uri.parse(`${normalizedUri.scheme}://${normalizedUri.authority}`); + const hostString = host.toString(); + + // for authentication purposes only the host portion matters + if (this._octokits.has(hostString)) { + return this._octokits.get(hostString); } + let octokit: Octokit; - const webflow = new WebFlow(remote.host); - let creds = this._configuration as IHostConfiguration; - if (creds.host === remote.host && creds.token && await webflow.validate(creds)) { - return this.authenticate('token', remote.url, creds); + const webflow = new WebFlow(normalizedUri.authority); + const creds: IHostConfiguration = this._configuration; + if (creds.token && await webflow.validate(creds)) { + octokit = this.createOctokit('token', hostString, creds); } else { - let data = await fill(remote.url); + const data = await fill(host.toString()); if (data) { - creds.username = data.username; - creds.token = data.password; - if (await webflow.validate(creds)) { - return this.authenticate('basic', remote.url, creds); + const newCreds = { host: creds.host, username: data.username, token: data.password }; + if (await webflow.validate(newCreds)) { + octokit = this.createOctokit('token', hostString, newCreds) + this._configuration.update(newCreds.username, newCreds.token, false); } } - return webflow.login() - .then(login => { - creds = login.host; - if (creds.host === remote.host) { - this._configuration.update(creds.username, creds.token); - } - return this.authenticate('token', remote.url, creds); - }) - .catch(reason => { - return undefined; - }); + 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) { + webflow.login() + .then(login => { + octokit = this.createOctokit('token', hostString, login.hostConfiguration) + this._configuration.update(login.hostConfiguration.username, login.hostConfiguration.token, false); + }) + .catch(reason => { + vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${reason}`); + }); + } } - } - private authenticate(type: string, url: string, creds: IHostConfiguration): Octokit { - this._octokits[url] = new Octokit(); - if (type === 'token') { - this._octokits[url].authenticate({ - type: 'token', - token: creds.token, - }); + if (!octokit) { + + // anonymous access, not guaranteed to work for everything, and rate limited + if (await webflow.checkAnonymousAccess()) { + vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Some functionality may fail.`) + octokit = this.createOctokit('token', hostString); + + // the server does not support anonymous access, disable everything + } else { + vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Pull Requests functionality won't work.`) + } } - else { - this._octokits[url].authenticate({ - type: 'basic', - username: creds.username, - password: creds.token, - }); + + this._octokits.set(hostString, octokit); + return octokit; + } + + private createOctokit(type: string, url: string, creds?: IHostConfiguration): Octokit { + const octokit = new Octokit(); + if (creds) { + if (type === 'token') { + octokit.authenticate({ + type: 'token', + token: creds.token, + }); + } + else { + octokit.authenticate({ + type: 'basic', + username: creds.username, + password: creds.token, + }); + } } - return this._octokits[url]; + return octokit; } } From 3c4c30d8ee3b73a7b9855f5bdca16a9d09c74063 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 1 Aug 2018 19:29:13 +0200 Subject: [PATCH 08/17] Add support for any github host (hosted business instances, enterprise, etc) --- src/authentication/configuration.ts | 20 +++++++++ src/authentication/webflow.ts | 49 +++++++++++---------- src/extension.ts | 3 +- src/github/credentials.ts | 66 +++++++++++++++++------------ src/github/pullRequestManager.ts | 14 +++--- 5 files changed, 94 insertions(+), 58 deletions(-) diff --git a/src/authentication/configuration.ts b/src/authentication/configuration.ts index 40346b68c9..1e519b5c51 100644 --- a/src/authentication/configuration.ts +++ b/src/authentication/configuration.ts @@ -6,6 +6,26 @@ export interface IHostConfiguration { token: string | undefined; } +export const HostHelper = class { + public static getApiHost(host: IHostConfiguration): vscode.Uri { + const hostUri = 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, path: string): string { + const hostUri = 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; } diff --git a/src/authentication/webflow.ts b/src/authentication/webflow.ts index f2ff26bb6e..9cdd13d2b7 100644 --- a/src/authentication/webflow.ts +++ b/src/authentication/webflow.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { IHostConfiguration } from './configuration'; +import { IHostConfiguration, HostHelper } from './configuration'; import * as ws from 'ws'; import * as https from 'https'; @@ -72,37 +72,33 @@ class Client { } } -export interface IWebFlow { - authenticated: boolean; - hostConfiguration: IHostConfiguration; -} - -export class WebFlow implements IWebFlow { - authenticated: boolean; +export class WebFlow { hostConfiguration: IHostConfiguration; public constructor(host: string) { this.hostConfiguration = { host: host, username: 'oauth', token: undefined }; } - public async login(): Promise { - return new Promise((resolve, reject) => { + public async login(): Promise { + return new Promise((resolve, reject) => { new Client(this.hostConfiguration.host, SCOPES) .start() .then(token => { this.hostConfiguration.token = token; - resolve(this); + resolve(this.hostConfiguration); }) - .catch(reject); + .catch(reason => { + resolve(undefined); + }); }); } public async checkAnonymousAccess(): Promise { let options = { - host: `api.${this.hostConfiguration.host}`, + host: HostHelper.getApiHost(this.hostConfiguration).authority, port: 443, method: 'GET', - path: '/rate_limit', + path: HostHelper.getApiPath(this.hostConfiguration, '/rate_limit'), headers: { 'User-Agent': 'GitHub VSCode Pull Requests', }, @@ -123,34 +119,41 @@ export class WebFlow implements IWebFlow { }); } - public async validate(creds: IHostConfiguration): Promise { + public async validate(username?: string, token?: string): Promise { + if (!username) username = this.hostConfiguration.username; + if (!token) token = this.hostConfiguration.token; let options = { - host: `api.${creds.host}`, + host: HostHelper.getApiHost(this.hostConfiguration).authority, port: 443, method: 'GET', - path: '/rate_limit', + path: HostHelper.getApiPath(this.hostConfiguration, '/rate_limit'), headers: { 'User-Agent': 'GitHub VSCode Pull Requests', - Authorization: `token ${creds.token}`, + Authorization: `token ${token}`, }, }; - return new Promise((resolve, _) => { + + return new Promise((resolve, _) => { const get = https.request(options, res => { if (res.statusCode !== 200) { - resolve(false); + resolve(undefined); } let scopes = res.headers['x-oauth-scopes'] as string; if (!scopes) { - resolve(false); + resolve(undefined); } let expected = SCOPES.split(' '); let serverScopes = new Set(scopes.split(', ')); - resolve(expected.every(x => serverScopes.has(x))); + if (expected.every(x => serverScopes.has(x))) { + this.hostConfiguration.username = username; + this.hostConfiguration.token = token; + resolve(this.hostConfiguration); + } }); get.end(); get.on('error', err => { - resolve(false); + resolve(undefined); }); }); } diff --git a/src/extension.ts b/src/extension.ts index 74aee5394c..5d33021d1b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -45,7 +45,8 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const configuration = new VSCodeConfiguration(remote.host); + const host = remote.gitProtocol.normalizeUri(); + const configuration = new VSCodeConfiguration(`${host.scheme}://${host.authority}`); configuration.onDidChange(async _ => { if (prManager) { try { diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 883c555bc3..55726e5497 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -6,7 +6,7 @@ import * as Octokit from '@octokit/rest'; import { fill } from 'git-credential-node'; import * as vscode from 'vscode'; -import { Configuration, IHostConfiguration } from '../authentication/configuration'; +import { Configuration, IHostConfiguration, HostHelper } from '../authentication/configuration'; import { WebFlow } from '../authentication/webflow'; import { Remote } from '../common/remote'; @@ -29,40 +29,46 @@ export class CredentialStore { // 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 = vscode.Uri.parse(`${normalizedUri.scheme}://${normalizedUri.authority}`); - const hostString = host.toString(); + const host = vscode.Uri.parse(`${normalizedUri.scheme}://${normalizedUri.authority}`).toString(); // for authentication purposes only the host portion matters - if (this._octokits.has(hostString)) { - return this._octokits.get(hostString); + if (this._octokits.has(host)) { + return this._octokits.get(host); } let octokit: Octokit; - const webflow = new WebFlow(normalizedUri.authority); + const webflow = new WebFlow(host); const creds: IHostConfiguration = this._configuration; - if (creds.token && await webflow.validate(creds)) { - octokit = this.createOctokit('token', hostString, creds); + if (creds.token && await webflow.validate(creds.username, creds.token)) { + octokit = this.createOctokit('token', creds); } else { - const data = await fill(host.toString()); + + // see if the system keychain has something we can use + const data = await fill(host); if (data) { - const newCreds = { host: creds.host, username: data.username, token: data.password }; - if (await webflow.validate(newCreds)) { - octokit = this.createOctokit('token', hostString, newCreds) - this._configuration.update(newCreds.username, newCreds.token, false); + const login = await webflow.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}`, + 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) { - webflow.login() - .then(login => { - octokit = this.createOctokit('token', hostString, login.hostConfiguration) - this._configuration.update(login.hostConfiguration.username, login.hostConfiguration.token, false); - }) - .catch(reason => { - vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${reason}`); - }); + try { + const login = await webflow.login(); + if (login) { + octokit = this.createOctokit('token', login) + this._configuration.update(login.username, login.token, false); + } + } catch (e) { + vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${e}`); + } + } else { + vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}`); } } @@ -71,21 +77,25 @@ export class CredentialStore { // anonymous access, not guaranteed to work for everything, and rate limited if (await webflow.checkAnonymousAccess()) { vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Some functionality may fail.`) - octokit = this.createOctokit('token', hostString); + octokit = this.createOctokit('token', creds); - // the server does not support anonymous access, disable everything + // the server does not support anonymous access, disable everything } else { vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Pull Requests functionality won't work.`) } } - this._octokits.set(hostString, octokit); + this._octokits.set(host, octokit); return octokit; } - private createOctokit(type: string, url: string, creds?: IHostConfiguration): Octokit { - const octokit = new Octokit(); - if (creds) { + 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', diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 723a810a17..6b5f2f13c8 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -50,12 +50,7 @@ export class PullRequestManager implements IPullRequestManager { } async updateRepositories(): Promise { - const gitHubRemotes = this._repository.remotes.filter(remote => remote.host && remote.host.toLowerCase() === "github.com"); - if (gitHubRemotes.length) { - await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true); - } else { - await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', false); - } + const gitHubRemotes = this._repository.remotes.filter(remote => remote.host); let repositories = []; for (let remote of gitHubRemotes) { @@ -68,6 +63,13 @@ export class PullRequestManager implements IPullRequestManager { } } + if (repositories.length > 0) { + await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true); + } else { + await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', false); + } + + this._githubRepositories = repositories; for (let repository of this._githubRepositories) { From 5b198d5adf1de997202cf7afebe70f2643380a26 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 2 Aug 2018 16:16:17 +0200 Subject: [PATCH 09/17] Check if the server is a github instance when selecting remotes to use Also show a message when the user is signed in successfully. --- src/authentication/configuration.ts | 14 +-- .../{webflow.ts => githubServer.ts} | 94 +++++++++++++------ src/authentication/vsConfiguration.ts | 68 +++++++++----- src/extension.ts | 14 +-- src/github/credentials.ts | 32 ++++--- src/github/pullRequestManager.ts | 30 +++--- 6 files changed, 152 insertions(+), 100 deletions(-) rename src/authentication/{webflow.ts => githubServer.ts} (62%) diff --git a/src/authentication/configuration.ts b/src/authentication/configuration.ts index 1e519b5c51..ab66e7bdce 100644 --- a/src/authentication/configuration.ts +++ b/src/authentication/configuration.ts @@ -7,8 +7,8 @@ export interface IHostConfiguration { } export const HostHelper = class { - public static getApiHost(host: IHostConfiguration): vscode.Uri { - const hostUri = vscode.Uri.parse(host.host); + 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 { @@ -16,8 +16,8 @@ export const HostHelper = class { } } - public static getApiPath(host: IHostConfiguration, path: string): string { - const hostUri = vscode.Uri.parse(host.host); + 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 { @@ -31,9 +31,9 @@ export interface IConfiguration extends IHostConfiguration { } export class Configuration implements IConfiguration { - username: string | undefined; - token: string | undefined; - onDidChange: vscode.Event; + public username: string | undefined; + public token: string | undefined; + public onDidChange: vscode.Event; private _emitter: vscode.EventEmitter; constructor(public host: string) { diff --git a/src/authentication/webflow.ts b/src/authentication/githubServer.ts similarity index 62% rename from src/authentication/webflow.ts rename to src/authentication/githubServer.ts index 9cdd13d2b7..c36cbdde3e 100644 --- a/src/authentication/webflow.ts +++ b/src/authentication/githubServer.ts @@ -29,7 +29,6 @@ class Client { public start(): Promise { return new Promise((resolve, reject) => { const socket = new ws(`${WS_PROTOCOL}://${HOST}`); - socket.on('open', () => {}); socket.on('message', data => { const message: IMessage = JSON.parse(data.toString()); @@ -72,11 +71,62 @@ class Client { } } -export class WebFlow { - hostConfiguration: IHostConfiguration; +export class GitHubManager { + private servers: Map; + + 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 => { + let 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, + }; + } +} + +export class GitHubServer { + public hostConfiguration: IHostConfiguration; + private hostUri: vscode.Uri; public constructor(host: string) { - this.hostConfiguration = { host: host, username: 'oauth', token: undefined }; + this.hostConfiguration = { host, username: 'oauth', token: undefined }; + this.hostUri = vscode.Uri.parse(host); } public async login(): Promise { @@ -94,15 +144,7 @@ export class WebFlow { } public async checkAnonymousAccess(): Promise { - let options = { - host: HostHelper.getApiHost(this.hostConfiguration).authority, - port: 443, - method: 'GET', - path: HostHelper.getApiPath(this.hostConfiguration, '/rate_limit'), - headers: { - 'User-Agent': 'GitHub VSCode Pull Requests', - }, - }; + const options = GitHubManager.getOptions(this.hostUri); return new Promise((resolve, _) => { const get = https.request(options, res => { if (res.statusCode !== 200) { @@ -120,30 +162,26 @@ export class WebFlow { } public async validate(username?: string, token?: string): Promise { - if (!username) username = this.hostConfiguration.username; - if (!token) token = this.hostConfiguration.token; - let options = { - host: HostHelper.getApiHost(this.hostConfiguration).authority, - port: 443, - method: 'GET', - path: HostHelper.getApiPath(this.hostConfiguration, '/rate_limit'), - headers: { - 'User-Agent': 'GitHub VSCode Pull Requests', - Authorization: `token ${token}`, - }, - }; + 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 => { if (res.statusCode !== 200) { resolve(undefined); } - let scopes = res.headers['x-oauth-scopes'] as string; + const scopes = res.headers['x-oauth-scopes'] as string; if (!scopes) { resolve(undefined); } - let expected = SCOPES.split(' '); - let serverScopes = new Set(scopes.split(', ')); + const expected = SCOPES.split(' '); + const serverScopes = new Set(scopes.split(', ')); if (expected.every(x => serverScopes.has(x))) { this.hostConfiguration.username = username; this.hostConfiguration.token = token; diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts index 5fbedf2426..29aeadcaa2 100644 --- a/src/authentication/vsConfiguration.ts +++ b/src/authentication/vsConfiguration.ts @@ -7,63 +7,79 @@ const HOSTS_KEY = 'hosts'; export class VSCodeConfiguration extends Configuration { private _hosts: Map; - constructor(public host: string) { - super(host); - this.loadHosts(); - const config = this.getHost(this.host); - super.update(config.username, config.token); + constructor() { + super(undefined); + this.loadConfiguration(); } public listenForVSCodeChanges(): vscode.Disposable { return vscode.workspace.onDidChangeConfiguration(() => { - this.loadHosts(); + this.loadConfiguration(); const config = this.getHost(this.host); super.update(config.username, config.token, true); }); } - public update(username: string | undefined, token: string | undefined, raiseEvent: boolean = true): void { - super.update(username, token, raiseEvent); - this.saveConfiguration(); + public setHost(host: string): IHostConfiguration { + 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); } + 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 loadHosts(): void { + private loadConfiguration(): void { this.reset(); const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); let defaultEntry: IHostConfiguration[] = []; let configHosts = config.get(HOSTS_KEY, defaultEntry); - configHosts.map(c => this.setHost(c)); + configHosts.map(c => this._hosts.set(c.host, c)); - if (!this._hosts.has(this.host)) { - this.setHost({ - host: this.host, - username: undefined, - token: undefined, - }); + if (this.host && !this._hosts.has(this.host)) { + this._hosts.set(this.host, this); } } private saveConfiguration(): void { - this.setHost({ - host: this.host, - username: this.username, - token: this.token, - }); + if (this.host) { + this._hosts.set(this.host, this); + } const config = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); config.update(HOSTS_KEY, Array.from(this._hosts.values()), true); } - - private setHost(host: IHostConfiguration): void { - this._hosts.set(host.host, host); - } } diff --git a/src/extension.ts b/src/extension.ts index 5d33021d1b..9f52575503 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -35,18 +35,9 @@ export async function activate(context: vscode.ExtensionContext) { return; } - let remotes = repository.remotes.filter(remote => remote.host); - let remote = remotes.find(remote => remote.remoteName === 'origin'); - if (!remote && remotes.length > 0) { - remote = remotes[0]; - } - - if (!remote) { - return; - } + Logger.appendLine('Git repository found, initializing review manager and pr tree view.'); - const host = remote.gitProtocol.normalizeUri(); - const configuration = new VSCodeConfiguration(`${host.scheme}://${host.authority}`); + const configuration = new VSCodeConfiguration(); configuration.onDidChange(async _ => { if (prManager) { try { @@ -61,7 +52,6 @@ export async function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push(configuration.listenForVSCodeChanges()); - Logger.appendLine('Git repository found, initializing review manager and pr tree view.'); repositoryInitialized = true; prManager = new PullRequestManager(configuration, repository); await prManager.updateRepositories(); diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 55726e5497..02126a74d5 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -6,47 +6,50 @@ import * as Octokit from '@octokit/rest'; import { fill } from 'git-credential-node'; import * as vscode from 'vscode'; -import { Configuration, IHostConfiguration, HostHelper } from '../authentication/configuration'; -import { WebFlow } from '../authentication/webflow'; +import { IHostConfiguration, HostHelper } from '../authentication/configuration'; +import { GitHubServer } from '../authentication/githubServer'; import { Remote } from '../common/remote'; +import { VSCodeConfiguration } from '../authentication/vsConfiguration'; const SIGNIN_COMMAND = 'Sign in'; export class CredentialStore { - //private _octokits: { [key: string]: Octokit | Promise }; private _octokits: Map; - private _configuration: Configuration; - constructor(configuration: Configuration) { + private _configuration: VSCodeConfiguration; + constructor(configuration: any) { this._configuration = configuration; this._octokits = new Map(); } - reset() { + public reset() { this._octokits = new Map(); } - async getOctokit(remote: Remote): Promise { + 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 = vscode.Uri.parse(`${normalizedUri.scheme}://${normalizedUri.authority}`).toString(); + const host = `${normalizedUri.scheme}://${normalizedUri.authority}`; // for authentication purposes only the host portion matters if (this._octokits.has(host)) { return this._octokits.get(host); } - let octokit: Octokit; - const webflow = new WebFlow(host); + this._configuration.setHost(host); + + let octokit: Octokit; const creds: IHostConfiguration = this._configuration; - if (creds.token && await webflow.validate(creds.username, creds.token)) { + const server = new GitHubServer(host); + + if (creds.token && await server.validate(creds.username, creds.token)) { octokit = this.createOctokit('token', creds); } else { // see if the system keychain has something we can use const data = await fill(host); if (data) { - const login = await webflow.validate(data.username, data.password); + const login = await server.validate(data.username, data.password); if (login) { octokit = this.createOctokit('token', login) this._configuration.update(login.username, login.token, false); @@ -59,10 +62,11 @@ export class CredentialStore { if (result === SIGNIN_COMMAND) { try { - const login = await webflow.login(); + 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) { vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${e}`); @@ -75,7 +79,7 @@ export class CredentialStore { if (!octokit) { // anonymous access, not guaranteed to work for everything, and rate limited - if (await webflow.checkAnonymousAccess()) { + if (await server.checkAnonymousAccess()) { vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Some functionality may fail.`) octokit = this.createOctokit('token', creds); diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 6b5f2f13c8..8b1aafca7b 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -16,6 +16,7 @@ import { PullRequestModel } from "./pullRequestModel"; import { parserCommentDiffHunk } from "../common/diffHunk"; import { Configuration } from '../authentication/configuration'; import { formatError } from '../common/utils'; +import { GitHubManager } from '../authentication/githubserver'; interface PageInformation { pullRequestPage: number; @@ -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,7 +53,15 @@ export class PullRequestManager implements IPullRequestManager { } async updateRepositories(): Promise { - const gitHubRemotes = this._repository.remotes.filter(remote => remote.host); + const potentialRemotes = this._repository.remotes.filter(remote => remote.host); + const gitHubRemotes = await Promise.all(potentialRemotes.map(remote => this._githubManager.isGitHub(remote.gitProtocol.normalizeUri()))) + .then(results => potentialRemotes.filter((_, index, __) => results[index])); + + if (gitHubRemotes.length) { + await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true); + } else { + await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', false); + } let repositories = []; for (let remote of gitHubRemotes) { @@ -63,13 +74,6 @@ export class PullRequestManager implements IPullRequestManager { } } - if (repositories.length > 0) { - await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true); - } else { - await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', false); - } - - this._githubRepositories = repositories; for (let repository of this._githubRepositories) { @@ -159,7 +163,7 @@ 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 { @@ -181,9 +185,9 @@ export class PullRequestManager implements IPullRequestManager { try { const { octokit, remote } = (pullRequest as PullRequestModel).githubRepository; 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; @@ -386,7 +390,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 { From 3856af59ccf4fb2094454d2f3dc98ca1359bcbe1 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 2 Aug 2018 16:34:27 +0200 Subject: [PATCH 10/17] Oh yeah, we don't want to save all the other fields... --- src/authentication/vsConfiguration.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts index 29aeadcaa2..696a52217c 100644 --- a/src/authentication/vsConfiguration.ts +++ b/src/authentication/vsConfiguration.ts @@ -71,13 +71,21 @@ export class VSCodeConfiguration extends Configuration { configHosts.map(c => this._hosts.set(c.host, c)); if (this.host && !this._hosts.has(this.host)) { - this._hosts.set(this.host, this); + 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, this); + 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); From 7c64a683e15e92beb560dea456c1548c08d3066d Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 2 Aug 2018 16:48:13 +0200 Subject: [PATCH 11/17] Tweak the authentication user messages --- src/github/credentials.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 02126a74d5..d710a748ca 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -10,6 +10,7 @@ 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'; @@ -41,6 +42,7 @@ export class CredentialStore { 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); @@ -69,23 +71,31 @@ export class CredentialStore { vscode.window.showInformationMessage(`You are now signed in to ${normalizedUri.authority}`); } } catch (e) { - vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}: ${e}`); + error = e; } - } else { - vscode.window.showErrorMessage(`Error signing in to ${normalizedUri.authority}`); } } 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()) { - vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Some functionality may fail.`) 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 { - vscode.window.showWarningMessage(`Not signed in to ${normalizedUri.authority}. Pull Requests functionality won't work.`) + 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.`) + } } } From f6f779995a7b32d63dcfe0b0eab62c3757f9bccd Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 7 Aug 2018 11:50:24 +0200 Subject: [PATCH 12/17] Sync yarn.lock and package-lock.json --- package-lock.json | 2 +- yarn.lock | 586 +++++++++++++++++++++++----------------------- 2 files changed, 297 insertions(+), 291 deletions(-) diff --git a/package-lock.json b/package-lock.json index 768a954ba0..58311e1051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-pull-request-github", - "version": "0.0.5", + "version": "0.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 86c715b8f1..451a38a609 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,12 +10,12 @@ glob-to-regexp "^0.3.0" "@nodelib/fs.stat@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.0.2.tgz#d056b68999769728a1cff8d643bc59eb6f0be436" + version "1.1.0" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" -"@octokit/rest@^15.8.0": - version "15.9.2" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.9.2.tgz#148b80676c26253c793d707779004ce9009f235f" +"@octokit/rest@^15.9.5": + version "15.9.5" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.9.5.tgz#e356d202bd0b517e381f705ad77d98ccb84e0c65" dependencies: before-after-hook "^1.1.0" btoa-lite "^1.0.0" @@ -26,6 +26,12 @@ node-fetch "^2.1.1" url-template "^2.0.8" +"@samverschueren/stream-to-observable@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" + dependencies: + any-observable "^0.3.0" + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -39,16 +45,16 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" "@types/lodash@^4.14.106": - version "4.14.106" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd" + version "4.14.109" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.109.tgz#b1c4442239730bf35cabaf493c772b18c045886d" "@types/mocha@^5.2.2": - version "5.2.3" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.3.tgz#11f3a5629d67cd444fa6c94536576244e6a52ea9" + version "5.2.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.2.tgz#202d2b8fe1364c5b617b439b26a54f0e75eac0a7" "@types/node@*": - version "9.6.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.2.tgz#e49ac1adb458835e95ca6487bc20f916b37aff23" + version "10.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.2.tgz#3840ec6c12556fdda6e0e6d036df853101d732a4" "@types/ws@^5.1.2": version "5.1.2" @@ -57,120 +63,140 @@ "@types/events" "*" "@types/node" "*" -"@webassemblyjs/ast@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.4.3.tgz#3b3f6fced944d8660273347533e6d4d315b5934a" +"@webassemblyjs/ast@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.12.tgz#a9acbcb3f25333c4edfa1fdf3186b1ccf64e6664" dependencies: - "@webassemblyjs/helper-wasm-bytecode" "1.4.3" - "@webassemblyjs/wast-parser" "1.4.3" + "@webassemblyjs/helper-module-context" "1.5.12" + "@webassemblyjs/helper-wasm-bytecode" "1.5.12" + "@webassemblyjs/wast-parser" "1.5.12" debug "^3.1.0" - webassemblyjs "1.4.3" + mamacro "^0.0.3" + +"@webassemblyjs/floating-point-hex-parser@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.12.tgz#0f36044ffe9652468ce7ae5a08716a4eeff9cd9c" -"@webassemblyjs/floating-point-hex-parser@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.4.3.tgz#f5aee4c376a717c74264d7bacada981e7e44faad" +"@webassemblyjs/helper-api-error@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.12.tgz#05466833ff2f9d8953a1a327746e1d112ea62aaf" -"@webassemblyjs/helper-buffer@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.4.3.tgz#0434b55958519bf503697d3824857b1dea80b729" +"@webassemblyjs/helper-buffer@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.12.tgz#1f0de5aaabefef89aec314f7f970009cd159c73d" dependencies: debug "^3.1.0" -"@webassemblyjs/helper-code-frame@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.4.3.tgz#f1349ca3e01a8e29ee2098c770773ef97af43641" +"@webassemblyjs/helper-code-frame@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.12.tgz#3cdc1953093760d1c0f0caf745ccd62bdb6627c7" dependencies: - "@webassemblyjs/wast-printer" "1.4.3" - -"@webassemblyjs/helper-fsm@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.4.3.tgz#65a921db48fb43e868f17b27497870bdcae22b79" + "@webassemblyjs/wast-printer" "1.5.12" -"@webassemblyjs/helper-wasm-bytecode@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.4.3.tgz#0e5b4b5418e33f8a26e940b7809862828c3721a5" +"@webassemblyjs/helper-fsm@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.12.tgz#6bc1442b037f8e30f2e57b987cee5c806dd15027" -"@webassemblyjs/helper-wasm-section@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.4.3.tgz#9ceedd53a3f152c3412e072887ade668d0b1acbf" +"@webassemblyjs/helper-module-context@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.12.tgz#b5588ca78b33b8a0da75f9ab8c769a3707baa861" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/helper-buffer" "1.4.3" - "@webassemblyjs/helper-wasm-bytecode" "1.4.3" - "@webassemblyjs/wasm-gen" "1.4.3" debug "^3.1.0" + mamacro "^0.0.3" -"@webassemblyjs/leb128@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.4.3.tgz#5a5e5949dbb5adfe3ae95664d0439927ac557fb8" - dependencies: - leb "^0.3.0" - -"@webassemblyjs/validation@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/validation/-/validation-1.4.3.tgz#9e66c9b3079d7bbcf2070c1bf52a54af2a09aac9" - dependencies: - "@webassemblyjs/ast" "1.4.3" +"@webassemblyjs/helper-wasm-bytecode@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.12.tgz#d12a3859db882a448891a866a05d0be63785b616" -"@webassemblyjs/wasm-edit@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.4.3.tgz#87febd565e0ffb5ae25f6495bb3958d17aa0a779" +"@webassemblyjs/helper-wasm-section@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.12.tgz#ff9fe1507d368ad437e7969d25e8c1693dac1884" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/helper-buffer" "1.4.3" - "@webassemblyjs/helper-wasm-bytecode" "1.4.3" - "@webassemblyjs/helper-wasm-section" "1.4.3" - "@webassemblyjs/wasm-gen" "1.4.3" - "@webassemblyjs/wasm-opt" "1.4.3" - "@webassemblyjs/wasm-parser" "1.4.3" - "@webassemblyjs/wast-printer" "1.4.3" + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-buffer" "1.5.12" + "@webassemblyjs/helper-wasm-bytecode" "1.5.12" + "@webassemblyjs/wasm-gen" "1.5.12" debug "^3.1.0" -"@webassemblyjs/wasm-gen@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.4.3.tgz#8553164d0154a6be8f74d653d7ab355f73240aa4" +"@webassemblyjs/ieee754@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.12.tgz#ee9574bc558888f13097ce3e7900dff234ea19a4" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/helper-wasm-bytecode" "1.4.3" - "@webassemblyjs/leb128" "1.4.3" + ieee754 "^1.1.11" -"@webassemblyjs/wasm-opt@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.4.3.tgz#26c7a23bfb136aa405b1d3410e63408ec60894b8" +"@webassemblyjs/leb128@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.12.tgz#0308eec652765ee567d8a5fa108b4f0b25b458e1" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/helper-buffer" "1.4.3" - "@webassemblyjs/wasm-gen" "1.4.3" - "@webassemblyjs/wasm-parser" "1.4.3" + leb "^0.3.0" + +"@webassemblyjs/utf8@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.12.tgz#d5916222ef314bf60d6806ed5ac045989bfd92ce" + +"@webassemblyjs/wasm-edit@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.12.tgz#821c9358e644a166f2c910e5af1b46ce795a17aa" + dependencies: + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-buffer" "1.5.12" + "@webassemblyjs/helper-wasm-bytecode" "1.5.12" + "@webassemblyjs/helper-wasm-section" "1.5.12" + "@webassemblyjs/wasm-gen" "1.5.12" + "@webassemblyjs/wasm-opt" "1.5.12" + "@webassemblyjs/wasm-parser" "1.5.12" + "@webassemblyjs/wast-printer" "1.5.12" debug "^3.1.0" -"@webassemblyjs/wasm-parser@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.4.3.tgz#7ddd3e408f8542647ed612019cfb780830993698" +"@webassemblyjs/wasm-gen@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.12.tgz#0b7ccfdb93dab902cc0251014e2e18bae3139bcb" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/helper-wasm-bytecode" "1.4.3" - "@webassemblyjs/leb128" "1.4.3" - "@webassemblyjs/wasm-parser" "1.4.3" - webassemblyjs "1.4.3" + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-wasm-bytecode" "1.5.12" + "@webassemblyjs/ieee754" "1.5.12" + "@webassemblyjs/leb128" "1.5.12" + "@webassemblyjs/utf8" "1.5.12" -"@webassemblyjs/wast-parser@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.4.3.tgz#3250402e2c5ed53dbe2233c9de1fe1f9f0d51745" +"@webassemblyjs/wasm-opt@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.12.tgz#bd758a8bc670f585ff1ae85f84095a9e0229cbc9" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/floating-point-hex-parser" "1.4.3" - "@webassemblyjs/helper-code-frame" "1.4.3" - "@webassemblyjs/helper-fsm" "1.4.3" + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-buffer" "1.5.12" + "@webassemblyjs/wasm-gen" "1.5.12" + "@webassemblyjs/wasm-parser" "1.5.12" + debug "^3.1.0" + +"@webassemblyjs/wasm-parser@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.12.tgz#7b10b4388ecf98bd7a22e702aa62ec2f46d0c75e" + dependencies: + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/helper-api-error" "1.5.12" + "@webassemblyjs/helper-wasm-bytecode" "1.5.12" + "@webassemblyjs/ieee754" "1.5.12" + "@webassemblyjs/leb128" "1.5.12" + "@webassemblyjs/utf8" "1.5.12" + +"@webassemblyjs/wast-parser@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.12.tgz#9cf5ae600ecae0640437b5d4de5dd6b6088d0d8b" + dependencies: + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/floating-point-hex-parser" "1.5.12" + "@webassemblyjs/helper-api-error" "1.5.12" + "@webassemblyjs/helper-code-frame" "1.5.12" + "@webassemblyjs/helper-fsm" "1.5.12" long "^3.2.0" - webassemblyjs "1.4.3" + mamacro "^0.0.3" -"@webassemblyjs/wast-printer@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.4.3.tgz#3d59aa8d0252d6814a3ef4e6d2a34c9ded3904e0" +"@webassemblyjs/wast-printer@1.5.12": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.12.tgz#563ca4d01b22d21640b2463dc5e3d7f7d9dac520" dependencies: - "@webassemblyjs/ast" "1.4.3" - "@webassemblyjs/wast-parser" "1.4.3" + "@webassemblyjs/ast" "1.5.12" + "@webassemblyjs/wast-parser" "1.5.12" long "^3.2.0" abbrev@1: @@ -183,13 +209,13 @@ acorn-dynamic-import@^3.0.0: dependencies: acorn "^5.0.0" -acorn@^5.0.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" +acorn@^5.0.0, acorn@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7" agent-base@4, agent-base@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" dependencies: es6-promisify "^5.0.0" @@ -226,8 +252,8 @@ ansi-colors@^1.0.1: ansi-wrap "^0.1.0" ansi-colors@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-2.0.2.tgz#1a95e0163c461846d44cbdea97bb2ac1aa35f1b2" + version "2.0.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-2.0.1.tgz#592299d94d1c403642098ec4fd7b7f2d8895a957" ansi-cyan@^0.1.1: version "0.1.1" @@ -281,9 +307,9 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" -any-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242" +any-observable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" anymatch@^2.0.0: version "2.0.0" @@ -416,9 +442,9 @@ ast-types@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd" -ast-types@0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8" +ast-types@0.11.5: + version "0.11.5" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.5.tgz#9890825d660c03c28339f315e9fa0a360e31ec28" async-each@^1.0.0: version "1.0.1" @@ -433,10 +459,10 @@ async@^1.5.0: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: - lodash "^4.14.0" + lodash "^4.17.10" asynckit@^0.4.0: version "0.4.0" @@ -462,8 +488,8 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" babel-code-frame@^6.26.0: version "6.26.0" @@ -1049,7 +1075,7 @@ babylon@^6.17.3, babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" -babylon@^7.0.0-beta.30: +babylon@^7.0.0-beta.47: version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.47.tgz#6d1fa44f0abec41ab7c780481e62fd9aafbdea80" @@ -1231,8 +1257,8 @@ buffer-equal@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" buffer-from@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" buffer-xor@^1.0.3: version "1.0.3" @@ -1316,8 +1342,8 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000859" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000859.tgz#6e813a1757f19a93cb3675f6b5037aca80bea062" + version "1.0.30000851" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000851.tgz#8a0d3ca4dde72068560acc98bacf75a359e8d3e3" caseless@~0.12.0: version "0.12.0" @@ -1333,7 +1359,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1375,9 +1401,11 @@ chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" -chrome-trace-event@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz#d395af2d31c87b90a716c831fe326f69768ec084" +chrome-trace-event@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" + dependencies: + tslib "^1.9.0" cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -1501,22 +1529,12 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.3.0: - version "1.9.2" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" - dependencies: - color-name "1.1.1" - -color-convert@^1.9.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" dependencies: color-name "^1.1.1" -color-name@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" - color-name@^1.0.0, color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" @@ -1552,8 +1570,8 @@ colors@1.0.3: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" colors@^1.1.2: - version "1.2.5" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + version "1.3.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e" colors@~1.1.2: version "1.1.2" @@ -1632,8 +1650,8 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: - version "2.5.6" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.6.tgz#0fe6d45bf3cac3ac364a9d72de7576f4eb221b9d" + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1995,8 +2013,8 @@ ejs@^2.5.9: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" electron-to-chromium@^1.2.7: - version "1.3.50" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.50.tgz#7438b76f92b41b919f3fbdd350fbd0757dacddf7" + version "1.3.48" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz#d3b0d8593814044e092ece2108fc3ac9aea4b900" elegant-spinner@^1.0.1: version "1.0.1" @@ -2042,9 +2060,9 @@ entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -envinfo@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-4.4.2.tgz#472c49f3a8b9bca73962641ce7cb692bf623cd1c" +envinfo@^5.7.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df" errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -2251,14 +2269,10 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0: +extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - fancy-log@^1.1.0: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" @@ -2392,8 +2406,8 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" flow-parser@^0.*: - version "0.72.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.72.0.tgz#6c8041e76ac7d0be1a71ce29c00cd1435fb6013c" + version "0.74.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.74.0.tgz#4acc8f55bdce5fa4da43c72c28ef8a9600ace87c" flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: version "1.0.3" @@ -2750,7 +2764,7 @@ got@^7.0.0: url-parse-lax "^1.0.0" url-to-options "^1.0.1" -got@^8.2.0: +got@^8.3.1: version "8.3.1" resolved "https://registry.yarnpkg.com/got/-/got-8.3.1.tgz#093324403d4d955f5a16a7a8d39955d055ae10ed" dependencies: @@ -3111,12 +3125,18 @@ https-proxy-agent@^2.2.0: agent-base "^4.1.0" debug "^3.1.0" -iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.4: +iconv-lite@0.4.23, iconv-lite@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: safer-buffer ">= 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" @@ -3127,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" @@ -3185,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" @@ -3197,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: @@ -3215,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" @@ -3397,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" @@ -3588,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" @@ -3603,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" @@ -3619,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" @@ -3755,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" @@ -3773,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: @@ -3904,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" @@ -3957,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: @@ -3983,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" @@ -4246,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" @@ -4309,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" @@ -4329,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" @@ -4649,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" @@ -5091,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" @@ -5110,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" @@ -5180,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" @@ -5331,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" @@ -5520,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" @@ -5585,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" @@ -5605,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" @@ -5840,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" @@ -5895,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" @@ -5908,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" @@ -6057,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" @@ -6074,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: @@ -6217,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" @@ -6226,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" @@ -6245,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" @@ -6328,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" @@ -6393,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" @@ -6550,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: @@ -6561,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: @@ -6605,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" @@ -6622,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" @@ -6660,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" @@ -6695,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" @@ -6799,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" @@ -6819,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: From 1b51607ca1886c0128c8e5dc1f3204d4ef7e1b81 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 7 Aug 2018 16:00:37 +0200 Subject: [PATCH 13/17] Delay checking credentials and displaying signin Delay the credential check and doing sign in until the first interaction of the user with something that requires API access, so basically it will only kick in when the user expands one of the PR lists. --- src/authentication/githubServer.ts | 2 +- src/github/githubRepository.ts | 44 ++++++++++++++++++--------- src/github/pullRequestManager.ts | 49 ++++++++---------------------- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/authentication/githubServer.ts b/src/authentication/githubServer.ts index c36cbdde3e..54a229393d 100644 --- a/src/authentication/githubServer.ts +++ b/src/authentication/githubServer.ts @@ -86,7 +86,7 @@ export class GitHubManager { const options = GitHubManager.getOptions(host, 'HEAD'); return new Promise((resolve, _) => { const get = https.request(options, res => { - let ret = res.headers['x-github-request-id']; + const ret = res.headers['x-github-request-id']; resolve(ret !== undefined); }); 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/pullRequestManager.ts b/src/github/pullRequestManager.ts index 2e4b5d0e68..c79f080459 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -68,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)); } } @@ -168,9 +165,7 @@ export class PullRequestManager implements IPullRequestManager { } 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, @@ -184,7 +179,7 @@ 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, @@ -200,7 +195,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, @@ -215,9 +210,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, @@ -231,9 +224,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, @@ -246,9 +237,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, @@ -261,9 +250,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, @@ -276,9 +263,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, @@ -292,9 +277,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, @@ -310,9 +293,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, @@ -325,9 +306,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, @@ -351,9 +330,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, From ea59abb73df00cf6feb7948a471f0f6bb63c5bab Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 7 Aug 2018 18:20:14 +0200 Subject: [PATCH 14/17] Improve scope validation by checking supersets as well A token with a "user" scope will work just as well as one with "user:email". --- src/authentication/githubServer.ts | 74 +++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/authentication/githubServer.ts b/src/authentication/githubServer.ts index 54a229393d..c38aca0992 100644 --- a/src/authentication/githubServer.ts +++ b/src/authentication/githubServer.ts @@ -2,11 +2,12 @@ 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 = 'read:user user:email repo write:discussion'; -const HOST = 'github-editor-auth.herokuapp.com'; -const HTTP_PROTOCOL = 'https'; -const WS_PROTOCOL = 'wss'; +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, @@ -74,6 +75,21 @@ class Client { 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); } @@ -118,6 +134,24 @@ export class GitHubManager { 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 { @@ -147,11 +181,7 @@ export class GitHubServer { const options = GitHubManager.getOptions(this.hostUri); return new Promise((resolve, _) => { const get = https.request(options, res => { - if (res.statusCode !== 200) { - resolve(false); - } else { - resolve(true); - } + resolve(res.statusCode === 200); }); get.end(); @@ -173,20 +203,20 @@ export class GitHubServer { return new Promise((resolve, _) => { const get = https.request(options, res => { - if (res.statusCode !== 200) { - resolve(undefined); - } - const scopes = res.headers['x-oauth-scopes'] as string; - if (!scopes) { - resolve(undefined); - } - const expected = SCOPES.split(' '); - const serverScopes = new Set(scopes.split(', ')); - if (expected.every(x => serverScopes.has(x))) { - this.hostConfiguration.username = username; - this.hostConfiguration.token = token; - resolve(this.hostConfiguration); + 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(); From 4e49ad6dd36130f0a9af6ace54292e1d73645d7d Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 9 Aug 2018 16:36:54 +0200 Subject: [PATCH 15/17] Add nice descriptions of the settings --- package.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7add91fba8..2da5b09a59 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,21 @@ "default": [], "description": "Host tokens", "items": { - "type": "object" + "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" + } + } } } } From cb7a3d2165427a291f454361b55c218fc236d381 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 9 Aug 2018 16:37:25 +0200 Subject: [PATCH 16/17] Fix import casing --- src/github/pullRequestManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 3ecde2bec0..a11041440a 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -15,7 +15,7 @@ import { PullRequestGitHelper } from "./pullRequestGitHelper"; import { PullRequestModel } from "./pullRequestModel"; import { parserCommentDiffHunk } from "../common/diffHunk"; import { Configuration } from '../authentication/configuration'; -import { GitHubManager } from '../authentication/githubserver'; +import { GitHubManager } from '../authentication/githubServer'; import { formatError, uniqBy } from '../common/utils'; interface PageInformation { From d62e444dcdb4f56929ec29a7c7f616d20ce258a3 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 9 Aug 2018 16:37:58 +0200 Subject: [PATCH 17/17] Make sure we always look at uris in a case-insensitive way --- src/authentication/githubServer.ts | 1 + src/authentication/vsConfiguration.ts | 4 +++- src/common/protocol.ts | 2 +- src/github/pullRequestManager.ts | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/authentication/githubServer.ts b/src/authentication/githubServer.ts index c38aca0992..f324846531 100644 --- a/src/authentication/githubServer.ts +++ b/src/authentication/githubServer.ts @@ -159,6 +159,7 @@ export class GitHubServer { private hostUri: vscode.Uri; public constructor(host: string) { + host = host.toLocaleLowerCase(); this.hostConfiguration = { host, username: 'oauth', token: undefined }; this.hostUri = vscode.Uri.parse(host); } diff --git a/src/authentication/vsConfiguration.ts b/src/authentication/vsConfiguration.ts index 696a52217c..a964d609db 100644 --- a/src/authentication/vsConfiguration.ts +++ b/src/authentication/vsConfiguration.ts @@ -21,6 +21,7 @@ export class VSCodeConfiguration extends Configuration { } public setHost(host: string): IHostConfiguration { + host = host.toLocaleLowerCase(); if (host && host.substr(host.length - 2, 1) === '/') { host = host.slice(0, -1); } @@ -49,7 +50,7 @@ export class VSCodeConfiguration extends Configuration { } public getHost(host: string): IHostConfiguration { - return this._hosts.get(host); + return this._hosts.get(host.toLocaleLowerCase()); } public update(username: string | undefined, token: string | undefined, raiseEvent: boolean = true): void { @@ -68,6 +69,7 @@ export class VSCodeConfiguration extends Configuration { 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)) { 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/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index a11041440a..e1285b1c75 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -56,7 +56,7 @@ export class PullRequestManager implements IPullRequestManager { 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.host}:${remote.owner}/${remote.repositoryName}`); + gitHubRemotes = uniqBy(gitHubRemotes, remote => remote.gitProtocol.normalizeUri().toString()); if (gitHubRemotes.length) { await vscode.commands.executeCommand('setContext', 'github:hasGitHubRemotes', true);