angular-cn/dev-infra/release/notes/release-notes.ts
Paul Gschwendtner 67f65a9d25 refactor(dev-infra): improve type-safety of git client utility (#42468)
Currently the `GitClient` accepts a generic parameter for determining
whether the `githubToken` should be set or not. This worked fine so far
in terms of distinguishing between an authenticated and
non-authenticated git client instance, but if we intend to conditionally
show methods only for authenticated instances, the generic parameter
is not suitable.

This commit splits up the `GitClient` into two classes. One for
the base logic without any authorization, and a second class that
extends the base logic with authentication logic. i.e. the
`AuthenticatedGitClient`. This allows us to have specific methods only
for the authenticated instance. e.g.

  * `hasOauthScopes` has been moved to only exist for authenticated
    instances.
  * the GraphQL functionality within `gitClient.github` is not
    accessible for non-authenticated instances. GraphQL API requires
    authentication as per Github.

The initial motiviation for this was that we want to throw if
`hasOAuthScopes` is called without the Octokit instance having
a token configured. This should help avoiding issues as within
3b434ed94d
that prevented the caretaker process momentarily.

Additionally, the Git client has moved from `index.ts` to
`git-client.ts` for better discoverability in the codebase.

PR Close #42468
2021-06-03 14:34:33 -07:00

93 lines
3.3 KiB
TypeScript

/**
* @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 {render} from 'ejs';
import * as semver from 'semver';
import {CommitFromGitLog} from '../../commit-message/parse';
import {getCommitsInRange} from '../../commit-message/utils';
import {promptInput} from '../../utils/console';
import {GitClient} from '../../utils/git/git-client';
import {DevInfraReleaseConfig, getReleaseConfig, ReleaseNotesConfig} from '../config/index';
import {RenderContext} from './context';
import changelogTemplate from './templates/changelog';
import githubReleaseTemplate from './templates/github-release';
/** Release note generation. */
export class ReleaseNotes {
static async fromRange(version: semver.SemVer, startingRef: string, endingRef: string) {
return new ReleaseNotes(version, startingRef, endingRef);
}
/** An instance of GitClient. */
private git = GitClient.get();
/** The RenderContext to be used during rendering. */
private renderContext: RenderContext|undefined;
/** The title to use for the release. */
private title: string|false|undefined;
/** A promise resolving to a list of Commits since the latest semver tag on the branch. */
private commits: Promise<CommitFromGitLog[]> =
this.getCommitsInRange(this.startingRef, this.endingRef);
/** The configuration for release notes. */
private config: ReleaseNotesConfig = this.getReleaseConfig().releaseNotes;
protected constructor(
public version: semver.SemVer, private startingRef: string, private endingRef: string) {}
/** Retrieve the release note generated for a Github Release. */
async getGithubReleaseEntry(): Promise<string> {
return render(githubReleaseTemplate, await this.generateRenderContext(), {rmWhitespace: true});
}
/** Retrieve the release note generated for a CHANGELOG entry. */
async getChangelogEntry() {
return render(changelogTemplate, await this.generateRenderContext(), {rmWhitespace: true});
}
/**
* Prompt the user for a title for the release, if the project's configuration is defined to use a
* title.
*/
async promptForReleaseTitle() {
if (this.title === undefined) {
if (this.config.useReleaseTitle) {
this.title = await promptInput('Please provide a title for the release:');
} else {
this.title = false;
}
}
return this.title;
}
/** Build the render context data object for constructing the RenderContext instance. */
private async generateRenderContext(): Promise<RenderContext> {
if (!this.renderContext) {
this.renderContext = new RenderContext({
commits: await this.commits,
github: this.git.remoteConfig,
version: this.version.format(),
groupOrder: this.config.groupOrder,
hiddenScopes: this.config.hiddenScopes,
title: await this.promptForReleaseTitle(),
});
}
return this.renderContext;
}
// These methods are used for access to the utility functions while allowing them to be
// overwritten in subclasses during testing.
protected async getCommitsInRange(from: string, to?: string) {
return getCommitsInRange(from, to);
}
protected getReleaseConfig(config?: Partial<DevInfraReleaseConfig>) {
return getReleaseConfig(config);
}
}