refactor(dev-infra): share more github code between commands (#38656)

Instead of repeating the logic for adding the github token to
a repository git url, we add a shared function for automatically
computing the URls with token.

Additionally, URLs for updating/generating tokens have been moved
to a dedicated file in the `utils` folder. Also while being at it,
the yargs github token helper is also moved into the dedicated
Git/Github related util folder.

PR Close #38656
This commit is contained in:
Paul Gschwendtner
2020-09-01 10:39:35 +02:00
committed by Alex Rickabaugh
parent 4744c229db
commit 758d0e2045
11 changed files with 62 additions and 47 deletions

View File

@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {URL} from 'url';
import {GithubConfig} from '../config';
/** URL to the Github page where personal access tokens can be managed. */
export const GITHUB_TOKEN_SETTINGS_URL = `https://github.com/settings/tokens`;
/** URL to the Github page where personal access tokens can be generated. */
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens/new`;
/** Adds the provided token to the given Github HTTPs remote url. */
export function addTokenToGitHttpsUrl(githubHttpsUrl: string, token: string) {
const url = new URL(githubHttpsUrl);
url.username = token;
return url.toString();
}
/** Gets the repository Git URL for the given github config. */
export function getRepositoryGitUrl(config: GithubConfig, githubToken?: string): string {
if (config.useSsh) {
return `git@github.com:${config.owner}/${config.name}.git`;
}
const baseHttpUrl = `https://github.com/${config.owner}/${config.name}.git`;
if (githubToken !== undefined) {
return addTokenToGitHttpsUrl(baseHttpUrl, githubToken);
}
return baseHttpUrl;
}

View File

@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Argv} from 'yargs';
import {error, red, yellow} from '../console';
import {GITHUB_TOKEN_GENERATE_URL} from './github-urls';
export type ArgvWithGithubToken = Argv<{githubToken: string}>;
/** Sets up the `github-token` command option for the given Yargs instance. */
export function addGithubTokenOption(yargs: Argv): ArgvWithGithubToken {
return yargs
// 'github-token' is casted to 'githubToken' to properly set up typings to reflect the key in
// the Argv object being camelCase rather than kebob case due to the `camel-case-expansion`
// config: https://github.com/yargs/yargs-parser#camel-case-expansion
.option('github-token' as 'githubToken', {
type: 'string',
description: 'Github token. If not set, token is retrieved from the environment variables.',
coerce: (token: string) => {
const githubToken = token || process.env.GITHUB_TOKEN || process.env.TOKEN;
if (!githubToken) {
error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
error(red('Alternatively, pass the `--github-token` command line flag.'));
error(yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
process.exit(1);
}
return githubToken;
},
})
.default('github-token' as 'githubToken', '', '<LOCAL TOKEN>');
}

View File

@ -84,7 +84,6 @@ class GithubGraphqlClient {
}
}
/** Perform a query using Github's GraphQL API. */
async query<T extends GraphQLQueryObject>(queryObject: T, params: RequestParameters = {}) {
const queryString = query(queryObject);

View File

@ -12,6 +12,7 @@ import {spawnSync, SpawnSyncOptions, SpawnSyncReturns} from 'child_process';
import {getConfig, getRepoBaseDir, NgDevConfig} from '../config';
import {info, yellow} from '../console';
import {GithubClient} from './github';
import {getRepositoryGitUrl, GITHUB_TOKEN_GENERATE_URL, GITHUB_TOKEN_SETTINGS_URL} from './github-urls';
/** Github response type extended to include the `x-oauth-scopes` headers presence. */
type RateLimitResponseWithOAuthScopeHeader = Octokit.Response<Octokit.RateLimitGetResponse>&{
@ -45,11 +46,8 @@ export class GitClient {
remoteConfig = this._config.github;
/** Octokit request parameters object for targeting the configured remote. */
remoteParams = {owner: this.remoteConfig.owner, repo: this.remoteConfig.name};
/** URL that resolves to the configured repository. */
repoGitUrl = this.remoteConfig.useSsh ?
`git@github.com:${this.remoteConfig.owner}/${this.remoteConfig.name}.git` :
`https://${this._githubToken}@github.com/${this.remoteConfig.owner}/${
this.remoteConfig.name}.git`;
/** Git URL that resolves to the configured repository. */
repoGitUrl = getRepositoryGitUrl(this.remoteConfig, this._githubToken);
/** Instance of the authenticated Github octokit API. */
github = new GithubClient(this._githubToken);
@ -191,8 +189,8 @@ export class GitClient {
`The provided <TOKEN> does not have required permissions due to missing scope(s): ` +
`${yellow(missingScopes.join(', '))}\n\n` +
`Update the token in use at:\n` +
` https://github.com/settings/tokens\n\n` +
`Alternatively, a new token can be created at: https://github.com/settings/tokens/new\n`;
` ${GITHUB_TOKEN_SETTINGS_URL}\n\n` +
`Alternatively, a new token can be created at: ${GITHUB_TOKEN_GENERATE_URL}\n`;
return {error};
}
@ -202,12 +200,12 @@ export class GitClient {
**/
private async getAuthScopesForToken() {
// If the OAuth scopes have already been loaded, return the Promise containing them.
if (this._oauthScopes !== null) {
return this._oauthScopes;
if (this._cachedOauthScopes !== null) {
return this._cachedOauthScopes;
}
// OAuth scopes are loaded via the /rate_limit endpoint to prevent
// usage of a request against that rate_limit for this lookup.
return this._oauthScopes = this.github.rateLimit.get().then(_response => {
return this._cachedOauthScopes = this.github.rateLimit.get().then(_response => {
const response = _response as RateLimitResponseWithOAuthScopeHeader;
const scopes: string = response.headers['x-oauth-scopes'] || '';
return scopes.split(',').map(scope => scope.trim());