豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 02089f7

Browse files
committed
feat(github-actions): create action to syncronize labels in angular repositories (#841)
Establish a Github action which syncronizes the labels used in angular repositories and managed by dev-infra tooling. PR Close #841
1 parent 3df1a9f commit 02089f7

File tree

8 files changed

+148
-4
lines changed

8 files changed

+148
-4
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ github-actions/commit-message-based-labels/main.js
1010
github-actions/slash-commands/main.js
1111
github-actions/post-approval-changes/main.js
1212
github-actions/org-file-sync/main.js
13+
github-actions/labels-sync/main.js
1314
.github/local-actions/changelog/main.js
1415

1516
bazel/map-size-tracking/test/size-golden.json
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
load("//tools:defaults.bzl", "esbuild_checked_in", "ts_library")
2+
3+
package(default_visibility = ["//github-actions/labels-sync:__subpackages__"])
4+
5+
ts_library(
6+
name = "labels-sync",
7+
srcs = glob(
8+
["src/*.ts"],
9+
),
10+
deps = [
11+
"//github-actions:utils",
12+
"//ng-dev/pr/common:labels",
13+
"@npm//@actions/core",
14+
"@npm//@actions/github",
15+
"@npm//@octokit/rest",
16+
"@npm//@octokit/types",
17+
"@npm//@types/node",
18+
],
19+
)
20+
21+
esbuild_checked_in(
22+
name = "main",
23+
entry_point = ":src/main.ts",
24+
target = "node16",
25+
deps = [
26+
":labels-sync",
27+
],
28+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Labels Sync
2+
3+
This directory contains an action which can be executed within the dev-infra repository to ensure
4+
that labels which are defined as part of our schema, and kept in sync and consistent across our
5+
repositories.
6+
7+
## License
8+
9+
MIT
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: 'Labels Sync'
2+
description: 'Synchronize labels in repositories throughout the organization'
3+
author: 'Angular'
4+
inputs:
5+
angular-robot-key:
6+
description: 'The private key for the Angular Robot Github app.'
7+
required: true
8+
repos:
9+
description: |
10+
The repositories in which to keep labels in sync. The organization name is derived from
11+
the context in where the action runs.
12+
required: true
13+
14+
runs:
15+
using: 'node16'
16+
main: 'main.js'
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as core from '@actions/core';
2+
import {context} from '@actions/github';
3+
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest';
4+
import {allLabels, Label} from '../../../ng-dev/pr/common/labels.js';
5+
import {getAuthTokenFor, ANGULAR_ROBOT, revokeActiveInstallationToken} from '../../utils.js';
6+
7+
/** The type for a Github label returned from the Github API. */
8+
type GithubLabel =
9+
RestEndpointMethodTypes['issues']['listLabelsForRepo']['response']['data'][number];
10+
11+
/** Synchronize the provided managed labels with the given repository. */
12+
async function syncLabelsInRepo(github: Octokit, repoName: string, managedLabels: Label[]) {
13+
core.startGroup(`Syncing ${repoName}`);
14+
/** The current repository name and owner for usage in the Github API. */
15+
const repo = {repo: repoName, owner: context.repo.owner};
16+
17+
core.debug(`Requesting labels`);
18+
/** The list of current labels from Github for the repository. */
19+
const repoLabels = await github.paginate(github.issues.listLabelsForRepo, context.repo);
20+
core.debug(`Retrieved ${repoLabels.length} from Github`);
21+
22+
// For each label in the list of managed labels, ensure that it exists and is in sync.
23+
// NOTE: Not all labels in repositories are managed. Labels which are not included or managed in
24+
// our tooling definitions and configurations are ignored entirely by tooling.
25+
for (const {description, label} of managedLabels) {
26+
await core.group(`Syncing label: ${label}`, async () => {
27+
/** The label from Github if a match is found. */
28+
const matchedLabel = repoLabels.find(({name}: GithubLabel) => name === label);
29+
30+
// When no matched label is found, Github doesn't currently have the label we intend to sync,
31+
// we create the label via the API directly.
32+
if (matchedLabel === undefined) {
33+
core.info('Adding label to repository');
34+
await github.issues.createLabel({...repo, name: label, description: description});
35+
return;
36+
}
37+
38+
// If a description and name of the label are defined for the managed label, and they match
39+
// the current name and description of the label from Github, everything is in sync.
40+
if (
41+
(description === undefined || description === matchedLabel.description) &&
42+
(label === undefined || label === matchedLabel.name)
43+
) {
44+
core.info('Skipping, label already in sync');
45+
return;
46+
}
47+
48+
// Since the synced and new label cases have been handled, the only remaining action would be
49+
// to update the label to bring the name and description in sync without expectations.
50+
core.info('Updating label in repository');
51+
await github.issues.updateLabel({
52+
...repo,
53+
new_name: label,
54+
name: matchedLabel.name,
55+
description: description,
56+
});
57+
});
58+
}
59+
core.endGroup();
60+
}
61+
62+
async function main() {
63+
/** The Github API instance to use for requests. */
64+
const github = new Octokit({auth: await getAuthTokenFor(ANGULAR_ROBOT)});
65+
66+
try {
67+
/** The list of managed labels. */
68+
const labels = [...Object.values(allLabels)];
69+
/** The repositories to sync the labels in, from the provided config. */
70+
const repos = core.getMultilineInput('repos', {required: true, trimWhitespace: true});
71+
72+
await core.group('Repos being synced:', async () =>
73+
repos.forEach((repo) => core.info(`- ${repo}`)),
74+
);
75+
for (const repo of repos) {
76+
await syncLabelsInRepo(github, repo, labels);
77+
}
78+
} finally {
79+
await revokeActiveInstallationToken(github);
80+
}
81+
}
82+
83+
main().catch((err) => {
84+
console.error(err);
85+
core.setFailed('Failed with the above error');
86+
});

ng-dev/pr/common/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ ts_library(
3434
srcs = [
3535
"labels.ts",
3636
],
37-
deps = [
38-
"//ng-dev/pr/common/labels",
39-
],
4037
visibility = [
4138
"//github-actions:__subpackages__",
4239
"//ng-dev:__subpackages__",
4340
],
41+
deps = [
42+
"//ng-dev/pr/common/labels",
43+
],
4444
)

ng-dev/pr/common/labels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export const allLabels = {
1515
};
1616

1717
export {managedLabels, actionLabels, mergeLabels, targetLabels, priorityLabels};
18+
export {Label} from './labels/base.js';

ng-dev/pr/common/labels/base.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export const createTypedObject = <T>() => <O extends Record<PropertyKey, T>>(v: O) => v;
1+
export const createTypedObject =
2+
<T>() =>
3+
<O extends Record<PropertyKey, T>>(v: O) =>
4+
v;
25

36
export interface Label {
47
/* The label string. */

0 commit comments

Comments
 (0)