@swisspost/design-system-changelog-github 10.0.0-next.71 → 10.0.0-next.75

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @swisspost/design-system-changelog-github
2
2
 
3
+ ## 10.0.0-next.75
4
+
5
+ ### Minor Changes
6
+
7
+ - Updated dependencies, co-contributors are now mentioned in changelogs and added a delay when making GitHub API calls to enable exiting pre-mode with 900+ changesets. (by [@gfellerph](https://github.com/gfellerph) with [#7923](https://github.com/swisspost/design-system/pull/7923))
8
+
9
+ ## 10.0.0-next.74
10
+
11
+ ## 10.0.0-next.73
12
+
13
+ ## 10.0.0-next.72
14
+
3
15
  ## 10.0.0-next.71
4
16
 
5
17
  ## 10.0.0-next.70
package/dist/index.js CHANGED
@@ -7,6 +7,105 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const dotenv_1 = require("dotenv");
8
8
  const get_github_info_1 = require("@changesets/get-github-info");
9
9
  (0, dotenv_1.config)();
10
+ /**
11
+ * Simple concurrency limiter to avoid overwhelming the GitHub API.
12
+ * Limits parallel requests and adds a small delay between them.
13
+ */
14
+ function createRateLimiter(concurrency, delayMs) {
15
+ let active = 0;
16
+ const queue = [];
17
+ function next() {
18
+ if (queue.length > 0 && active < concurrency) {
19
+ active++;
20
+ const resolve = queue.shift();
21
+ resolve();
22
+ }
23
+ }
24
+ return async function (fn) {
25
+ await new Promise(resolve => {
26
+ queue.push(resolve);
27
+ next();
28
+ });
29
+ try {
30
+ const result = await fn();
31
+ return result;
32
+ }
33
+ finally {
34
+ active--;
35
+ // Small delay between requests to avoid hitting secondary rate limits
36
+ await new Promise(resolve => setTimeout(resolve, delayMs));
37
+ next();
38
+ }
39
+ };
40
+ }
41
+ // Cache GitHub info lookups by commit hash to avoid duplicate API calls
42
+ const commitInfoCache = new Map();
43
+ // Cache co-authors by commit hash
44
+ const coAuthorsCache = new Map();
45
+ // Allow max 5 concurrent requests with 100ms delay between them
46
+ const rateLimited = createRateLimiter(5, 100);
47
+ // Progress tracking
48
+ let processedCount = 0;
49
+ let cacheHits = 0;
50
+ async function getGitHubInfo(repo, commit) {
51
+ if (commitInfoCache.has(commit)) {
52
+ cacheHits++;
53
+ processedCount++;
54
+ process.stderr.write(`\ršŸ”— Changelog: ${processedCount} processed (${cacheHits} cached)`);
55
+ return commitInfoCache.get(commit);
56
+ }
57
+ const promise = rateLimited(() => (0, get_github_info_1.getInfo)({ repo, commit }));
58
+ commitInfoCache.set(commit, promise);
59
+ processedCount++;
60
+ process.stderr.write(`\ršŸ”— Changelog: ${processedCount} processed (${cacheHits} cached)`);
61
+ return promise;
62
+ }
63
+ /**
64
+ * Fetch co-authors from the squash merge commit's Co-authored-by trailers.
65
+ * With squash merging, the merge commit contains all co-author information.
66
+ */
67
+ async function getCoAuthors(repo, commit, mainUser) {
68
+ if (coAuthorsCache.has(commit)) {
69
+ return coAuthorsCache.get(commit);
70
+ }
71
+ const promise = rateLimited(async () => {
72
+ const token = process.env.GITHUB_TOKEN;
73
+ if (!token)
74
+ return [];
75
+ const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
76
+ const apiUrl = process.env.GITHUB_API_URL || 'https://api.github.com';
77
+ const response = await fetch(`${apiUrl}/repos/${repo}/commits/${commit}`, {
78
+ headers: {
79
+ Authorization: `Token ${token}`,
80
+ Accept: 'application/vnd.github.v3+json',
81
+ },
82
+ });
83
+ if (!response.ok)
84
+ return [];
85
+ const commitData = (await response.json());
86
+ const users = new Set();
87
+ // Parse Co-authored-by trailers from the squash merge commit
88
+ const coAuthorMatches = commitData.commit.message.matchAll(/^Co-authored-by:\s+.+<([^>]+)>/gm);
89
+ for (const match of coAuthorMatches) {
90
+ // GitHub noreply emails contain the username
91
+ const noreplyMatch = match[1].match(/^(\d+\+)?([^@]+)@users\.noreply\.github\.com$/);
92
+ if (noreplyMatch) {
93
+ users.add(noreplyMatch[2]);
94
+ }
95
+ }
96
+ // Remove the main PR author and bots
97
+ if (mainUser)
98
+ users.delete(mainUser);
99
+ for (const user of users) {
100
+ if (user.endsWith('[bot]') || user === 'web-flow') {
101
+ users.delete(user);
102
+ }
103
+ }
104
+ return [...users].map(login => `[@${login}](${serverUrl}/${login})`);
105
+ });
106
+ coAuthorsCache.set(commit, promise);
107
+ return promise;
108
+ }
10
109
  const changelogFunctions = {
11
110
  getDependencyReleaseLine: async (_changesets, dependenciesUpdated, options) => {
12
111
  if (!options.repo) {
@@ -27,10 +126,7 @@ const changelogFunctions = {
27
126
  const links = await (async () => {
28
127
  const commitToFetchFrom = changeset.commit;
29
128
  if (commitToFetchFrom) {
30
- const { links } = await (0, get_github_info_1.getInfo)({
31
- repo: options.repo,
32
- commit: commitToFetchFrom,
33
- });
129
+ const { links } = await getGitHubInfo(options.repo, commitToFetchFrom);
34
130
  return links;
35
131
  }
36
132
  return {
@@ -40,8 +136,18 @@ const changelogFunctions = {
40
136
  };
41
137
  })();
42
138
  const users = links.user;
139
+ // Fetch co-authors from the squash merge commit
140
+ let coAuthors = [];
141
+ if (changeset.commit) {
142
+ const mainUser = links.user ? (links.user.match(/@([\w-]+)/)?.[1] ?? null) : null;
143
+ coAuthors = await getCoAuthors(options.repo, changeset.commit, mainUser);
144
+ }
43
145
  const pullOrCommit = links.pull || links.commit || null;
44
- const userString = users !== null ? `by ${users}` : '';
146
+ const allUsersList = [users, ...coAuthors].filter(Boolean);
147
+ const allUsers = allUsersList.length > 1
148
+ ? `${allUsersList.slice(0, -1).join(', ')} and ${allUsersList[allUsersList.length - 1]}`
149
+ : allUsersList.join('');
150
+ const userString = allUsers ? `by ${allUsers}` : '';
45
151
  const pullString = pullOrCommit !== null ? `with ${pullOrCommit}` : '';
46
152
  const hasUserOrPull = userString && pullString;
47
153
  const entry = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swisspost/design-system-changelog-github",
3
- "version": "10.0.0-next.71",
3
+ "version": "10.0.0-next.75",
4
4
  "description": "A changelog entry generator for GitHub that links to PRs and users",
5
5
  "author": "Swiss Post <design-system@post.ch>",
6
6
  "license": "Apache-2.0",
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "main": "./dist/index.js",
20
20
  "dependencies": {
21
- "@changesets/get-github-info": "0.6.0",
22
- "@changesets/types": "6.0.0",
21
+ "@changesets/get-github-info": "0.8.0",
22
+ "@changesets/types": "6.1.0",
23
23
  "dotenv": "17.2.3"
24
24
  },
25
25
  "devDependencies": {
@@ -27,9 +27,9 @@
27
27
  "@eslint/js": "9.18.0",
28
28
  "eslint": "9.26.0",
29
29
  "globals": "17.3.0",
30
+ "rimraf": "6.0.1",
30
31
  "typescript": "5.9.3",
31
- "typescript-eslint": "8.59.2",
32
- "rimraf": "6.0.1"
32
+ "typescript-eslint": "8.59.2"
33
33
  },
34
34
  "keywords": [
35
35
  "changelog",