@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.
@@ -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.0.0",
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 };