Skip to content

Commit

Permalink
ci: create local action to generate changelog entries weekly
Browse files Browse the repository at this point in the history
  • Loading branch information
josephperrott committed Aug 26, 2021
1 parent 28bf40c commit 57472ba
Show file tree
Hide file tree
Showing 14 changed files with 62,165 additions and 2 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/update-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Update changelog

on:
workflow_dispatch:
schedule:
# Run every Sunday at 0:00
- cron: '0 0 * * 0'

jobs:
update_changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./tools/local-actions/changelog
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ github-actions/breaking-changes-label/main.js
github-actions/breaking-changes-label/post.js
github-actions/slash-commands/main.js
github-actions/slash-commands/post.js
tools/local-actions/changelog/main.js
tools/local-actions/changelog/post.js
Empty file added CHANGELOG.md
Empty file.
4 changes: 4 additions & 0 deletions github-actions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ ts_library(
srcs = [
"utils.ts",
],
visibility = [
"//github-actions:__subpackages__",
"//tools/local-actions:__subpackages__",
],
deps = [
"@npm//@actions/core",
"@npm//@actions/github",
Expand Down
5 changes: 4 additions & 1 deletion ng-dev/release/config/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ ts_library(
srcs = glob([
"**/*.ts",
]),
visibility = ["//ng-dev:__subpackages__"],
visibility = [
"//ng-dev:__subpackages__",
"//tools/local-actions/changelog/lib:__subpackages__",
],
deps = [
"//ng-dev/commit-message",
"//ng-dev/utils",
Expand Down
5 changes: 4 additions & 1 deletion ng-dev/release/notes/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ ts_library(
srcs = glob([
"**/*.ts",
]),
visibility = ["//ng-dev:__subpackages__"],
visibility = [
"//ng-dev:__subpackages__",
"//tools/local-actions/changelog/lib:__subpackages__",
],
deps = [
"//ng-dev/commit-message",
"//ng-dev/release/config",
Expand Down
1 change: 1 addition & 0 deletions ng-dev/utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ts_library(
visibility = [
"//github-actions/slash-commands/lib:__subpackages__",
"//ng-dev:__subpackages__",
"//tools/local-actions/changelog/lib:__subpackages__",
],
deps = [
"@npm//@octokit/core",
Expand Down
19 changes: 19 additions & 0 deletions tools/local-actions/changelog/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("//tools:defaults.bzl", "esbuild_checked_in")

esbuild_checked_in(
name = "post",
entry_point = "//tools/local-actions/changelog/lib:post.ts",
external = ["ts-node"],
deps = [
"//tools/local-actions/changelog/lib",
],
)

esbuild_checked_in(
name = "main",
entry_point = "//tools/local-actions/changelog/lib:main.ts",
external = ["ts-node"],
deps = [
"//tools/local-actions/changelog/lib",
],
)
11 changes: 11 additions & 0 deletions tools/local-actions/changelog/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: 'Create DevInfra Changelogs'
description: 'Automatically create changelog entries for the dev-infra repo'
author: 'Angular'
inputs:
angular-robot-key:
description: 'The private key for the Angular Robot Github app.'
required: true
runs:
using: 'node12'
main: 'main.js'
post: 'post.js'
28 changes: 28 additions & 0 deletions tools/local-actions/changelog/lib/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("//tools:defaults.bzl", "ts_library")

package(default_visibility = ["//tools/local-actions/changelog:__subpackages__"])

exports_files([
"main.ts",
"post.ts",
])

ts_library(
name = "lib",
srcs = glob(
["*.ts"],
exclude = ["*.spec.ts"],
),
deps = [
"//github-actions:utils",
"//ng-dev/release/config",
"//ng-dev/release/notes",
"//ng-dev/utils",
"@npm//@actions/core",
"@npm//@actions/github",
"@npm//@octokit/rest",
"@npm//@types/node",
"@npm//@types/semver",
"@npm//semver",
],
)
132 changes: 132 additions & 0 deletions tools/local-actions/changelog/lib/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as core from '@actions/core';
import {context} from '@actions/github';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {join} from 'path';
import {SemVer} from 'semver';
import {ReleaseNotes} from '../../../../ng-dev/release/notes/release-notes';
import {AuthenticatedGitClient} from '../../../../ng-dev/utils/git/authenticated-git-client';
import {ANGULAR_ROBOT, getAuthTokenFor} from '../../../../github-actions/utils';
import {GithubConfig, setConfig} from '../../../../ng-dev/utils/config';
import {ReleaseConfig} from '../../../../ng-dev/release/config/index';

/** The tag used for tracking the last time the changlog was generated. */
const lastChangelogTag = 'most-recent-changelog-generation';
/** Marker comment used to split the changelog into a list of distinct changelog entries. */
const splitMarker = '\n<!-- CHANGELOG SPLIT MARKER -->\n';
/** The commit message used for the changes to the CHANGELOG. */
const commitMessage = 'release: create weekly changelog entry';

// Set the cached configuration object to be used throughout the action.
const config: {github: GithubConfig; release: ReleaseConfig} = {
github: {
mainBranchName: 'main',
name: context.repo.repo,
owner: context.repo.owner,
},
release: {
npmPackages: [],
buildPackages: async () => [],
releaseNotes: {
categorizeCommit: (commit) => {
const [groupName, area] = commit.scope.split('/');
/** The scope slug to be used in the description's used in CHANGELOG.md */
const scope = area ? `**${area}:** ` : '';
return {
groupName,
description: `${scope}${commit.subject}`,
};
},
},
},
};
setConfig(config);

async function run(): Promise<void> {
// Configure the AuthenticatedGitClient to be authenticated with the token for the Angular Robot.
AuthenticatedGitClient.configure(await getAuthTokenFor(ANGULAR_ROBOT));
/** The authenticed GitClient. */
const git = AuthenticatedGitClient.get();
git.run(['config', 'user.email', '[email protected]']);
git.run(['config', 'user.name', 'Angular Robot']);

/** The full path to the changelog file. */
const changelogFile = join(git.baseDir, 'CHANGELOG.md');
/** The full path of the changelog */
const changelogArchiveFile = join(git.baseDir, 'CHANGELOG_ARCHIVE.md');
/** The sha of the commit when the changelog was most recently generated. */
const lastChangelogRef = getLatestRefFromUpstream(lastChangelogTag);
/** The sha of the latest commit on the main branch. */
const latestRef = getLatestRefFromUpstream(git.mainBranchName);
/** The release notes generation object. */
const releaseNotes = await ReleaseNotes.forRange(getTodayAsSemver(), lastChangelogRef, latestRef);

if ((await releaseNotes.getCommitCountInReleaseNotes()) === 0) {
console.log('No release notes are needed as no commits would be included.');
return;
}

/** The changelog entry for commits on the main branch since the last changelog was generated. */
const changelogEntry = await releaseNotes.getChangelogEntry();

// Checkout the main branch at the latest commit.
git.run(['checkout', '--detach', latestRef]);

/** The changelog entries in the current changelog. */
const changelog = readFileSync(changelogFile, {encoding: 'utf8'}).split(splitMarker);

// When the changelog has more than 12 entries (roughly one quarter of the year in weekly
// releases), extra changelog entries are moved to the changelog archive.
if (changelog.length > 12) {
/** The changelog entries in the changelog archive. */
let changelogArchive: string[] = [];
if (existsSync(changelogArchiveFile)) {
changelogArchive = readFileSync(changelogArchiveFile, {encoding: 'utf8'}).split(splitMarker);
}
changelogArchive.unshift(...changelog.splice(12));
writeAndAddToGit(changelogArchiveFile, changelogArchive.join(splitMarker));
}

// Place the new changelog entry at the beginning of the changelog entries list.
changelog.unshift(changelogEntry);
writeAndAddToGit(changelogFile, changelog.join(splitMarker));

// Commit the new changelog(s) and push the changes to github.
git.run(['commit', '--no-verify', '-m', commitMessage]);
git.run(['push', git.getRepoGitUrl(), `HEAD:refs/heads/${git.mainBranchName}`]);
// A force push is used to update the tag git does not expect it to move and a force is neccessary
// to update it from its old sha.
git.run(['push', '-f', git.getRepoGitUrl(), `HEAD:refs/tags/${lastChangelogTag}`]);
}

/** Write the contents to the provided file and add it to git staging. */
function writeAndAddToGit(filePath: string, contents: string) {
const git = AuthenticatedGitClient.get();
writeFileSync(filePath, contents);
git.run(['add', filePath]);
}

/** Retrieve the latest ref for the branch or tag from upstream. */
function getLatestRefFromUpstream(branchOrTag: string) {
try {
const git = AuthenticatedGitClient.get();
git.runGraceful(['fetch', git.getRepoGitUrl(), branchOrTag, '--depth=250']);
return git.runGraceful(['rev-parse', 'FETCH_HEAD']).stdout.trim();
} catch {
core.error(`Unable to retrieve '${branchOrTag}' from upstream`);
process.exit(1);
}
}

/** Create a semver tag based on todays date. */
function getTodayAsSemver() {
const today = new Date();
return new SemVer(`${today.getFullYear()}.${today.getMonth() + 1}.${today.getDay()}`);
}

// This action should only be run in the angular/dev-infra repo.
if (context.repo.owner === 'angular' && context.repo.repo === 'dev-infra') {
run().catch((e: Error) => {
core.error(e);
core.setFailed(e.message);
});
}
7 changes: 7 additions & 0 deletions tools/local-actions/changelog/lib/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {revokeAuthTokenFor, ANGULAR_ROBOT} from '../../../../github-actions/utils';

async function run(): Promise<void> {
await revokeAuthTokenFor(ANGULAR_ROBOT);
}

run();
Loading

0 comments on commit 57472ba

Please sign in to comment.