@duckduckgo/autoconsent 16.0.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- package/.github/labeler.yml +36 -0
- package/.github/workflows/release-labels.yml +110 -0
- package/CHANGELOG.md +28 -0
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/content.bundle.js +516 -10
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/content.bundle.js +516 -10
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.cjs.js +516 -10
- package/dist/autoconsent.esm.js +516 -10
- package/dist/autoconsent.playwright.js +516 -10
- package/dist/autoconsent.standalone.js +517 -11
- package/docs/release-notes.md +65 -0
- package/lib/heuristic-patterns.ts +553 -1
- package/package.json +109 -2
- package/rules/autoconsent/nhnieuws.json +5 -8
- package/rules/compact-rules.json +1 -1
- package/rules/rules.json +1 -1
- package/scripts/check-pr-release-labels.ts +255 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { Octokit } from '@octokit/rest';
|
|
3
|
+
|
|
4
|
+
const RELEASE_IMPACT_LABELS = ['major', 'minor', 'patch'] as const;
|
|
5
|
+
const CATEGORY_LABELS = [
|
|
6
|
+
'rules',
|
|
7
|
+
'bug',
|
|
8
|
+
'enhancement',
|
|
9
|
+
'performance',
|
|
10
|
+
'dependencies',
|
|
11
|
+
'ci',
|
|
12
|
+
'ai',
|
|
13
|
+
'documentation',
|
|
14
|
+
'tests',
|
|
15
|
+
'internal',
|
|
16
|
+
'other',
|
|
17
|
+
] as const;
|
|
18
|
+
const COMMENT_MARKER = '<!-- autoconsent-release-label-check -->';
|
|
19
|
+
|
|
20
|
+
type LabelName = (typeof RELEASE_IMPACT_LABELS)[number] | (typeof CATEGORY_LABELS)[number];
|
|
21
|
+
|
|
22
|
+
interface Args {
|
|
23
|
+
event?: string;
|
|
24
|
+
fetchCurrentLabels?: boolean;
|
|
25
|
+
jsonOutput?: string;
|
|
26
|
+
labels?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PullRequestLabel {
|
|
30
|
+
name: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PullRequest {
|
|
34
|
+
labels?: Array<string | PullRequestLabel>;
|
|
35
|
+
number: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface PullRequestEvent {
|
|
39
|
+
pull_request?: PullRequest;
|
|
40
|
+
labels?: Array<string | PullRequestLabel>;
|
|
41
|
+
number?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ValidationResult {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
labels: string[];
|
|
47
|
+
releaseImpactLabels: string[];
|
|
48
|
+
categoryLabels: string[];
|
|
49
|
+
errors: string[];
|
|
50
|
+
comment?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseArgs(argv: string[]): Args {
|
|
54
|
+
const args: Args = {};
|
|
55
|
+
|
|
56
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
57
|
+
const arg = argv[i];
|
|
58
|
+
|
|
59
|
+
if (arg === '--fetch-current-labels') {
|
|
60
|
+
args.fetchCurrentLabels = true;
|
|
61
|
+
} else if (arg === '--event') {
|
|
62
|
+
args.event = argv[(i += 1)];
|
|
63
|
+
} else if (arg === '--json-output') {
|
|
64
|
+
args.jsonOutput = argv[(i += 1)];
|
|
65
|
+
} else if (arg === '--labels') {
|
|
66
|
+
args.labels = argv[(i += 1)]
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((label) => label.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return args;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readJson(filePath: string): unknown {
|
|
79
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readEvent(eventPath?: string): PullRequestEvent {
|
|
83
|
+
if (!eventPath) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return readJson(eventPath) as PullRequestEvent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getPullRequest(event: PullRequestEvent): PullRequest | undefined {
|
|
91
|
+
if (event.pull_request) {
|
|
92
|
+
return event.pull_request;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (event.number && event.labels) {
|
|
96
|
+
return event as PullRequest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getLabelsFromPullRequest(pullRequest: Pick<PullRequest, 'labels'>): string[] {
|
|
103
|
+
return (pullRequest.labels || []).map((label) => {
|
|
104
|
+
if (typeof label === 'string') {
|
|
105
|
+
return label;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return label.name;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function uniqueLabels(labels: string[]): string[] {
|
|
113
|
+
return [...new Set(labels)].sort((a, b) => a.localeCompare(b));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isKnownLabel(labelSet: readonly LabelName[], label: string): boolean {
|
|
117
|
+
return labelSet.some((knownLabel) => knownLabel === label);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function validateLabels(labels: string[]): ValidationResult {
|
|
121
|
+
const currentLabels = uniqueLabels(labels);
|
|
122
|
+
const releaseImpactLabels = currentLabels.filter((label) => isKnownLabel(RELEASE_IMPACT_LABELS, label));
|
|
123
|
+
const categoryLabels = currentLabels.filter((label) => isKnownLabel(CATEGORY_LABELS, label));
|
|
124
|
+
const errors: string[] = [];
|
|
125
|
+
|
|
126
|
+
if (releaseImpactLabels.length === 0) {
|
|
127
|
+
errors.push(`Add exactly one release impact label: ${RELEASE_IMPACT_LABELS.join(', ')}.`);
|
|
128
|
+
} else if (releaseImpactLabels.length > 1) {
|
|
129
|
+
errors.push(`Keep exactly one release impact label; found ${releaseImpactLabels.join(', ')}.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (categoryLabels.length === 0) {
|
|
133
|
+
errors.push(`Add exactly one release-note category label: ${CATEGORY_LABELS.join(', ')}.`);
|
|
134
|
+
} else if (categoryLabels.length > 1) {
|
|
135
|
+
errors.push(`Keep exactly one release-note category label; found ${categoryLabels.join(', ')}.`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
ok: errors.length === 0,
|
|
140
|
+
labels: currentLabels,
|
|
141
|
+
releaseImpactLabels,
|
|
142
|
+
categoryLabels,
|
|
143
|
+
errors,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatLabels(labels: readonly string[]): string {
|
|
148
|
+
if (labels.length === 0) {
|
|
149
|
+
return '_none_';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return labels.map((label) => `\`${label}\``).join(', ');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatComment(result: ValidationResult): string {
|
|
156
|
+
if (result.ok) {
|
|
157
|
+
return `${COMMENT_MARKER}
|
|
158
|
+
### Release label check resolved
|
|
159
|
+
|
|
160
|
+
The release labels are valid.`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return `${COMMENT_MARKER}
|
|
164
|
+
### Release label check failed
|
|
165
|
+
|
|
166
|
+
Please update this PR's release labels before merging.
|
|
167
|
+
|
|
168
|
+
Current labels: ${formatLabels(result.labels)}
|
|
169
|
+
|
|
170
|
+
Problems:
|
|
171
|
+
${result.errors.map((error) => `- ${error}`).join('\n')}
|
|
172
|
+
|
|
173
|
+
Required labels:
|
|
174
|
+
- One release impact label: ${formatLabels(RELEASE_IMPACT_LABELS)}
|
|
175
|
+
- One release-note category label: ${formatLabels(CATEGORY_LABELS)}
|
|
176
|
+
|
|
177
|
+
See [docs/release-notes.md](https://github.com/duckduckgo/autoconsent/blob/main/docs/release-notes.md) for examples.`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getRepositoryParts(repository: string): { owner: string; repo: string } {
|
|
181
|
+
const [owner, repo] = repository.split('/');
|
|
182
|
+
|
|
183
|
+
if (!owner || !repo) {
|
|
184
|
+
throw new Error(`Invalid GITHUB_REPOSITORY value: ${repository}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { owner, repo };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function fetchCurrentLabels(event: PullRequestEvent): Promise<string[]> {
|
|
191
|
+
const pullRequest = getPullRequest(event);
|
|
192
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
193
|
+
|
|
194
|
+
if (!pullRequest || !repository) {
|
|
195
|
+
throw new Error('Cannot fetch current labels without a pull request event and repository.');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { owner, repo } = getRepositoryParts(repository);
|
|
199
|
+
const octokit = new Octokit({
|
|
200
|
+
auth: process.env.GITHUB_TOKEN,
|
|
201
|
+
baseUrl: process.env.GITHUB_API_URL || 'https://api.github.com',
|
|
202
|
+
});
|
|
203
|
+
const labels = await octokit.paginate(octokit.rest.issues.listLabelsOnIssue, {
|
|
204
|
+
owner,
|
|
205
|
+
repo,
|
|
206
|
+
issue_number: pullRequest.number,
|
|
207
|
+
per_page: 100,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return labels.map((label) => label.name);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function main(): Promise<void> {
|
|
214
|
+
const args = parseArgs(process.argv);
|
|
215
|
+
const eventPath = args.event || process.env.GITHUB_EVENT_PATH;
|
|
216
|
+
const event = readEvent(eventPath);
|
|
217
|
+
const pullRequest = getPullRequest(event);
|
|
218
|
+
let labels = args.labels;
|
|
219
|
+
|
|
220
|
+
if (!labels && args.fetchCurrentLabels) {
|
|
221
|
+
labels = await fetchCurrentLabels(event);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!labels && pullRequest) {
|
|
225
|
+
labels = getLabelsFromPullRequest(pullRequest);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!labels) {
|
|
229
|
+
throw new Error('No pull request labels found to validate.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const result: ValidationResult = validateLabels(labels);
|
|
233
|
+
result.comment = formatComment(result);
|
|
234
|
+
|
|
235
|
+
if (args.jsonOutput) {
|
|
236
|
+
fs.writeFileSync(args.jsonOutput, `${JSON.stringify(result, null, 2)}\n`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (result.ok) {
|
|
240
|
+
console.log('Release labels look good.');
|
|
241
|
+
} else {
|
|
242
|
+
console.error(result.comment);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
process.exit(result.ok ? 0 : 1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (require.main === module) {
|
|
249
|
+
main().catch((error: unknown) => {
|
|
250
|
+
console.error(error instanceof Error ? error.message : error);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export { CATEGORY_LABELS, RELEASE_IMPACT_LABELS };
|