Skip to content

Commit 247bbb3

Browse files
BridgeARpabloerhard
authored andcommitted
ci(release): improve release changelog readability and dependency scope (#9044)
1. Render pull request references as explicit `[#1234](…/pull/1234)` links, inline ones included, so GitHub no longer expands each reference into a preview card or adds a back-reference to the linked PR on every release. 2. Drop development and instrumented-library dependency bumps; only the repo root and the bundled `/vendor` tree ship, so the rest is changelog noise. 3. Carry the commit scope on internal entries like the other categories. Section headings switch from bold to Markdown `###` headings with a bold, colon-separated product label, and contributors render as one line of linked avatars.
1 parent de64e52 commit 247bbb3

2 files changed

Lines changed: 221 additions & 93 deletions

File tree

scripts/release/changelog.js

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,22 @@ const CONVENTIONAL_PATTERN = new RegExp(
1313
String.raw`(feat|fix|docs|style|refactor|perf|test|bench|build|ci|chore)(?:\(([^)]+)\))?(!)?: (.+)$`
1414
)
1515
const PULL_REQUEST_PATTERN = /\s+\(#([0-9]+)\)$/
16-
const INTERNAL_TYPES = new Set(['bench', 'build', 'chore', 'ci', 'refactor', 'style', 'test'])
16+
const REFERENCE_PATTERN = /#([0-9]+)/g
17+
const GITHUB_URL = 'https://github.com'
18+
const REPO_URL = `${GITHUB_URL}/DataDog/dd-trace-js`
19+
const UNCATEGORIZED_PRODUCT = 'Other'
20+
const DEPENDENCY_PRODUCT = 'Dependencies'
21+
// Dependabot tags the commit scope `deps-dev` for development dependencies and
22+
// `deps` for production ones, but the `deps` manifests under test/benchmark/docs
23+
// directories are not shipped. The shipped manifests are the repo root and the
24+
// bundled `/vendor` tree; only those (and the matching production groups from
25+
// `.github/dependabot.yml`) reach customers. Keep this set in sync with the
26+
// `dependency-type: "production"` groups there.
27+
const PRODUCTION_DEPENDENCY_GROUPS = new Set([
28+
'runtime-minor-and-patch-dependencies',
29+
'vendor-minor-and-patch-dependencies',
30+
'security-production',
31+
])
1732

1833
const CATEGORY_BY_TYPE = {
1934
docs: 'Documentation',
@@ -120,9 +135,9 @@ for (const [product, scopes] of PRODUCTS) {
120135
* @property {string} category
121136
* @property {string} product
122137
* @property {string} subject
123-
* @property {string} pr
124-
* @property {boolean} internal
138+
* @property {string} pr Bare pull request number, e.g. `8012`, or `''` when absent.
125139
* @property {boolean} revert
140+
* @property {boolean} [drop] Set when the entry is intentionally omitted from the changelog.
126141
* @property {string} [warning]
127142
*/
128143

@@ -139,6 +154,7 @@ function createReleaseChangelog (entries) {
139154
for (const entry of entries) {
140155
const change = parseChange(entry)
141156

157+
if (change.drop) continue
142158
if (change.warning) warnings.push(change.warning)
143159
if (change.category === 'Features' && !change.revert) isMinor = true
144160
if (entry.author) contributors.add(entry.author)
@@ -169,29 +185,51 @@ function parseChange (entry) {
169185
if (!parsed) {
170186
return {
171187
category: INTERNAL_CATEGORY,
172-
product: 'Other',
188+
product: UNCATEGORIZED_PRODUCT,
173189
subject: subjectWithPullRequest.subject,
174190
pr: subjectWithPullRequest.pr,
175-
internal: true,
176191
revert: false,
177192
warning: `Non-conventional release-note subject for ${entry.sha}: ${entry.subject}`,
178193
}
179194
}
180195

181-
const category = CATEGORY_BY_TYPE[parsed.type] || INTERNAL_CATEGORY
182-
const internal = INTERNAL_TYPES.has(parsed.type)
183-
const product = selectProduct(parsed.scopes)
196+
const dependency = classifyDependencyBump(parsed.scopes, parsed.subject)
197+
if (dependency === 'other') {
198+
return { drop: true }
199+
}
184200

185201
return {
186-
category,
187-
product,
202+
category: CATEGORY_BY_TYPE[parsed.type] || INTERNAL_CATEGORY,
203+
product: dependency === 'production' ? DEPENDENCY_PRODUCT : selectProduct(parsed.scopes),
188204
subject: parsed.subject,
189205
pr: subjectWithPullRequest.pr,
190-
internal,
191206
revert: parsed.isRevert,
192207
}
193208
}
194209

210+
/**
211+
* Classify a Dependabot dependency bump as shipped (`production`) or not
212+
* (`other`, dropped from the changelog), or `undefined` when the commit is not
213+
* a dependency bump at all. Development dependencies and the instrumented-library
214+
* support ranges under test/benchmark/docs directories never reach customers.
215+
*
216+
* @param {string[]} scopes
217+
* @param {string} subject
218+
* @returns {'production'|'other'|undefined}
219+
*/
220+
function classifyDependencyBump (scopes, subject) {
221+
if (!scopes.includes('deps') && !scopes.includes('deps-dev')) return
222+
if (scopes.includes('deps-dev')) return 'other'
223+
224+
const directory = subject.match(/\bin (\/\S+)/)
225+
if (directory && directory[1] !== '/vendor') return 'other'
226+
227+
const group = subject.match(/\bthe (\S+) group\b/)
228+
if (group && !PRODUCTION_DEPENDENCY_GROUPS.has(group[1])) return 'other'
229+
230+
return 'production'
231+
}
232+
195233
/**
196234
* @param {string} subject
197235
*/
@@ -203,7 +241,7 @@ function parsePullRequest (subject) {
203241

204242
return {
205243
subject: subject.slice(0, match.index),
206-
pr: `#${match[1]}`,
244+
pr: match[1],
207245
}
208246
}
209247

@@ -299,11 +337,8 @@ function renderMarkdown (sections, contributors) {
299337
}
300338

301339
if (contributors.size > 0) {
302-
lines.push('<b>Contributors</b>')
303-
for (const contributor of [...contributors].sort(compareContributors)) {
304-
lines.push(`- ${contributor}`)
305-
}
306-
lines.push('')
340+
const badges = [...contributors].sort(compareContributors).map(renderContributor)
341+
lines.push('### Contributors', '', badges.join(' '), '')
307342
}
308343

309344
return lines.join('\n')
@@ -314,24 +349,21 @@ function renderMarkdown (sections, contributors) {
314349
*/
315350
function renderHeading (category) {
316351
if (category === INTERNAL_CATEGORY) {
317-
return `<b>${category}</b> (CI, Testing, Benchmarking)`
352+
return `### ${category} (CI, Testing, Benchmarking)`
318353
}
319354

320-
return `<b>${category}</b>`
355+
return `### ${category}`
321356
}
322357

323358
/**
324-
* Groups same-product entries together; the internal section carries no product,
325-
* so it falls through to a plain subject sort.
359+
* Groups same-product entries together, then orders by subject within a product.
326360
*
327361
* @param {Change} a
328362
* @param {Change} b
329363
*/
330364
function compareChanges (a, b) {
331-
if (!a.internal) {
332-
const byProduct = a.product.toLowerCase().localeCompare(b.product.toLowerCase())
333-
if (byProduct !== 0) return byProduct
334-
}
365+
const byProduct = a.product.toLowerCase().localeCompare(b.product.toLowerCase())
366+
if (byProduct !== 0) return byProduct
335367

336368
return a.subject.toLowerCase().localeCompare(b.subject.toLowerCase())
337369
}
@@ -344,16 +376,49 @@ function compareContributors (a, b) {
344376
return a.toLowerCase().localeCompare(b.toLowerCase())
345377
}
346378

379+
/**
380+
* Renders a GitHub avatar that links to the contributor's profile. Display
381+
* strings that are not a `@handle` (a plain git author name) have no profile to
382+
* link, so they render verbatim.
383+
*
384+
* @param {string} contributor
385+
*/
386+
function renderContributor (contributor) {
387+
if (!contributor.startsWith('@')) return contributor
388+
389+
const login = contributor.slice(1)
390+
return `[<img src="${GITHUB_URL}/${login}.png?size=48" width="24" height="24" ` +
391+
`alt="${contributor}" title="${contributor}" />](${GITHUB_URL}/${login})`
392+
}
393+
347394
/**
348395
* @param {Change} change
349396
*/
350397
function renderChange (change) {
351-
const suffix = change.pr ? ` ${change.pr}` : ''
352-
if (change.internal) {
353-
return `- ${change.subject}${suffix}`
398+
const subject = linkifyReferences(change.subject)
399+
const suffix = change.pr ? ` ${renderPullRequest(change.pr)}` : ''
400+
if (change.product === UNCATEGORIZED_PRODUCT) {
401+
return `- ${subject}${suffix}`
354402
}
355403

356-
return `- <b>${change.product}</b> ${change.subject}${suffix}`
404+
return `- **${change.product}:** ${subject}${suffix}`
405+
}
406+
407+
/**
408+
* Wraps inline `#1234` references in an explicit link so GitHub renders them as
409+
* plain links instead of expanding each one into a pull request preview.
410+
*
411+
* @param {string} text
412+
*/
413+
function linkifyReferences (text) {
414+
return text.replaceAll(REFERENCE_PATTERN, (_, number) => renderPullRequest(number))
415+
}
416+
417+
/**
418+
* @param {string} number Bare pull request number.
419+
*/
420+
function renderPullRequest (number) {
421+
return `[#${number}](${REPO_URL}/pull/${number})`
357422
}
358423

359424
module.exports = {

0 commit comments

Comments
 (0)