@duckduckgo/autoconsent 16.0.0 → 16.1.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 +13 -0
- package/docs/release-notes.md +65 -0
- package/package.json +109 -2
- package/scripts/check-pr-release-labels.ts +255 -0
|
@@ -2,6 +2,13 @@ Task/Issue URL:
|
|
|
2
2
|
|
|
3
3
|
## Description:
|
|
4
4
|
|
|
5
|
+
<!--
|
|
6
|
+
Don't forget to label the PR.
|
|
7
|
+
|
|
8
|
+
Impact: choose one of `major`, `minor`, or `patch`.
|
|
9
|
+
Category: choose one of `rules`, `bug`, `enhancement`, `performance`, `dependencies`, `ci`, `ai`, `documentation`, `tests`, `internal`, or `other`.
|
|
10
|
+
Details: https://github.com/duckduckgo/autoconsent/blob/main/docs/release-notes.md
|
|
11
|
+
-->
|
|
5
12
|
|
|
6
13
|
## Steps to test this PR:
|
|
7
14
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
ai:
|
|
2
|
+
- changed-files:
|
|
3
|
+
- any-glob-to-any-file:
|
|
4
|
+
- '.agents/**'
|
|
5
|
+
- 'AGENTS.md'
|
|
6
|
+
- 'CLAUDE.md'
|
|
7
|
+
|
|
8
|
+
ci:
|
|
9
|
+
- changed-files:
|
|
10
|
+
- any-glob-to-any-file:
|
|
11
|
+
- '.github/**'
|
|
12
|
+
- 'ci/**'
|
|
13
|
+
- 'scripts/**'
|
|
14
|
+
|
|
15
|
+
dependencies:
|
|
16
|
+
- changed-files:
|
|
17
|
+
- any-glob-to-any-file:
|
|
18
|
+
- 'package.json'
|
|
19
|
+
- 'package-lock.json'
|
|
20
|
+
|
|
21
|
+
documentation:
|
|
22
|
+
- changed-files:
|
|
23
|
+
- any-glob-to-any-file:
|
|
24
|
+
- 'docs/**'
|
|
25
|
+
|
|
26
|
+
rules:
|
|
27
|
+
- changed-files:
|
|
28
|
+
- any-glob-to-any-file:
|
|
29
|
+
- 'rules/**'
|
|
30
|
+
- 'tests/**/*.spec.ts'
|
|
31
|
+
|
|
32
|
+
tests:
|
|
33
|
+
- changed-files:
|
|
34
|
+
- any-glob-to-any-file:
|
|
35
|
+
- 'playwright/**'
|
|
36
|
+
- 'web-test-runner.config.mjs'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
name: Release Labels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types:
|
|
6
|
+
- opened
|
|
7
|
+
- reopened
|
|
8
|
+
- synchronize
|
|
9
|
+
- ready_for_review
|
|
10
|
+
- labeled
|
|
11
|
+
- unlabeled
|
|
12
|
+
- edited
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
issues: write
|
|
17
|
+
pull-requests: write
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: release-labels-${{ github.event.pull_request.number }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
release-labels:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Apply path-based labels
|
|
30
|
+
if: >-
|
|
31
|
+
${{
|
|
32
|
+
github.event.action == 'opened' ||
|
|
33
|
+
github.event.action == 'reopened' ||
|
|
34
|
+
github.event.action == 'ready_for_review'
|
|
35
|
+
}}
|
|
36
|
+
uses: actions/labeler@v6
|
|
37
|
+
with:
|
|
38
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
+
sync-labels: false
|
|
40
|
+
|
|
41
|
+
- name: Use Node.js
|
|
42
|
+
uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: lts/*
|
|
45
|
+
cache: 'npm'
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: npm ci --ignore-scripts
|
|
49
|
+
|
|
50
|
+
- name: Check release labels
|
|
51
|
+
id: check_release_labels
|
|
52
|
+
continue-on-error: true
|
|
53
|
+
env:
|
|
54
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
run: |
|
|
56
|
+
npm run check-pr-release-labels -- \
|
|
57
|
+
--fetch-current-labels \
|
|
58
|
+
--json-output "$RUNNER_TEMP/release-label-check.json"
|
|
59
|
+
|
|
60
|
+
- name: Comment on release label issues
|
|
61
|
+
if: always()
|
|
62
|
+
uses: actions/github-script@v7
|
|
63
|
+
with:
|
|
64
|
+
script: |
|
|
65
|
+
const fs = require('fs');
|
|
66
|
+
|
|
67
|
+
const resultPath = `${process.env.RUNNER_TEMP}/release-label-check.json`;
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(resultPath)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const marker = '<!-- autoconsent-release-label-check -->';
|
|
74
|
+
const result = JSON.parse(fs.readFileSync(resultPath, 'utf8'));
|
|
75
|
+
const { owner, repo } = context.repo;
|
|
76
|
+
const issue_number = context.payload.pull_request.number;
|
|
77
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
78
|
+
owner,
|
|
79
|
+
repo,
|
|
80
|
+
issue_number,
|
|
81
|
+
per_page: 100,
|
|
82
|
+
});
|
|
83
|
+
const existingComment = comments.find(
|
|
84
|
+
(comment) => comment.user.type === 'Bot' && comment.body?.includes(marker)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!existingComment && result.ok) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (existingComment) {
|
|
92
|
+
await github.rest.issues.updateComment({
|
|
93
|
+
owner,
|
|
94
|
+
repo,
|
|
95
|
+
comment_id: existingComment.id,
|
|
96
|
+
body: result.comment,
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await github.rest.issues.createComment({
|
|
102
|
+
owner,
|
|
103
|
+
repo,
|
|
104
|
+
issue_number,
|
|
105
|
+
body: result.comment,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
- name: Fail on release label issues
|
|
109
|
+
if: steps.check_release_labels.outcome == 'failure'
|
|
110
|
+
run: exit 1
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# v16.1.0 (Fri Jun 26 2026)
|
|
2
|
+
|
|
3
|
+
#### CI / Release Automation
|
|
4
|
+
|
|
5
|
+
- Revamp release note labeling [#1410](https://github.com/duckduckgo/autoconsent/pull/1410) ([@cursoragent](https://github.com/cursoragent) [@muodov](https://github.com/muodov))
|
|
6
|
+
|
|
7
|
+
#### Authors: 2
|
|
8
|
+
|
|
9
|
+
- Cursor Agent ([@cursoragent](https://github.com/cursoragent))
|
|
10
|
+
- Maxim Tsoy ([@muodov](https://github.com/muodov))
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
1
14
|
# v16.0.0 (Tue Jun 23 2026)
|
|
2
15
|
|
|
3
16
|
#### 💥 Breaking Change
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Release note labels
|
|
2
|
+
|
|
3
|
+
Release notes are generated by `auto` from merged PR labels. Each PR that should
|
|
4
|
+
appear in release notes needs two release-related decisions before merge:
|
|
5
|
+
|
|
6
|
+
1. Exactly one release impact label.
|
|
7
|
+
2. Exactly one primary release-note category label.
|
|
8
|
+
|
|
9
|
+
Use `other` as the category for changes that should appear in release notes but
|
|
10
|
+
do not fit another category.
|
|
11
|
+
|
|
12
|
+
## Release impact labels
|
|
13
|
+
|
|
14
|
+
Choose exactly one:
|
|
15
|
+
|
|
16
|
+
| Label | Use when |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `major` | The change is breaking for consumers or downstream app integration. |
|
|
19
|
+
| `minor` | The change adds new behavior, rule coverage, or capabilities. |
|
|
20
|
+
| `patch` | The change fixes existing behavior or makes a non-breaking maintenance update. |
|
|
21
|
+
|
|
22
|
+
## Release-note category labels
|
|
23
|
+
|
|
24
|
+
Choose exactly one category:
|
|
25
|
+
|
|
26
|
+
| Label | Use when |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `rules` | Adding, updating, or regenerating autoconsent rules. |
|
|
29
|
+
| `bug` | Fixing broken behavior, including rule fixes and race-condition fixes. |
|
|
30
|
+
| `enhancement` | Adding or improving non-rule functionality. |
|
|
31
|
+
| `performance` | Improving runtime performance or measurement overhead. |
|
|
32
|
+
| `dependencies` | Updating package dependencies. |
|
|
33
|
+
| `ci` | Changing CI, release automation, or scripts used by automation. |
|
|
34
|
+
| `ai` | Changing agent instructions, workflows, or skills. |
|
|
35
|
+
| `documentation` | Documentation-only changes. |
|
|
36
|
+
| `tests` | Changing test harnesses, fixtures, or test infrastructure. Do not use this for ordinary tests added with feature or rule work. |
|
|
37
|
+
| `internal` | Internal-only maintenance that does not fit the categories above. |
|
|
38
|
+
| `other` | Changes that should appear in release notes but do not fit another category. |
|
|
39
|
+
|
|
40
|
+
## Common examples
|
|
41
|
+
|
|
42
|
+
| PR type | Labels |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| New CMP rule or new site-specific rule | `minor`, `rules` |
|
|
45
|
+
| Existing rule fix | `patch`, `bug` |
|
|
46
|
+
| Generated rule update | `minor` or `patch`, `rules` |
|
|
47
|
+
| Core API or integration breaking change | `major`, `enhancement` |
|
|
48
|
+
| CI workflow or release script update | `patch`, `ci` |
|
|
49
|
+
| Dependency update | `patch`, `dependencies` |
|
|
50
|
+
| Agent skill or instruction update | `patch`, `ai` |
|
|
51
|
+
| Documentation-only update | `patch`, `documentation` |
|
|
52
|
+
| Test runner, fixture, or harness update | `patch`, `tests` |
|
|
53
|
+
| Miscellaneous repo maintenance | `patch`, `other` |
|
|
54
|
+
|
|
55
|
+
If a PR spans multiple categories, choose the category that best describes the
|
|
56
|
+
user-visible release note. Mention secondary details in the PR description.
|
|
57
|
+
|
|
58
|
+
## Syncing GitHub labels
|
|
59
|
+
|
|
60
|
+
The label definitions live in `package.json` under the `auto.labels` config. If
|
|
61
|
+
labels are missing or descriptions/colors drift, sync them with:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx auto create-labels
|
|
65
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duckduckgo/autoconsent",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"types": "./dist/types/web.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -36,13 +36,15 @@
|
|
|
36
36
|
"launch:firefox": "web-ext run -s dist/addon-firefox",
|
|
37
37
|
"release": "auto shipit",
|
|
38
38
|
"get-text-for-xpath": "ts-node scripts/get-text-for-xpath.ts",
|
|
39
|
-
"bundle-config-rules": "ts-node scripts/bundle-config-rules.ts"
|
|
39
|
+
"bundle-config-rules": "ts-node scripts/bundle-config-rules.ts",
|
|
40
|
+
"check-pr-release-labels": "ts-node scripts/check-pr-release-labels.ts"
|
|
40
41
|
},
|
|
41
42
|
"author": "Sam Macbeth",
|
|
42
43
|
"license": "MPL-2.0",
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#v0.1.0",
|
|
45
46
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
|
47
|
+
"@octokit/rest": "^22.0.1",
|
|
46
48
|
"@playwright/test": "^1.17.1",
|
|
47
49
|
"@types/chai": "^5.0.0",
|
|
48
50
|
"@types/chrome": "^0.1.0",
|
|
@@ -76,6 +78,111 @@
|
|
|
76
78
|
},
|
|
77
79
|
"repository": "duckduckgo/autoconsent",
|
|
78
80
|
"auto": {
|
|
81
|
+
"noDefaultLabels": true,
|
|
82
|
+
"labels": [
|
|
83
|
+
{
|
|
84
|
+
"name": "major",
|
|
85
|
+
"description": "Increment the major version when merged",
|
|
86
|
+
"releaseType": "major",
|
|
87
|
+
"color": "#C5000B"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "minor",
|
|
91
|
+
"description": "Increment the minor version when merged",
|
|
92
|
+
"releaseType": "minor",
|
|
93
|
+
"color": "#F1A60E"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "patch",
|
|
97
|
+
"description": "Increment the patch version when merged",
|
|
98
|
+
"releaseType": "patch",
|
|
99
|
+
"default": true,
|
|
100
|
+
"color": "#870048"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "release",
|
|
104
|
+
"description": "Create a release when this PR is merged",
|
|
105
|
+
"releaseType": "release",
|
|
106
|
+
"color": "#007f70"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "rules",
|
|
110
|
+
"changelogTitle": "Rules",
|
|
111
|
+
"description": "Autoconsent rule additions, updates, and generated rule changes",
|
|
112
|
+
"releaseType": "none",
|
|
113
|
+
"color": "#bfdadc"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "bug",
|
|
117
|
+
"changelogTitle": "Bug Fixes",
|
|
118
|
+
"description": "Something isn't working",
|
|
119
|
+
"releaseType": "none",
|
|
120
|
+
"color": "#d73a4a"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "enhancement",
|
|
124
|
+
"changelogTitle": "Enhancements",
|
|
125
|
+
"description": "New feature or request",
|
|
126
|
+
"releaseType": "none",
|
|
127
|
+
"color": "#a2eeef"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "performance",
|
|
131
|
+
"changelogTitle": "Performance",
|
|
132
|
+
"description": "Improve performance of an existing feature",
|
|
133
|
+
"releaseType": "none",
|
|
134
|
+
"color": "#f4b2d8"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "dependencies",
|
|
138
|
+
"changelogTitle": "Dependencies",
|
|
139
|
+
"description": "Update one or more dependencies version",
|
|
140
|
+
"releaseType": "none",
|
|
141
|
+
"color": "#8732bc"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "ci",
|
|
145
|
+
"changelogTitle": "CI / Release Automation",
|
|
146
|
+
"description": "Automation scripts and release infrastructure",
|
|
147
|
+
"releaseType": "none",
|
|
148
|
+
"color": "#2612BF"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "ai",
|
|
152
|
+
"changelogTitle": "AI / Agent Workflow",
|
|
153
|
+
"description": "Changes to agent workflows and skills",
|
|
154
|
+
"releaseType": "none",
|
|
155
|
+
"color": "#df4f06"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "documentation",
|
|
159
|
+
"changelogTitle": "Documentation",
|
|
160
|
+
"description": "Changes only affect the documentation",
|
|
161
|
+
"releaseType": "none",
|
|
162
|
+
"color": "#cfd3d7"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "tests",
|
|
166
|
+
"changelogTitle": "Test Infrastructure",
|
|
167
|
+
"description": "Changes to test harnesses, fixtures, or test infrastructure",
|
|
168
|
+
"releaseType": "none",
|
|
169
|
+
"color": "#ffd3cc"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "internal",
|
|
173
|
+
"changelogTitle": "Internal",
|
|
174
|
+
"description": "Changes only affect internal implementation details",
|
|
175
|
+
"releaseType": "none",
|
|
176
|
+
"color": "#696969"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "other",
|
|
180
|
+
"changelogTitle": "Other",
|
|
181
|
+
"description": "Changes that should appear in release notes but do not fit another category",
|
|
182
|
+
"releaseType": "none",
|
|
183
|
+
"color": "#ededed"
|
|
184
|
+
}
|
|
185
|
+
],
|
|
79
186
|
"plugins": [
|
|
80
187
|
"npm"
|
|
81
188
|
]
|
|
@@ -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 };
|