feat(dev-infra): create ReleaseNotes class for generating release notes during publishing (#41476)

Generate release notes to be used for entries in both CHANGELOG.md files as well as
Github releases.

PR Close #41476
This commit is contained in:
Joey Perrott 2021-04-01 16:28:17 -07:00 committed by Zach Arend
parent 7e6989ee4b
commit 5e0d5a9ec2
17 changed files with 648 additions and 30 deletions

View File

@ -44,6 +44,7 @@ pkg_npm(
"index.bzl", "index.bzl",
"//dev-infra/bazel:files", "//dev-infra/bazel:files",
"//dev-infra/benchmark:files", "//dev-infra/benchmark:files",
"//dev-infra/release/publish/release-notes/templates",
], ],
substitutions = { substitutions = {
# angular/angular should not consume it's own packages, so we use # angular/angular should not consume it's own packages, so we use

View File

@ -25,6 +25,7 @@ var cliProgress = require('cli-progress');
var os = require('os'); var os = require('os');
var minimatch = require('minimatch'); var minimatch = require('minimatch');
var ora = require('ora'); var ora = require('ora');
require('ejs');
var glob = require('glob'); var glob = require('glob');
var ts = require('typescript'); var ts = require('typescript');
@ -5580,6 +5581,11 @@ function isCommitClosingPullRequest(api, sha, id) {
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
/** List of types to be included in the release notes. */
const typesToIncludeInReleaseNotes = Object.values(COMMIT_TYPES)
.filter(type => type.releaseNotesLevel === ReleaseNotesLevel.Visible)
.map(type => type.name);
/** /**
* Gets the default pattern for extracting release notes for the given version. * Gets the default pattern for extracting release notes for the given version.
* This pattern matches for the conventional-changelog Angular preset. * This pattern matches for the conventional-changelog Angular preset.

View File

@ -36,6 +36,25 @@ export interface ReleaseConfig {
extractReleaseNotesPattern?: (version: semver.SemVer) => RegExp; extractReleaseNotesPattern?: (version: semver.SemVer) => RegExp;
/** The list of github labels to add to the release PRs. */ /** The list of github labels to add to the release PRs. */
releasePrLabels?: string[]; releasePrLabels?: string[];
/** Configuration for creating release notes during publishing. */
// TODO(josephperrott): Make releaseNotes a required attribute on the interface when tooling is
// integrated.
releaseNotes?: ReleaseNotesConfig;
}
/** Configuration for creating release notes during publishing. */
export interface ReleaseNotesConfig {
/** Whether to prompt for and include a release title in the generated release notes. */
useReleaseTitle?: boolean;
/** List of commit scopes to disclude from generated release notes. */
hiddenScopes?: string[];
/**
* List of commit groups, either {npmScope}/{scope} or {scope}, to use for ordering.
*
* Each group for the release notes, will appear in the order provided in groupOrder and any other
* groups will appear after these groups, sorted by `Array.sort`'s default sorting order.
*/
groupOrder?: string[];
} }
/** Configuration for releases in the dev-infra configuration. */ /** Configuration for releases in the dev-infra configuration. */

View File

@ -8,15 +8,18 @@ ts_library(
module_name = "@angular/dev-infra-private/release/publish", module_name = "@angular/dev-infra-private/release/publish",
visibility = ["//dev-infra:__subpackages__"], visibility = ["//dev-infra:__subpackages__"],
deps = [ deps = [
"//dev-infra/commit-message",
"//dev-infra/pr/merge", "//dev-infra/pr/merge",
"//dev-infra/release/config", "//dev-infra/release/config",
"//dev-infra/release/versioning", "//dev-infra/release/versioning",
"//dev-infra/utils", "//dev-infra/utils",
"@npm//@octokit/rest", "@npm//@octokit/rest",
"@npm//@types/ejs",
"@npm//@types/inquirer", "@npm//@types/inquirer",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/semver", "@npm//@types/semver",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//ejs",
"@npm//inquirer", "@npm//inquirer",
"@npm//ora", "@npm//ora",
"@npm//semver", "@npm//semver",

View File

@ -14,7 +14,7 @@ import * as semver from 'semver';
import {debug, error, green, info, promptConfirm, red, warn, yellow} from '../../utils/console'; import {debug, error, green, info, promptConfirm, red, warn, yellow} from '../../utils/console';
import {getListCommitsInBranchUrl, getRepositoryGitUrl} from '../../utils/git/github-urls'; import {getListCommitsInBranchUrl, getRepositoryGitUrl} from '../../utils/git/github-urls';
import {GitClient} from '../../utils/git/index'; import {GitClient} from '../../utils/git/index';
import {BuiltPackage, ReleaseConfig} from '../config'; import {BuiltPackage, ReleaseConfig} from '../config/index';
import {ActiveReleaseTrains} from '../versioning/active-release-trains'; import {ActiveReleaseTrains} from '../versioning/active-release-trains';
import {runNpmPublish} from '../versioning/npm-publish'; import {runNpmPublish} from '../versioning/npm-publish';
@ -24,7 +24,7 @@ import {changelogPath, packageJsonPath, waitForPullRequestInterval} from './cons
import {invokeBazelCleanCommand, invokeReleaseBuildCommand, invokeYarnInstallCommand} from './external-commands'; import {invokeBazelCleanCommand, invokeReleaseBuildCommand, invokeYarnInstallCommand} from './external-commands';
import {findOwnedForksOfRepoQuery} from './graphql-queries'; import {findOwnedForksOfRepoQuery} from './graphql-queries';
import {getPullRequestState} from './pull-request-state'; import {getPullRequestState} from './pull-request-state';
import {getDefaultExtractReleaseNotesPattern, getLocalChangelogFilePath} from './release-notes'; import {getDefaultExtractReleaseNotesPattern, getLocalChangelogFilePath} from './release-notes/release-notes';
/** Interface describing a Github repository. */ /** Interface describing a Github repository. */
export interface GithubRepo { export interface GithubRepo {

View File

@ -11,7 +11,7 @@ import {ListChoiceOptions, prompt} from 'inquirer';
import {GithubConfig} from '../../utils/config'; import {GithubConfig} from '../../utils/config';
import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console'; import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console';
import {GitClient} from '../../utils/git/index'; import {GitClient} from '../../utils/git/index';
import {ReleaseConfig} from '../config'; import {ReleaseConfig} from '../config/index';
import {ActiveReleaseTrains, fetchActiveReleaseTrains, nextBranchName} from '../versioning/active-release-trains'; import {ActiveReleaseTrains, fetchActiveReleaseTrains, nextBranchName} from '../versioning/active-release-trains';
import {npmIsLoggedIn, npmLogin, npmLogout} from '../versioning/npm-publish'; import {npmIsLoggedIn, npmLogin, npmLogout} from '../versioning/npm-publish';
import {printActiveReleaseTrains} from '../versioning/print-active-trains'; import {printActiveReleaseTrains} from '../versioning/print-active-trains';

View File

@ -1,27 +0,0 @@
/**
* @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 {join} from 'path';
import * as semver from 'semver';
import {changelogPath} from './constants';
/**
* Gets the default pattern for extracting release notes for the given version.
* This pattern matches for the conventional-changelog Angular preset.
*/
export function getDefaultExtractReleaseNotesPattern(version: semver.SemVer): RegExp {
const escapedVersion = version.format().replace('.', '\\.');
// TODO: Change this once we have a canonical changelog generation tool. Also update this
// based on the conventional-changelog version. They removed anchors in more recent versions.
return new RegExp(`(<a name="${escapedVersion}"></a>.*?)(?:<a name="|$)`, 's');
}
/** Gets the path for the changelog file in a given project. */
export function getLocalChangelogFilePath(projectDir: string): string {
return join(projectDir, changelogPath);
}

View File

@ -0,0 +1,152 @@
/**
* @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 {COMMIT_TYPES, ReleaseNotesLevel} from '../../../commit-message/config';
import {CommitFromGitLog} from '../../../commit-message/parse';
import {GithubConfig} from '../../../utils/config';
import {ReleaseNotesConfig} from '../../config/index';
/** List of types to be included in the release notes. */
const typesToIncludeInReleaseNotes =
Object.values(COMMIT_TYPES)
.filter(type => type.releaseNotesLevel === ReleaseNotesLevel.Visible)
.map(type => type.name);
/** Data used for context during rendering. */
export interface RenderContextData {
title: string|false;
groupOrder?: ReleaseNotesConfig['groupOrder'];
hiddenScopes?: ReleaseNotesConfig['hiddenScopes'];
date?: Date;
commits: CommitFromGitLog[];
version: string;
github: GithubConfig;
}
/** Context class used for rendering release notes. */
export class RenderContext {
/** An array of group names in sort order if defined. */
private readonly groupOrder = this.data.groupOrder || [];
/** An array of scopes to hide from the release entry output. */
private readonly hiddenScopes = this.data.hiddenScopes || [];
/** The title of the release, or `false` if no title should be used. */
readonly title = this.data.title;
/** An array of commits in the release period. */
readonly commits = this.data.commits;
/** The version of the release. */
readonly version = this.data.version;
/** The date stamp string for use in the release notes entry. */
readonly dateStamp = buildDateStamp(this.data.date);
constructor(private readonly data: RenderContextData) {}
/**
* Organizes and sorts the commits into groups of commits.
*
* Groups are sorted either by default `Array.sort` order, or using the provided group order from
* the configuration. Commits are order in the same order within each groups commit list as they
* appear in the provided list of commits.
* */
asCommitGroups(commits: CommitFromGitLog[]) {
/** The discovered groups to organize into. */
const groups = new Map<string, CommitFromGitLog[]>();
// Place each commit in the list into its group.
commits.forEach(commit => {
const key = commit.npmScope ? `${commit.npmScope}/${commit.scope}` : commit.scope;
const groupCommits = groups.get(key) || [];
groups.set(key, groupCommits);
groupCommits.push(commit);
});
/**
* Array of CommitGroups containing the discovered commit groups. Sorted in alphanumeric order
* of the group title.
*/
const commitGroups = Array.from(groups.entries())
.map(([title, commits]) => ({title, commits}))
.sort((a, b) => a.title > b.title ? 1 : a.title < b.title ? -1 : 0);
// If the configuration provides a sorting order, updated the sorted list of group keys to
// satisfy the order of the groups provided in the list with any groups not found in the list at
// the end of the sorted list.
if (this.groupOrder.length) {
for (const groupTitle of this.groupOrder.reverse()) {
const currentIdx = commitGroups.findIndex(k => k.title === groupTitle);
if (currentIdx !== -1) {
const removedGroups = commitGroups.splice(currentIdx, 1);
commitGroups.splice(0, 0, ...removedGroups);
}
}
}
return commitGroups;
}
/**
* A filter function for filtering a list of commits to only include commits which should appear
* in release notes.
*/
includeInReleaseNotes() {
return (commit: CommitFromGitLog) => {
if (!typesToIncludeInReleaseNotes.includes(commit.type)) {
return false;
}
if (this.hiddenScopes.includes(commit.scope)) {
return false;
}
return true;
};
}
/**
* A filter function for filtering a list of commits to only include commits which contain a
* truthy value, or for arrays an array with 1 or more elements, for the provided field.
*/
contains(field: keyof CommitFromGitLog) {
return (commit: CommitFromGitLog) => {
const fieldValue = commit[field];
if (!fieldValue) {
return false;
}
if (Array.isArray(fieldValue) && fieldValue.length === 0) {
return false;
}
return true;
};
}
/**
* A filter function for filtering a list of commits to only include commits which contain a
* unique value for the provided field across all commits in the list.
*/
unique(field: keyof CommitFromGitLog) {
const set = new Set<CommitFromGitLog[typeof field]>();
return (commit: CommitFromGitLog) => {
const include = !set.has(commit[field]);
set.add(commit[field]);
return include;
};
}
}
/**
* Builds a date stamp for stamping in release notes.
*
* Uses the current date, or a provided date in the format of YYYY-MM-DD, i.e. 1970-11-05.
*/
export function buildDateStamp(date = new Date()) {
const year = `${date.getFullYear()}`;
const month = `${(date.getMonth() + 1)}`.padStart(2, '0');
const day = `${date.getDate()}`.padStart(2, '0');
return [year, month, day].join('-');
}

View File

@ -0,0 +1,98 @@
/**
* @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 {renderFile} from 'ejs';
import {join} from 'path';
import * as semver from 'semver';
import {getCommitsInRange} from '../../../commit-message/utils';
import {getConfig} from '../../../utils/config';
import {promptInput} from '../../../utils/console';
import {GitClient} from '../../../utils/git/index';
import {getReleaseConfig} from '../../config/index';
import {changelogPath} from '../constants';
import {RenderContext} from './context';
/**
* Gets the default pattern for extracting release notes for the given version.
* This pattern matches for the conventional-changelog Angular preset.
*/
export function getDefaultExtractReleaseNotesPattern(version: semver.SemVer): RegExp {
const escapedVersion = version.format().replace('.', '\\.');
// TODO: Change this once we have a canonical changelog generation tool. Also update this
// based on the conventional-changelog version. They removed anchors in more recent versions.
return new RegExp(`(<a name="${escapedVersion}"></a>.*?)(?:<a name="|$)`, 's');
}
/** Gets the path for the changelog file in a given project. */
export function getLocalChangelogFilePath(projectDir: string): string {
return join(projectDir, changelogPath);
}
/** Release note generation. */
export class ReleaseNotes {
/** An instance of GitClient. */
private git = new GitClient();
/** The github configuration. */
private readonly github = getConfig().github;
/** The configuration for the release notes generation. */
// TODO(josephperrott): Remove non-null assertion after usage of ReleaseNotes is integrated into
// release publish tooling.
private readonly config = getReleaseConfig().releaseNotes! || {};
/** A promise resolving to a list of Commits since the latest semver tag on the branch. */
private commits = getCommitsInRange(this.git.getLatestSemverTag().format(), 'HEAD');
/** The RenderContext to be used during rendering. */
private renderContext: RenderContext|undefined;
/** The title to use for the release. */
private title: string|false|undefined;
constructor(private version: semver.SemVer) {}
/** Retrieve the release note generated for a Github Release. */
async getGithubReleaseEntry(): Promise<string> {
return await renderFile(
join(__dirname, 'templates/github-release.ejs'), await this.generateRenderContext());
}
/** Retrieve the release note generated for a CHANGELOG entry. */
async getChangelogEntry() {
return await renderFile(
join(__dirname, 'templates/changelog.ejs'), await this.generateRenderContext());
}
/**
* 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: getConfig().github,
version: this.version.format(),
groupOrder: this.config.groupOrder,
hiddenScopes: this.config.hiddenScopes,
title: await this.promptForReleaseTitle(),
});
}
return this.renderContext;
}
}

View File

@ -0,0 +1,6 @@
package(default_visibility = ["//dev-infra:__subpackages__"])
filegroup(
name = "templates",
srcs = glob(["*.ejs"]),
)

View File

@ -0,0 +1,77 @@
<a name="<%- version %>"></a>
# <%- version %><% if (title) { %> "<%- title %>"<% } %> (<%- dateStamp %>)
<%_
const commitsInChangelog = commits.filter(includeInReleaseNotes());
for (const group of asCommitGroups(commitsInChangelog)) {
_%>
### <%- group.title %>
| Commit | Description |
| -- | -- |
<%_
for (const commit of group.commits) {
_%>
| <%- commit.shortHash %> | <%- commit.header %> |
<%_
}
}
_%>
<%_
const breakingChanges = commits.filter(contains('breakingChanges'));
if (breakingChanges.length) {
_%>
## Breaking Changes
<%_
for (const group of asCommitGroups(breakingChanges)) {
_%>
### <%- group.title %>
<%_
for (const commit of group.commits) {
_%>
<%- commit.breakingChanges[0].text %>
<%_
}
}
}
_%>
<%_
const deprecations = commits.filter(contains('deprecations'));
if (deprecations.length) {
_%>
## Deprecations
<%_
for (const group of asCommitGroups(deprecations)) {
_%>
### <%- group.title %>
<%_
for (const commit of group.commits) {
_%>
<%- commit.deprecations[0].text %>
<%_
}
}
}
_%>
<%_
const authors = commits.filter(unique('author')).map(c => c.author).sort();
if (authors.length === 1) {
_%>
## Special Thanks:
<%- authors[0]%>
<%_
}
if (authors.length > 1) {
_%>
## Special Thanks:
<%- authors.slice(0, -1).join(', ') %> and <%- authors.slice(-1)[0] %>
<%_
}
_%>

View File

@ -0,0 +1,77 @@
<a name="<%- version %>"></a>
# <%- version %><% if (title) { %> "<%- title %>"<% } %> (<%- dateStamp %>)
<%_
const commitsInChangelog = commits.filter(includeInReleaseNotes());
for (const group of asCommitGroups(commitsInChangelog)) {
_%>
### <%- group.title %>
| Commit | Description |
| -- | -- |
<%_
for (const commit of group.commits) {
_%>
| <%- commit.shortHash %> | <%- commit.header %> |
<%_
}
}
_%>
<%_
const breakingChanges = commits.filter(contains('breakingChanges'));
if (breakingChanges.length) {
_%>
## Breaking Changes
<%_
for (const group of asCommitGroups(breakingChanges)) {
_%>
### <%- group.title %>
<%_
for (const commit of group.commits) {
_%>
<%- commit.breakingChanges[0].text %>
<%_
}
}
}
_%>
<%_
const deprecations = commits.filter(contains('deprecations'));
if (deprecations.length) {
_%>
## Deprecations
<%_
for (const group of asCommitGroups(deprecations)) {
_%>
### <%- group.title %>
<%_
for (const commit of group.commits) {
_%>
<%- commit.deprecations[0].text %>
<%_
}
}
}
_%>
<%_
const authors = commits.filter(unique('author')).map(c => c.author).sort();
if (authors.length === 1) {
_%>
## Special Thanks:
<%- authors[0]%>
<%_
}
if (authors.length > 1) {
_%>
## Special Thanks:
<%- authors.slice(0, -1).join(', ') %> and <%- authors.slice(-1)[0] %>
<%_
}
_%>

View File

@ -8,6 +8,7 @@ ts_library(
]), ]),
module_name = "@angular/dev-infra-private/release/test", module_name = "@angular/dev-infra-private/release/test",
deps = [ deps = [
"//dev-infra/commit-message",
"//dev-infra/release/config", "//dev-infra/release/config",
"//dev-infra/release/publish", "//dev-infra/release/publish",
"//dev-infra/release/versioning", "//dev-infra/release/versioning",

View File

@ -0,0 +1,168 @@
/**
* @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 {CommitFromGitLog, parseCommitFromGitLog} from '../../../../commit-message/parse';
import {commitMessageBuilder} from '../../../../commit-message/test-util';
import {RenderContext, RenderContextData,} from '../../release-notes/context';
const defaultContextData: RenderContextData = {
commits: [],
github: {
name: 'repoName',
owner: 'repoOwner',
},
title: false,
version: '1.2.3',
};
describe('RenderContext', () => {
beforeAll(() => {
jasmine.clock().install();
});
it('contains a date stamp using the current date by default', async () => {
jasmine.clock().mockDate(new Date(1996, 11, 11));
const renderContext = new RenderContext(defaultContextData);
expect(renderContext.dateStamp).toBe('1996-12-11');
});
it('contains a date stamp using a provided date', async () => {
const data = {...defaultContextData, date: new Date(2000, 0, 20)};
const renderContext = new RenderContext(data);
expect(renderContext.dateStamp).toBe('2000-01-20');
});
it('filters to include only commits which have specified field', () => {
const renderContext = new RenderContext(defaultContextData);
const matchingCommits = commitsFromList(2, 15);
expect(commits.filter(renderContext.contains('breakingChanges'))).toEqual(matchingCommits);
});
it('filters to include only the first commit discovered with a unique value for a specified field',
() => {
const renderContext = new RenderContext(defaultContextData);
const matchingCommits = commitsFromList(0, 1, 2, 3, 4, 7, 12);
expect(commits.filter(renderContext.unique('type'))).toEqual(matchingCommits);
});
describe('filters to include commits which are to be included in the release notes', () => {
it('including all scopes by default', () => {
const renderContext = new RenderContext(defaultContextData);
const matchingCommits = commitsFromList(0, 2, 5, 6, 8, 10, 11, 12, 15, 16);
expect(commits.filter(renderContext.includeInReleaseNotes())).toEqual(matchingCommits);
});
it('excluding hidden scopes defined in the config', () => {
const renderContext = new RenderContext({...defaultContextData, hiddenScopes: ['core']});
const matchingCommits = commitsFromList(0, 2, 6, 8, 10, 11, 15, 16);
expect(commits.filter(renderContext.includeInReleaseNotes())).toEqual(matchingCommits);
});
});
describe('organized lists of commits into groups', () => {
let devInfraCommits: CommitFromGitLog[];
let coreCommits: CommitFromGitLog[];
let compilerCommits: CommitFromGitLog[];
let unorganizedCommits: CommitFromGitLog[];
function assertOrganizedGroupsMatch(
generatedGroups: {title: string, commits: CommitFromGitLog[]}[],
providedGroups: {title: string, commits: CommitFromGitLog[]}[]) {
expect(generatedGroups.length).toBe(providedGroups.length);
generatedGroups.forEach(({title, commits}, idx) => {
expect(title).toBe(providedGroups[idx].title);
expect(commits).toEqual(jasmine.arrayWithExactContents(providedGroups[idx].commits));
});
}
beforeEach(() => {
devInfraCommits = commits.filter(c => c.scope === 'dev-infra');
coreCommits = commits.filter(c => c.scope === 'core');
compilerCommits = commits.filter(c => c.scope === 'compiler');
unorganizedCommits =
[...devInfraCommits, ...coreCommits, ...compilerCommits].sort(() => Math.random() - 0.5);
});
it('with default sorting', () => {
const renderContext = new RenderContext(defaultContextData);
const organizedCommits = renderContext.asCommitGroups(unorganizedCommits);
assertOrganizedGroupsMatch(organizedCommits, [
{title: 'compiler', commits: compilerCommits},
{title: 'core', commits: coreCommits},
{title: 'dev-infra', commits: devInfraCommits},
]);
});
it('sorted by the provided order in the config', () => {
const renderContext =
new RenderContext({...defaultContextData, groupOrder: ['core', 'dev-infra']});
const organizedCommits = renderContext.asCommitGroups(unorganizedCommits);
assertOrganizedGroupsMatch(organizedCommits, [
{title: 'core', commits: coreCommits},
{title: 'dev-infra', commits: devInfraCommits},
{title: 'compiler', commits: compilerCommits},
]);
});
});
afterAll(() => {
jasmine.clock().uninstall();
});
});
const buildCommitMessage = commitMessageBuilder({
prefix: '',
type: '',
npmScope: '',
scope: '',
summary: 'This is a short summary of the change',
body: 'This is a longer description of the change',
footer: '',
});
function buildCommit(type: string, scope: string, withBreakingChange = false) {
const footer = withBreakingChange ? 'BREAKING CHANGE: something is broken now' : '';
const parts = {type, scope, footer};
return parseCommitFromGitLog(Buffer.from(buildCommitMessage(parts)));
}
function commitsFromList(...indexes: number[]) {
const output: CommitFromGitLog[] = [];
for (const i of indexes) {
output.push(commits[i]);
}
return output;
}
const commits: CommitFromGitLog[] = [
buildCommit('fix', 'platform-browser'),
buildCommit('test', 'dev-infra'),
buildCommit('feat', 'dev-infra', true),
buildCommit('build', 'docs-infra'),
buildCommit('docs', 'router'),
buildCommit('feat', 'core'),
buildCommit('feat', 'common'),
buildCommit('refactor', 'compiler'),
buildCommit('fix', 'docs-infra'),
buildCommit('test', 'core'),
buildCommit('feat', 'compiler-cli'),
buildCommit('fix', 'dev-infra'),
buildCommit('perf', 'core'),
buildCommit('docs', 'forms'),
buildCommit('refactor', 'dev-infra'),
buildCommit('feat', 'docs-infra', true),
buildCommit('fix', 'compiler'),
];

View File

@ -17,6 +17,7 @@
"chalk": "<from-root>", "chalk": "<from-root>",
"cli-progress": "<from-root>", "cli-progress": "<from-root>",
"conventional-commits-parser": "<from-root>", "conventional-commits-parser": "<from-root>",
"ejs": "<from-root>",
"git-raw-commits": "<from-root>", "git-raw-commits": "<from-root>",
"glob": "<from-root>", "glob": "<from-root>",
"inquirer": "<from-root>", "inquirer": "<from-root>",

View File

@ -175,6 +175,7 @@
"@octokit/graphql": "^4.3.1", "@octokit/graphql": "^4.3.1",
"@types/cli-progress": "^3.4.2", "@types/cli-progress": "^3.4.2",
"@types/conventional-commits-parser": "^3.0.1", "@types/conventional-commits-parser": "^3.0.1",
"@types/ejs": "^3.0.6",
"@types/git-raw-commits": "^2.0.0", "@types/git-raw-commits": "^2.0.0",
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
"@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/lockfile": "^1.1.0",
@ -187,6 +188,7 @@
"cli-progress": "^3.7.0", "cli-progress": "^3.7.0",
"conventional-changelog": "^2.0.3", "conventional-changelog": "^2.0.3",
"conventional-commits-parser": "^3.2.1", "conventional-commits-parser": "^3.2.1",
"ejs": "^3.1.6",
"entities": "1.1.1", "entities": "1.1.1",
"firebase-tools": "^7.11.0", "firebase-tools": "^7.11.0",
"firefox-profile": "1.0.3", "firefox-profile": "1.0.3",

View File

@ -2586,6 +2586,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/ejs@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.0.6.tgz#aca442289df623bfa8e47c23961f0357847b83fe"
integrity sha512-fj1hi+ZSW0xPLrJJD+YNwIh9GZbyaIepG26E/gXvp8nCa2pYokxUYO1sK9qjGxp2g8ryZYuon7wmjpwE2cyASQ==
"@types/estree@0.0.39": "@types/estree@0.0.39":
version "0.0.39" version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@ -3642,6 +3647,11 @@ async-settle@^1.0.0:
dependencies: dependencies:
async-done "^1.2.2" async-done "^1.2.2"
async@0.9.x:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
async@1.2.x: async@1.2.x:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0"
@ -6455,6 +6465,13 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
ejs@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
dependencies:
jake "^10.6.1"
electron-to-chromium@^1.3.390: electron-to-chromium@^1.3.390:
version "1.3.394" version "1.3.394"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.394.tgz#50e927bb9f6a559ed21d284e7683ec5e2c784835" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.394.tgz#50e927bb9f6a559ed21d284e7683ec5e2c784835"
@ -7118,6 +7135,13 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
filelist@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
dependencies:
minimatch "^3.0.4"
filesize@^3.1.3: filesize@^3.1.3:
version "3.6.1" version "3.6.1"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
@ -9378,6 +9402,16 @@ istanbul-reports@^3.0.2:
html-escaper "^2.0.0" html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0" istanbul-lib-report "^3.0.0"
jake@^10.6.1:
version "10.8.2"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
dependencies:
async "0.9.x"
chalk "^2.4.2"
filelist "^1.0.1"
minimatch "^3.0.4"
jasmine-ajax@^4.0.0: jasmine-ajax@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/jasmine-ajax/-/jasmine-ajax-4.0.0.tgz#7d8ba7e47e3f7e780e155fe9aa563faafa7e1a26" resolved "https://registry.yarnpkg.com/jasmine-ajax/-/jasmine-ajax-4.0.0.tgz#7d8ba7e47e3f7e780e155fe9aa563faafa7e1a26"