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:
parent
4744c229db
commit
758d0e2045
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
import {addGithubTokenOption} from '../../utils/git/github-yargs';
|
||||
|
||||
import {checkServiceStatuses} from './check';
|
||||
|
||||
|
@ -17,12 +17,9 @@ export interface CaretakerCheckOptions {
|
|||
githubToken: string;
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens`;
|
||||
|
||||
/** Builds the command. */
|
||||
function builder(yargs: Argv) {
|
||||
return addGithubTokenFlag(yargs);
|
||||
return addGithubTokenOption(yargs);
|
||||
}
|
||||
|
||||
/** Handles the command. */
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
import {addGithubTokenOption} from '../../utils/git/github-yargs';
|
||||
import {checkOutPullRequestLocally} from '../common/checkout-pr';
|
||||
|
||||
export interface CheckoutOptions {
|
||||
|
@ -18,7 +18,7 @@ export interface CheckoutOptions {
|
|||
|
||||
/** Builds the checkout pull request command. */
|
||||
function builder(yargs: Argv) {
|
||||
return addGithubTokenFlag(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
return addGithubTokenOption(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
/** Handles the checkout pull request command. */
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
*/
|
||||
|
||||
import {types as graphQLTypes} from 'typed-graphqlify';
|
||||
import {URL} from 'url';
|
||||
|
||||
import {info} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {addTokenToGitHttpsUrl} from '../../utils/git/github-urls';
|
||||
import {getPr} from '../../utils/github';
|
||||
|
||||
/* GraphQL schema for the response body for a pending PR. */
|
||||
|
@ -83,7 +83,7 @@ export async function checkOutPullRequestLocally(
|
|||
/** The full ref for the repository and branch the PR came from. */
|
||||
const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
|
||||
/** The full URL path of the repository the PR came from with github token as authentication. */
|
||||
const headRefUrl = addAuthenticationToUrl(pr.headRef.repository.url, githubToken);
|
||||
const headRefUrl = addTokenToGitHttpsUrl(pr.headRef.repository.url, githubToken);
|
||||
// Note: Since we use a detached head for rebasing the PR and therefore do not have
|
||||
// remote-tracking branches configured, we need to set our expected ref and SHA. This
|
||||
// allows us to use `--force-with-lease` for the detached head while ensuring that we
|
||||
|
@ -126,10 +126,3 @@ export async function checkOutPullRequestLocally(
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Adds the provided token as username to the provided url. */
|
||||
function addAuthenticationToUrl(urlString: string, token: string) {
|
||||
const url = new URL(urlString);
|
||||
url.username = token;
|
||||
return url.toString();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Arguments, Argv} from 'yargs';
|
||||
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
import {addGithubTokenOption} from '../../utils/git/github-yargs';
|
||||
|
||||
import {mergePullRequest} from './index';
|
||||
|
||||
|
@ -20,7 +20,7 @@ export interface MergeCommandOptions {
|
|||
|
||||
/** Builds the options for the merge command. */
|
||||
export function buildMergeCommand(yargs: Argv): Argv<MergeCommandOptions> {
|
||||
return addGithubTokenFlag(yargs).help().strict().positional(
|
||||
return addGithubTokenOption(yargs).help().strict().positional(
|
||||
'pr-number', {demandOption: true, type: 'number'});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,11 @@ import {getConfig, getRepoBaseDir} from '../../utils/config';
|
|||
import {error, green, info, promptConfirm, red, yellow} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {GithubApiRequestError} from '../../utils/git/github';
|
||||
import {GITHUB_TOKEN_GENERATE_URL} from '../../utils/yargs';
|
||||
import {GITHUB_TOKEN_GENERATE_URL} from '../../utils/git/github-urls';
|
||||
|
||||
import {loadAndValidateConfig, MergeConfigWithRemote} from './config';
|
||||
import {MergeResult, MergeStatus, PullRequestMergeTask} from './task';
|
||||
|
||||
|
||||
/**
|
||||
* Merges a given pull request based on labels configured in the given merge configuration.
|
||||
* Pull requests can be merged with different strategies such as the Github API merge
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Arguments, Argv} from 'yargs';
|
||||
|
||||
import {addGithubTokenFlag} from '../../utils/yargs';
|
||||
import {addGithubTokenOption} from '../../utils/git/github-yargs';
|
||||
|
||||
import {rebasePr} from './index';
|
||||
|
||||
|
@ -20,7 +20,7 @@ export interface RebaseCommandOptions {
|
|||
|
||||
/** Builds the rebase pull request command. */
|
||||
export function buildRebaseCommand(yargs: Argv): Argv<RebaseCommandOptions> {
|
||||
return addGithubTokenFlag(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
return addGithubTokenOption(yargs).positional('prNumber', {type: 'number', demandOption: true});
|
||||
}
|
||||
|
||||
/** Handles the rebase pull request command. */
|
||||
|
|
|
@ -12,6 +12,7 @@ import {URL} from 'url';
|
|||
import {getConfig, NgDevConfig} from '../../utils/config';
|
||||
import {error, info, promptConfirm} from '../../utils/console';
|
||||
import {GitClient} from '../../utils/git';
|
||||
import {addTokenToGitHttpsUrl} from '../../utils/git/github-urls';
|
||||
import {getPr} from '../../utils/github';
|
||||
|
||||
/* GraphQL schema for the response body for each pending PR. */
|
||||
|
@ -61,8 +62,8 @@ export async function rebasePr(
|
|||
const baseRefName = pr.baseRef.name;
|
||||
const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
|
||||
const fullBaseRef = `${pr.baseRef.repository.nameWithOwner}:${baseRefName}`;
|
||||
const headRefUrl = addAuthenticationToUrl(pr.headRef.repository.url, githubToken);
|
||||
const baseRefUrl = addAuthenticationToUrl(pr.baseRef.repository.url, githubToken);
|
||||
const headRefUrl = addTokenToGitHttpsUrl(pr.headRef.repository.url, githubToken);
|
||||
const baseRefUrl = addTokenToGitHttpsUrl(pr.baseRef.repository.url, githubToken);
|
||||
|
||||
// Note: Since we use a detached head for rebasing the PR and therefore do not have
|
||||
// remote-tracking branches configured, we need to set our expected ref and SHA. This
|
||||
|
@ -140,10 +141,3 @@ export async function rebasePr(
|
|||
git.runGraceful(['checkout', previousBranchOrRevision], {stdio: 'ignore'});
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the provided token as username to the provided url. */
|
||||
function addAuthenticationToUrl(urlString: string, token: string) {
|
||||
const url = new URL(urlString);
|
||||
url.username = token;
|
||||
return url.toString();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -7,11 +7,13 @@
|
|||
*/
|
||||
|
||||
import {Argv} from 'yargs';
|
||||
import {error, red, yellow} from './console';
|
||||
import {error, red, yellow} from '../console';
|
||||
import {GITHUB_TOKEN_GENERATE_URL} from './github-urls';
|
||||
|
||||
export type ArgvWithGithubToken = Argv<{githubToken: string}>;
|
||||
|
||||
export function addGithubTokenFlag(yargs: Argv): ArgvWithGithubToken {
|
||||
/** 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`
|
||||
|
@ -32,6 +34,3 @@ export function addGithubTokenFlag(yargs: Argv): ArgvWithGithubToken {
|
|||
})
|
||||
.default('github-token' as 'githubToken', '', '<LOCAL TOKEN>');
|
||||
}
|
||||
|
||||
/** URL to the Github page where personal access tokens can be generated. */
|
||||
export const GITHUB_TOKEN_GENERATE_URL = 'https://github.com/settings/tokens/new';
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue