-
Notifications
You must be signed in to change notification settings - Fork 8
181 lines (163 loc) · 7.02 KB
/
sync-labels.yml
File metadata and controls
181 lines (163 loc) · 7.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Synchronizes the repo's labels with a centralized `labels.yml` file.
# Requires `GITHUB_TOKEN` to have write permissions; if not, replace it with a custom token.
name: Sync Labels
permissions:
issues: write
pull-requests: write
on:
push:
paths: [
'.github/workflows/sync-labels.yml',
'.github/labels.yml'
]
schedule:
# At 00:00 UTC every Sunday.
- cron: '0 0 * * 0'
workflow_dispatch:
inputs:
allow_custom:
type: boolean
description: Allow Custom Labels
required: true
default: true
jobs:
sync-labels:
runs-on: ubuntu-latest
steps:
# Checkout the repository under $GITHUB_WORKSPACE, so the workflow can access it.
# https://github.com/actions/checkout
- name: Checkout sources
uses: actions/checkout@v6
# Copy the local labels.yml if it exists, else fetch from remote.
- name: Prepare labels.yml
run: |
if [ -f ".github/labels.yml" ]; then
echo "Using local labels.yml file"
cp .github/labels.yml ./labels.yml
else
echo "Local labels.yml file not found, fetching from remote"
curl -sL "https://raw.githubusercontent.com/TerminalMC/.github/HEAD/.github/labels.yml" -o ./labels.yml
fi
- name: Convert to JSON
run: yq -o=json ./labels.yml > ./labels.json
- name: Sync labels
uses: actions/github-script@v9
env:
ALLOW_CUSTOM: ${{ github.event.inputs.allow_custom || 'true' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
// Parse the standard labels
const standardLabels = JSON.parse(fs.readFileSync('./labels.json', 'utf8'));
const { owner, repo } = context.repo;
// Fetch the existing labels
// https://docs.github.com/en/rest/issues/labels#list-labels-for-a-repository
const existingLabels = await github.paginate(github.rest.issues.listLabelsForRepo, {
owner,
repo,
per_page: 100
});
// Process the standard labels
for (const standardLabel of standardLabels) {
const name = standardLabel.name;
if (!name) {
console.error("Skipping label with no name");
continue;
}
const color = (standardLabel.color || 'ededed').replace(/^#/, '').toLowerCase();
const description = standardLabel.description || '';
const aliases = standardLabel.aliases || [];
const shouldDelete = standardLabel.delete === true;
// Initially assume we'll need to create it
let create = !shouldDelete;
// Iterate over existing labels, reversed to allow removal
for (let i = existingLabels.length - 1; i >= 0; i--) {
const existingLabel = existingLabels[i];
const existingName = existingLabel.name;
// Check for a match against the name or any alias
let matched = false;
if (existingName === name) {
matched = true;
// label exists: no need to create
create = false;
console.log(`Found an existing label matching name "${name}"`)
} else {
if (aliases.includes(existingName)) {
matched = true;
console.log(`Found an existing label matching alias "${existingName}" of name "${name}"`)
}
}
if (matched) {
// Match found: delete or update
if (shouldDelete) {
// Delete
// https://docs.github.com/en/rest/issues/labels#delete-a-label
console.log(`Deleting label: "${existingName}"`);
try {
await github.rest.issues.deleteLabel({ owner, repo, name: existingName });
} catch (error) {
console.error(`Failed to delete "${existingName}": ${error.message}`);
}
} else {
// Update
// https://docs.github.com/en/rest/issues/labels#update-a-label
const rename = existingName !== name;
const recolor = existingLabel.color !== color;
const redesc = existingLabel.description !== description;
if (rename || recolor || redesc) {
console.log(`Updating label: "${existingName}"${rename ? " -> " + name : ""}`);
try {
await github.rest.issues.updateLabel({
owner,
repo,
name: existingName,
new_name: rename ? name : undefined,
color,
description
});
} catch (error) {
console.error(`Failed to update "${existingName}": ${error.message}`);
}
} else {
console.log(`No change for label: "${existingName}"`)
}
}
// Remove from the list so it doesn't get deleted or updated again
existingLabels.splice(i, 1);
}
}
if (create) {
// Label doesn't exist yet: create it
// https://docs.github.com/en/rest/issues/labels#create-a-label
console.log(`Creating label: "${name}"`);
try {
await github.rest.issues.createLabel({
owner,
repo,
name,
color,
description
});
existingLabels.push(name);
} catch (error) {
console.error(`Failed to create "${name}": ${error.message}`);
}
}
}
// At this point we've deleted or updated every existing label that matched a
// standard name or alias, so the existing list now only contains custom labels.
if (process.env.ALLOW_CUSTOM !== "true") {
console.log(`Cleaning up ${existingLabels.length} custom label(s)`)
for (const existingLabel of existingLabels) {
const existingName = existingLabel.name;
// Delete
// https://docs.github.com/en/rest/issues/labels#delete-a-label
console.log(`Deleting label: "${existingName}"`);
try {
await github.rest.issues.deleteLabel({ owner, repo, name: existingName });
} catch (error) {
console.error(`Failed to delete "${existingName}": ${error.message}`);
}
}
}