From 4d13e2b27208d5dac1c7ed4b8d7b78ca798f8416 Mon Sep 17 00:00:00 2001 From: smlambert Date: Mon, 24 Aug 2026 14:53:05 -0400 Subject: [PATCH 01/13] Add groovy wrapper for generating release notes --- generate-release-notes/Jenkinsfile | 182 +++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 generate-release-notes/Jenkinsfile diff --git a/generate-release-notes/Jenkinsfile b/generate-release-notes/Jenkinsfile new file mode 100644 index 0000000..7d83c35 --- /dev/null +++ b/generate-release-notes/Jenkinsfile @@ -0,0 +1,182 @@ +#!groovy + +// Returns true when the tag follows the pre-JEP-322 JDK 8 scheme (e.g. jdk8u502-ga, jdk8u492-b07). +def isJdk8Tag(String tag) { + return tag ==~ /^jdk8u.*/ +} + +// Parses a JEP-322 tag (JDK 9+) and returns a map of its components. +// +// Supported formats: +// jdk-21.0.1+12 – standard release (no PATCH) +// jdk-21.0.12.1+1 – patch release; jdk-21.0.12.1+1 and jdk-21.0.12.1-ga point to the same SHA +// jdk-21.0.12-ga – GA alias for a standard release (no PATCH, no BUILD) +// jdk-21.0.12.1-ga – GA alias for a patch release +// +// Returned map: +// feature – major version number (e.g. "21") +// version – full version string from the tag (e.g. "21.0.12.1" or "21.0.12") +// patch – PATCH segment, "0" when absent (e.g. "1" or "0") +// build – BUILD number after '+', "0" when absent (e.g. "1" or "0") +def parseJdkTag(String tag) { + // 1. Strip leading "jdk-" prefix + def withoutPrefix = tag.replaceFirst(/^jdk-/, '') + + // 2. Strip optional qualifier (e.g. "-ga", "-ea"). A qualifier starts with '-' + // followed by a non-digit character and runs to the end of the string. + def withoutQualifier = withoutPrefix.replaceFirst(/-[a-zA-Z].*$/, '') + + // 3. Split on '+' to separate version from optional build number + def parts = withoutQualifier.tokenize('+') + if (parts.size() < 1 || parts.size() > 2) { + error("JDK_TAG '${tag}' does not match an expected JEP-322 format (jdk-FEATURE.INTERIM.UPDATE[.PATCH][+BUILD][-QUALIFIER])") + } + def build = parts.size() == 2 ? parts[1] : '0' + + // 4. Split version on '.' → [FEATURE, INTERIM, UPDATE] or [FEATURE, INTERIM, UPDATE, PATCH] + def versionParts = parts[0].tokenize('.') + if (versionParts.size() < 3) { + error("JDK_TAG '${tag}' version segment '${parts[0]}' must contain at least FEATURE.INTERIM.UPDATE") + } + def patch = versionParts.size() >= 4 ? versionParts[3] : '0' + // Preserve the full version string as it appears in the tag (3-part or 4-part) + def version = parts[0] + + return [ + feature: versionParts[0], + version: version, + patch : patch, + build : build, + ] +} + +pipeline { + agent { label 'master' } + parameters { + string(name: 'JDK_TAG', defaultValue: '', description: 'Required. The JDK release tag, e.g. jdk-21.0.1+12 or jdk-21.0.12.1-ga (JEP-322 format: jdk-FEATURE.INTERIM.UPDATE[.PATCH][+BUILD][-QUALIFIER])') + string(name: 'BASE_JDK_TAG', defaultValue: '', description: 'Required. The base (previous) JDK tag to compare against, e.g. jdk-21.0.0+35 or jdk-21.0.12-ga') + string(name: 'GITHUB_REPOSITORY', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). GitHub repository, e.g. adoptium/21u or adoptium/jdk8u') + string(name: 'JDK_VERSION', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). Version string for fetchReleaseNotes.js, e.g. 21.0.12.1 or openjdk8u462,8u501') + string(name: 'FILENAME', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). Output filename, e.g. OpenJDK21-jdk-release-notes_21.0.12.1_1.json or OpenJDK8U-jdk-release-notes_8.0.502_07.json') + } + stages { + stage('Validate and Resolve Parameters') { + steps { + script { + if (!params.JDK_TAG) { + error('JDK_TAG parameter is required') + } + if (!params.BASE_JDK_TAG) { + error('BASE_JDK_TAG parameter is required') + } + + if (isJdk8Tag(params.JDK_TAG)) { + // JDK 8 uses a pre-JEP-322 versioning scheme (e.g. jdk8u502-ga). + // The derived values for GITHUB_REPOSITORY, JDK_VERSION and FILENAME + // cannot be calculated reliably from the tag alone (the build number + // embedded in the filename requires a GitHub tag→SHA lookup), so all + // three must be provided explicitly. + if (!params.GITHUB_REPOSITORY) { + error('GITHUB_REPOSITORY is required for JDK 8 tags (e.g. adoptium/jdk8u)') + } + if (!params.JDK_VERSION) { + error('JDK_VERSION is required for JDK 8 tags (e.g. openjdk8u462,8u501)') + } + if (!params.FILENAME) { + error('FILENAME is required for JDK 8 tags (e.g. OpenJDK8U-jdk-release-notes_8.0.502_07.json)') + } + env.RESOLVED_GITHUB_REPOSITORY = params.GITHUB_REPOSITORY + env.RESOLVED_JDK_VERSION = params.JDK_VERSION + env.RESOLVED_FILENAME = params.FILENAME + + echo "JDK_TAG: ${params.JDK_TAG} (JDK 8 — all parameters required)" + echo "BASE_JDK_TAG: ${params.BASE_JDK_TAG}" + echo "GITHUB_REPOSITORY: ${env.RESOLVED_GITHUB_REPOSITORY}" + echo "JDK_VERSION: ${env.RESOLVED_JDK_VERSION}" + echo "FILENAME: ${env.RESOLVED_FILENAME}" + } else { + def parsed = parseJdkTag(params.JDK_TAG) + + // For +BUILD tags use build as the suffix. + // For -ga tags (build == '0') fall back to patch (e.g. jdk-21.0.12.1-ga → _1). + def filenameSuffix = (parsed.build != '0') ? parsed.build : parsed.patch + + // Resolve optional parameters: use the supplied value when non-empty, + // otherwise derive from JDK_TAG. The ?: operator returns the left-hand + // side when it is truthy (non-null, non-empty), so an explicit param + // always wins over the calculated default. + env.RESOLVED_GITHUB_REPOSITORY = params.GITHUB_REPOSITORY ?: "adoptium/${parsed.feature}u" + env.RESOLVED_JDK_VERSION = params.JDK_VERSION ?: parsed.version + env.RESOLVED_FILENAME = params.FILENAME ?: "OpenJDK${parsed.feature}-jdk-release-notes_${parsed.version}_${filenameSuffix}.json" + + echo "JDK_TAG: ${params.JDK_TAG}" + echo "BASE_JDK_TAG: ${params.BASE_JDK_TAG}" + echo "GITHUB_REPOSITORY: ${env.RESOLVED_GITHUB_REPOSITORY}${params.GITHUB_REPOSITORY ? ' (provided)' : ' (derived)'}" + echo "JDK_VERSION: ${env.RESOLVED_JDK_VERSION}${params.JDK_VERSION ? ' (provided)' : ' (derived)'}" + echo "FILENAME: ${env.RESOLVED_FILENAME}${params.FILENAME ? ' (provided)' : ' (derived)'}" + } + } + } + } + stage('Checkout') { + steps { + cleanWs() + checkout scm + } + } + stage('Install Dependencies') { + steps { + dir('generate-release-notes/generate-release-notes') { + sh 'node --version' + sh 'npm install' + } + } + } + stage('Fetch Commit List') { + steps { + dir('generate-release-notes/generate-release-notes') { + sh """ + echo "Generating release notes for ${params.JDK_TAG}" + node ./fetchCommitList.js \ + --repository ${env.RESOLVED_GITHUB_REPOSITORY} \ + --baseTag ${params.BASE_JDK_TAG} \ + --tag ${params.JDK_TAG} \ + --filename ${params.JDK_TAG}-commits.json + """ + sh "cat ${params.JDK_TAG}-commits.json" + } + } + } + stage('Fetch Release Notes') { + steps { + dir('generate-release-notes/generate-release-notes') { + sh """ + node ./fetchReleaseNotes.js \ + --commitList ./${params.JDK_TAG}-commits.json \ + --filename ${env.RESOLVED_FILENAME} \ + --version ${env.RESOLVED_JDK_VERSION} + """ + } + } + } + stage('Archive Artifacts') { + steps { + dir('generate-release-notes/generate-release-notes') { + archiveArtifacts artifacts: "${params.JDK_TAG}-commits.json, ${env.RESOLVED_FILENAME}", + fingerprint: true + } + } + } + } + post { + failure { + echo "Release notes generation failed for ${params.JDK_TAG}" + } + success { + echo "Release notes successfully generated: ${env.RESOLVED_FILENAME}" + } + cleanup { + cleanWs() + } + } +} From 936ed5c921f0a57e18dc59de71747005e78b1428 Mon Sep 17 00:00:00 2001 From: smlambert Date: Wed, 26 Aug 2026 13:21:46 -0400 Subject: [PATCH 02/13] Add generate-release-notes Groovy pipeline wrapper --- generate-release-notes/Jenkinsfile | 115 ++++++++++++++---- .../lib/fetchGitHubCommits.js | 5 +- 2 files changed, 95 insertions(+), 25 deletions(-) diff --git a/generate-release-notes/Jenkinsfile b/generate-release-notes/Jenkinsfile index 7d83c35..61d6429 100644 --- a/generate-release-notes/Jenkinsfile +++ b/generate-release-notes/Jenkinsfile @@ -1,5 +1,16 @@ #!groovy +// Workaround to handle different versions of the Badge/Groovy Postbuild plugin. +def appendSummaryText(summary, text) { + try { + def currentText = summary.getText() ?: '' + summary.setText(currentText + text) + } catch (Exception e) { + echo "setText failed, trying deprecated appendText: ${e.message}" + summary.appendText(text, false) + } +} + // Returns true when the tag follows the pre-JEP-322 JDK 8 scheme (e.g. jdk8u502-ga, jdk8u492-b07). def isJdk8Tag(String tag) { return tag ==~ /^jdk8u.*/ @@ -8,8 +19,9 @@ def isJdk8Tag(String tag) { // Parses a JEP-322 tag (JDK 9+) and returns a map of its components. // // Supported formats: -// jdk-21.0.1+12 – standard release (no PATCH) -// jdk-21.0.12.1+1 – patch release; jdk-21.0.12.1+1 and jdk-21.0.12.1-ga point to the same SHA +// jdk-26-ga - Feature release (first introduction of a new JDK version) +// jdk-21.0.1+12 – CPU release (no PATCH) +// jdk-21.0.12.1+1 – CSPU release; jdk-21.0.12.1+1 and jdk-21.0.12.1-ga point to the same SHA // jdk-21.0.12-ga – GA alias for a standard release (no PATCH, no BUILD) // jdk-21.0.12.1-ga – GA alias for a patch release // @@ -51,11 +63,11 @@ def parseJdkTag(String tag) { } pipeline { - agent { label 'master' } + agent { label 'worker' } parameters { - string(name: 'JDK_TAG', defaultValue: '', description: 'Required. The JDK release tag, e.g. jdk-21.0.1+12 or jdk-21.0.12.1-ga (JEP-322 format: jdk-FEATURE.INTERIM.UPDATE[.PATCH][+BUILD][-QUALIFIER])') + string(name: 'JDK_TAG', defaultValue: '', description: 'Required. The JDK release tag, e.g. jdk-21.0.1+12 or jdk-21.0.12.1-ga. Needs to match the tag used for building and publishing binaries.') string(name: 'BASE_JDK_TAG', defaultValue: '', description: 'Required. The base (previous) JDK tag to compare against, e.g. jdk-21.0.0+35 or jdk-21.0.12-ga') - string(name: 'GITHUB_REPOSITORY', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). GitHub repository, e.g. adoptium/21u or adoptium/jdk8u') + string(name: 'GITHUB_REPOSITORY', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). GitHub repository, e.g. adoptium/jdk21u or adoptium/jdk8u') string(name: 'JDK_VERSION', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). Version string for fetchReleaseNotes.js, e.g. 21.0.12.1 or openjdk8u462,8u501') string(name: 'FILENAME', defaultValue: '', description: 'Optional (JDK 9+, required for JDK 8). Output filename, e.g. OpenJDK21-jdk-release-notes_21.0.12.1_1.json or OpenJDK8U-jdk-release-notes_8.0.502_07.json') } @@ -105,7 +117,7 @@ pipeline { // otherwise derive from JDK_TAG. The ?: operator returns the left-hand // side when it is truthy (non-null, non-empty), so an explicit param // always wins over the calculated default. - env.RESOLVED_GITHUB_REPOSITORY = params.GITHUB_REPOSITORY ?: "adoptium/${parsed.feature}u" + env.RESOLVED_GITHUB_REPOSITORY = params.GITHUB_REPOSITORY ?: "adoptium/jdk${parsed.feature}u" env.RESOLVED_JDK_VERSION = params.JDK_VERSION ?: parsed.version env.RESOLVED_FILENAME = params.FILENAME ?: "OpenJDK${parsed.feature}-jdk-release-notes_${parsed.version}_${filenameSuffix}.json" @@ -127,35 +139,53 @@ pipeline { stage('Install Dependencies') { steps { dir('generate-release-notes/generate-release-notes') { - sh 'node --version' - sh 'npm install' + nvm(version: 'v24.19.0', + nvmInstallURL: 'https://raw.githubusercontent.com/creationix/nvm/v0.40.7/install.sh', + nvmNodeJsOrgMirror: 'https://nodejs.org/dist', + nvmIoJsOrgMirror: 'https://iojs.org/dist', + nvmInstallDir: '$HOME/.nvm') { + sh 'node --version' + sh 'npm install' + } } } } stage('Fetch Commit List') { steps { dir('generate-release-notes/generate-release-notes') { - sh """ - echo "Generating release notes for ${params.JDK_TAG}" - node ./fetchCommitList.js \ - --repository ${env.RESOLVED_GITHUB_REPOSITORY} \ - --baseTag ${params.BASE_JDK_TAG} \ - --tag ${params.JDK_TAG} \ - --filename ${params.JDK_TAG}-commits.json - """ - sh "cat ${params.JDK_TAG}-commits.json" + nvm(version: 'v24.19.0', + nvmInstallURL: 'https://raw.githubusercontent.com/creationix/nvm/v0.40.7/install.sh', + nvmNodeJsOrgMirror: 'https://nodejs.org/dist', + nvmIoJsOrgMirror: 'https://iojs.org/dist', + nvmInstallDir: '$HOME/.nvm') { + sh """ + echo "Generating release notes for ${params.JDK_TAG}" + node ./fetchCommitList.js \ + --repository ${env.RESOLVED_GITHUB_REPOSITORY} \ + --baseTag ${params.BASE_JDK_TAG} \ + --tag ${params.JDK_TAG} \ + --filename ${params.JDK_TAG}-commits.json + """ + sh "cat ${params.JDK_TAG}-commits.json" + } } } } stage('Fetch Release Notes') { steps { dir('generate-release-notes/generate-release-notes') { - sh """ - node ./fetchReleaseNotes.js \ - --commitList ./${params.JDK_TAG}-commits.json \ - --filename ${env.RESOLVED_FILENAME} \ - --version ${env.RESOLVED_JDK_VERSION} - """ + nvm(version: 'v24.19.0', + nvmInstallURL: 'https://raw.githubusercontent.com/creationix/nvm/v0.40.7/install.sh', + nvmNodeJsOrgMirror: 'https://nodejs.org/dist', + nvmIoJsOrgMirror: 'https://iojs.org/dist', + nvmInstallDir: '$HOME/.nvm') { + sh """ + node ./fetchReleaseNotes.js \ + --commitList ./${params.JDK_TAG}-commits.json \ + --filename ${env.RESOLVED_FILENAME} \ + --version ${env.RESOLVED_JDK_VERSION} + """ + } } } } @@ -173,7 +203,44 @@ pipeline { echo "Release notes generation failed for ${params.JDK_TAG}" } success { - echo "Release notes successfully generated: ${env.RESOLVED_FILENAME}" + script { + echo "Release notes successfully generated: ${env.RESOLVED_FILENAME}" + + // Build a pre-populated link to the release-tool publish job so the operator + // can publish the generated JSON with a single click. + // Mirrors the parambuild pattern used in ci-jenkins-pipelines/build_base_file.groovy. + def publishJobPath = 'build-scripts/release/refactor_openjdk_release_tool' + def releaseToolUrl = "${env.JENKINS_URL}job/${publishJobPath.replace('/', '/job/')}/parambuild?" + + // Derive the JDK major version for the VERSION parameter (e.g. "jdk21"). + def versionParam + if (isJdk8Tag(params.JDK_TAG)) { + versionParam = 'jdk8' + } else { + def parsed = parseJdkTag(params.JDK_TAG) + versionParam = "jdk${parsed.feature}" + } + + def encodedJobName = URLEncoder.encode(env.JOB_NAME, 'UTF-8') + def encodedArtifacts = URLEncoder.encode("**/${env.RESOLVED_FILENAME}", 'UTF-8') + def encodedTag = URLEncoder.encode(params.JDK_TAG, 'UTF-8') + + releaseToolUrl += "VERSION=${versionParam}" + releaseToolUrl += "&TAG=${encodedTag}" + releaseToolUrl += "&UPSTREAM_JOB_NAME=${encodedJobName}" + releaseToolUrl += "&UPSTREAM_JOB_NUMBER=${currentBuild.number}" + releaseToolUrl += "&ARTIFACTS_TO_COPY=${encodedArtifacts}" + releaseToolUrl += "&RELEASE=true" + releaseToolUrl += "&DRY_RUN=false" + + echo "Publish release notes — click to trigger: ${releaseToolUrl}" + + // Add a clickable summary badge to the Jenkins build page, matching the + // pattern used in ci-jenkins-pipelines/build_base_file.groovy. + def summary = manager.createSummary('document.svg') + appendSummaryText(summary, "Release notes generated: ${env.RESOLVED_FILENAME}
") + appendSummaryText(summary, "Publish release notes for ${params.JDK_TAG}") + } } cleanup { cleanWs() diff --git a/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js b/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js index c62ee06..019ce19 100644 --- a/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js +++ b/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js @@ -15,7 +15,10 @@ export const fetchCommits = async ({ console.log(`Fetching commits from ${githubQuery}`); const githubResponse = await fetch(githubQuery); if (!githubResponse.ok) { - throw new Error(`Failed to fetch commits from ${githubQuery}, status: ${githubResponse.status}`); + const hint = githubResponse.status === 404 + ? ` Check that the repository exists and both tags (${baseTag} and ${tag}) are present in it.` + : ''; + throw new Error(`Failed to fetch commits from ${githubQuery}, status: ${githubResponse.status}.${hint}`); } const githubResponseJson = await githubResponse.json(); From 13073404b94e7032e378647d6933c045b93dbadc Mon Sep 17 00:00:00 2001 From: smlambert Date: Wed, 26 Aug 2026 13:21:56 -0400 Subject: [PATCH 03/13] fix #200 --- .../generate-release-notes/fetchReleaseNotes.js | 11 ++++++++++- .../generate-release-notes/lib/fetchJiraIssues.js | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js index 6245e24..2d4ce0c 100755 --- a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js +++ b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js @@ -46,10 +46,19 @@ for (const commit of commits) { priority: null, component: null, subcomponent: null, - link: `https://bugs.openjdk.java.net/browse/${commit.id}`, + link: `https://bugs.openjdk.org/browse/${commit.id}`, type: null, backportOf: null, }; + } else if (releaseNote.type === 'Backport' && releaseNote.backportOf) { + // For backport issues, use the master bug ID and link so that the release + // notes refer to the canonical bug with a description rather than the + // backport ticket which is usually empty. + releaseNote = { + ...releaseNote, + id: releaseNote.backportOf, + link: `https://bugs.openjdk.org/browse/${releaseNote.backportOf}`, + }; } output.push(releaseNote); diff --git a/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js b/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js index 7ec3124..5d8b7ac 100644 --- a/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js +++ b/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js @@ -1,7 +1,7 @@ // Description: Fetches the JIRA issues from bugs.openjdk.org export default async function fetchReleaseNotes(version) { // fetch the release notes from the bugs.openjdk.org - const baseUrl = 'https://bugs.openjdk.java.net/rest/api/2/search?jql='; + const baseUrl = 'https://bugs.openjdk.org/rest/api/2/search?jql='; const jql = `project=JDK AND (status in (Closed, Resolved)) AND (resolution not in ("Won't Fix", "Duplicate", "Cannot Reproduce", "Not an Issue", "Withdrawn")) AND (labels not in (release-note, openjdk-na) OR labels is EMPTY) @@ -38,7 +38,7 @@ export default async function fetchReleaseNotes(version) { priority: issue.fields.priority.id, component: issue.fields.components[0].name, subcomponent: `${issue.fields.components[0].name}${issue.fields.customfield_10008?.name ? `/${issue.fields.customfield_10008?.name}` : ''}`, - link: `https://bugs.openjdk.java.net/browse/${issue.key}`, + link: `https://bugs.openjdk.org/browse/${issue.key}`, type: issue.fields.issuetype.name, backportOf: parent || null, }); From 5b31dd7c49548fe7bb432d7ad14f7f8e91e5b999 Mon Sep 17 00:00:00 2001 From: smlambert Date: Wed, 26 Aug 2026 14:05:29 -0400 Subject: [PATCH 04/13] Assisted-by: IBM Bob v2.0.3 --- generate-release-notes/Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/generate-release-notes/Jenkinsfile b/generate-release-notes/Jenkinsfile index 61d6429..3445f52 100644 --- a/generate-release-notes/Jenkinsfile +++ b/generate-release-notes/Jenkinsfile @@ -1,5 +1,6 @@ #!groovy +// Created with the assistance of IBM Bob v2.0.3 // Workaround to handle different versions of the Badge/Groovy Postbuild plugin. def appendSummaryText(summary, text) { try { From 8c54f31bfb575f1ae288be89973db265815a6006 Mon Sep 17 00:00:00 2001 From: smlambert Date: Tue, 1 Sep 2026 15:28:36 -0400 Subject: [PATCH 05/13] Use backportedBy Signed-off-by: smlambert --- .../generate-release-notes/fetchReleaseNotes.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js index 2d4ce0c..3f64925 100755 --- a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js +++ b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js @@ -54,10 +54,14 @@ for (const commit of commits) { // For backport issues, use the master bug ID and link so that the release // notes refer to the canonical bug with a description rather than the // backport ticket which is usually empty. + const canonicalId = releaseNote.backportOf; + const { type, backportOf, ...rest } = releaseNote; releaseNote = { - ...releaseNote, - id: releaseNote.backportOf, - link: `https://bugs.openjdk.org/browse/${releaseNote.backportOf}`, + ...rest, + id: canonicalId, + link: `https://bugs.openjdk.org/browse/${canonicalId}`, + type: null, + backportedBy: commit.id, }; } From 4282915f9923408ae2dc2f4ffca72865ae964b19 Mon Sep 17 00:00:00 2001 From: smlambert Date: Tue, 1 Sep 2026 15:54:25 -0400 Subject: [PATCH 06/13] Keep backportOf for all objects Signed-off-by: smlambert --- .../generate-release-notes/fetchReleaseNotes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js index 3f64925..8660468 100755 --- a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js +++ b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js @@ -49,18 +49,19 @@ for (const commit of commits) { link: `https://bugs.openjdk.org/browse/${commit.id}`, type: null, backportOf: null, + backportedBy: null, }; } else if (releaseNote.type === 'Backport' && releaseNote.backportOf) { // For backport issues, use the master bug ID and link so that the release // notes refer to the canonical bug with a description rather than the // backport ticket which is usually empty. const canonicalId = releaseNote.backportOf; - const { type, backportOf, ...rest } = releaseNote; releaseNote = { - ...rest, + ...releaseNote, id: canonicalId, link: `https://bugs.openjdk.org/browse/${canonicalId}`, type: null, + backportOf: null, backportedBy: commit.id, }; } From f73a8dc9cd0b4d44ceca35708dc62d0b71af3046 Mon Sep 17 00:00:00 2001 From: Shelley Lambert Date: Wed, 2 Sep 2026 16:46:57 -0400 Subject: [PATCH 07/13] Fix backportedBy value Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../generate-release-notes/fetchReleaseNotes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js index 8660468..b2244a4 100755 --- a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js +++ b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js @@ -56,14 +56,17 @@ for (const commit of commits) { // notes refer to the canonical bug with a description rather than the // backport ticket which is usually empty. const canonicalId = releaseNote.backportOf; + const backportIssueId = releaseNote.id; releaseNote = { ...releaseNote, id: canonicalId, link: `https://bugs.openjdk.org/browse/${canonicalId}`, type: null, backportOf: null, - backportedBy: commit.id, + backportedBy: backportIssueId, }; + } else { + releaseNote = { ...releaseNote, backportedBy: null }; } output.push(releaseNote); From 2a83f3e985538f68d28d16ab49921e179aa1fe15 Mon Sep 17 00:00:00 2001 From: smlambert Date: Wed, 14 Oct 2026 20:50:20 -0400 Subject: [PATCH 08/13] Update as per review request Signed-off-by: smlambert --- generate-release-notes/Jenkinsfile | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/generate-release-notes/Jenkinsfile b/generate-release-notes/Jenkinsfile index 3445f52..bf208f1 100644 --- a/generate-release-notes/Jenkinsfile +++ b/generate-release-notes/Jenkinsfile @@ -159,15 +159,21 @@ pipeline { nvmNodeJsOrgMirror: 'https://nodejs.org/dist', nvmIoJsOrgMirror: 'https://iojs.org/dist', nvmInstallDir: '$HOME/.nvm') { - sh """ - echo "Generating release notes for ${params.JDK_TAG}" - node ./fetchCommitList.js \ - --repository ${env.RESOLVED_GITHUB_REPOSITORY} \ - --baseTag ${params.BASE_JDK_TAG} \ - --tag ${params.JDK_TAG} \ - --filename ${params.JDK_TAG}-commits.json - """ - sh "cat ${params.JDK_TAG}-commits.json" + withEnv([ + "JDK_TAG=${params.JDK_TAG}", + "BASE_JDK_TAG=${params.BASE_JDK_TAG}", + "GITHUB_REPO=${env.RESOLVED_GITHUB_REPOSITORY}" + ]) { + sh ''' + echo "Generating release notes for $JDK_TAG" + node ./fetchCommitList.js \ + --repository "$GITHUB_REPO" \ + --baseTag "$BASE_JDK_TAG" \ + --tag "$JDK_TAG" \ + --filename "${JDK_TAG}-commits.json" + ''' + sh 'cat "${JDK_TAG}-commits.json"' + } } } } From 0f15a0780694a6e1c61f09ad27e91e80fb17bb04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:11:51 -0400 Subject: [PATCH 09/13] Bump actions/setup-java from 5.7.0 to 6.0.0 (#230) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.7.0 to 6.0.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/b6effb05e454b25005698d916606bdc6ffcbf961...dd06d9cba3e5552c54d9f8ea23572deb30010f7c) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 323ab9f..859fe65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 with: distribution: 'temurin' java-version: '21' From ca6e260ce61399337432131fbe1653fced1922ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:12:15 -0400 Subject: [PATCH 10/13] Bump js-yaml in /generate-release-notes/generate-release-notes (#231) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...4.3.2) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../generate-release-notes/package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/generate-release-notes/generate-release-notes/package-lock.json b/generate-release-notes/generate-release-notes/package-lock.json index ad620c0..95bb945 100644 --- a/generate-release-notes/generate-release-notes/package-lock.json +++ b/generate-release-notes/generate-release-notes/package-lock.json @@ -1262,9 +1262,9 @@ } }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -1276,6 +1276,7 @@ "url": "https://github.com/sponsors/nodeca" } ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2871,9 +2872,9 @@ "dev": true }, "js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "requires": { "argparse": "^2.0.1" From 91fa8dd8d9b9fa1293d97972391ab5657b9d10f1 Mon Sep 17 00:00:00 2001 From: Scott Fryer <60462088+steelhead31@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:51:36 +0100 Subject: [PATCH 11/13] Fix Release Note Generation Rate Limiting (#234) --- .../lib/fetchGitHubCommits.js | 6 +- .../lib/fetchJiraIssues.js | 68 +++++++++- .../test/extractJDKIssuesTest.js | 2 +- .../test/fetchGitHubCommitsTest.js | 51 +++++++- .../test/fetchJiraIssuesTest.js | 117 +++++++++++++++--- 5 files changed, 215 insertions(+), 29 deletions(-) diff --git a/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js b/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js index 019ce19..faccad4 100644 --- a/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js +++ b/generate-release-notes/generate-release-notes/lib/fetchGitHubCommits.js @@ -9,11 +9,13 @@ export const fetchCommits = async ({ let nextPage = true; let commits = []; - console.log(`Fetching commits for ${repository} between ${baseTag} and ${tag}...`); + const token = process.env.GITHUB_TOKEN; + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + console.log(`Fetching commits for ${repository} between ${baseTag} and ${tag} (${token ? 'authenticated' : 'unauthenticated'})...`); while (nextPage) { const githubQuery = `https://api.github.com/repos/${repository}/compare/${baseTag}...${tag}?per_page=${pageSize}&page=${page}`; console.log(`Fetching commits from ${githubQuery}`); - const githubResponse = await fetch(githubQuery); + const githubResponse = await fetch(githubQuery, { headers }); if (!githubResponse.ok) { const hint = githubResponse.status === 404 ? ` Check that the repository exists and both tags (${baseTag} and ${tag}) are present in it.` diff --git a/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js b/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js index 5d8b7ac..b85bbf7 100644 --- a/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js +++ b/generate-release-notes/generate-release-notes/lib/fetchJiraIssues.js @@ -1,22 +1,78 @@ // Description: Fetches the JIRA issues from bugs.openjdk.org -export default async function fetchReleaseNotes(version) { + +const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function fetchJiraJson(url, maxRetries = 5) { + const headers = { + Accept: 'application/json', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + }; + + for (let attempt = 1; attempt <= maxRetries; attempt += 1) { + try { + const response = await fetch(url, { headers, signal: AbortSignal.timeout(60000) }); + if (!response.ok) { + if (response.status === 429 || response.status >= 500) { + if (attempt < maxRetries) { + const backoff = Math.min(attempt * 10000, 60000); + console.warn(`Jira API HTTP ${response.status}. Retrying in ${backoff / 1000}s (attempt ${attempt}/${maxRetries})...`); + await delay(backoff); + continue; + } + } + throw new Error(`Jira API request failed: HTTP ${response.status} for ${url}`); + } + const contentType = response.headers.get('content-type') || ''; + if (!contentType.includes('application/json')) { + const body = await response.text(); + if (attempt < maxRetries) { + const backoff = Math.min(attempt * 10000, 60000); + console.warn(`Jira API returned non-JSON response (content-type: ${contentType}). Retrying in ${backoff / 1000}s (attempt ${attempt}/${maxRetries})...`); + await delay(backoff); + continue; + } + throw new Error(`Jira API returned non-JSON response (content-type: ${contentType}) for ${url}. Body starts with: ${body.slice(0, 200)}`); + } + return await response.json(); + } catch (err) { + if (attempt < maxRetries && (err.name === 'TimeoutError' || err.name === 'AbortError' || err.code === 'UND_ERR_CONNECT_TIMEOUT')) { + const backoff = Math.min(attempt * 10000, 60000); + console.warn(`Jira API request timed out. Retrying in ${backoff / 1000}s (attempt ${attempt}/${maxRetries})...`); + await delay(backoff); + continue; + } + throw err; + } + } +} + +export default async function fetchReleaseNotes(version, maxRetries = 5) { // fetch the release notes from the bugs.openjdk.org const baseUrl = 'https://bugs.openjdk.org/rest/api/2/search?jql='; const jql = `project=JDK AND (status in (Closed, Resolved)) AND (resolution not in ("Won't Fix", "Duplicate", "Cannot Reproduce", "Not an Issue", "Withdrawn")) AND (labels not in (release-note, openjdk-na) OR labels is EMPTY) AND (summary !~ "release note") AND (issuetype != CSR) AND (fixVersion in (${version}))`; + const encodedJql = encodeURIComponent(jql); + const fields = 'summary,priority,components,customfield_10008,issuetype,issuelinks'; + const pageSize = 100; + const pageDelayMs = 2500; + // execute the initial fetch to get the total number of issues - const totalQuery = await fetch(`${baseUrl + jql}&startAt=1&maxResults=1`); - const initialRes = await totalQuery.json(); + const initialRes = await fetchJiraJson(`${baseUrl + encodedJql}&startAt=0&maxResults=1&fields=summary`, maxRetries); const { total } = initialRes; + console.log(`Found ${total} JIRA issues for JDK ${version}. Fetching in pages of ${pageSize}...`); + const JIRA_ISSUES = []; // fetch all the issues by page - for (let startAt = 0; startAt <= total + 50; startAt += 50) { - const query = await fetch(`${baseUrl + jql}&startAt=${startAt}&maxResults=50`); - const pageRes = await query.json(); + for (let startAt = 0; startAt < total; startAt += pageSize) { + if (startAt > 0 && pageDelayMs > 0) { + await delay(pageDelayMs); + } + console.log(`Fetching JIRA issues ${startAt + 1}-${Math.min(startAt + pageSize, total)} of ${total}...`); + const pageRes = await fetchJiraJson(`${baseUrl + encodedJql}&startAt=${startAt}&maxResults=${pageSize}&fields=${fields}`, maxRetries); pageRes.issues.forEach((issue) => { let parent = ''; diff --git a/generate-release-notes/generate-release-notes/test/extractJDKIssuesTest.js b/generate-release-notes/generate-release-notes/test/extractJDKIssuesTest.js index 4eb040e..c4da5b6 100644 --- a/generate-release-notes/generate-release-notes/test/extractJDKIssuesTest.js +++ b/generate-release-notes/generate-release-notes/test/extractJDKIssuesTest.js @@ -1,7 +1,7 @@ import assert from 'assert'; import { test } from 'node:test'; import extractJDKIssues from '../lib/extractJDKIssues.js'; -import mockAPIResponse from './mock/mockAPIResponse.json' assert { type: 'json' }; +import mockAPIResponse from './mock/mockAPIResponse.json' with { type: 'json' }; test('extractJDKIssues', async (t) => { diff --git a/generate-release-notes/generate-release-notes/test/fetchGitHubCommitsTest.js b/generate-release-notes/generate-release-notes/test/fetchGitHubCommitsTest.js index 42afe90..4552a69 100644 --- a/generate-release-notes/generate-release-notes/test/fetchGitHubCommitsTest.js +++ b/generate-release-notes/generate-release-notes/test/fetchGitHubCommitsTest.js @@ -1,14 +1,14 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { fetchCommits } from '../lib/fetchGitHubCommits.js'; -import mockAPIResponse from './mock/mockAPIResponse.json' assert { type: 'json' }; +import mockAPIResponse from './mock/mockAPIResponse.json' with { type: 'json' }; const repository = 'adoptium/jdk17u' const baseTag = 'jdk-17.0.5-ga'; const tag = 'jdk-17.0.6+9'; test('fetchGithubCommits', async (t) => { - const commits = await fetchCommits( { repository, baseTag, tag }); + const commits = await fetchCommits({ repository, baseTag, tag }); await t.test('returns correct number of commits', (t) => { assert.strictEqual(commits.length, mockAPIResponse.total_commits); @@ -18,4 +18,51 @@ test('fetchGithubCommits', async (t) => { assert.strictEqual(commits[0].sha, mockAPIResponse.commits[0].sha); assert.strictEqual(commits[0].commit.message, mockAPIResponse.commits[0].commit.message); }); +}); + +test('fetchGithubCommits sends Authorization header when GITHUB_TOKEN is set', async () => { + const originalFetch = globalThis.fetch; + let capturedHeaders; + + globalThis.fetch = async (url, init) => { + capturedHeaders = init?.headers ?? {}; + return { + ok: true, + headers: { get: () => null }, + json: async () => ({ commits: [mockAPIResponse.commits[0]] }), + }; + }; + + process.env.GITHUB_TOKEN = 'test-token'; + + try { + await fetchCommits({ repository, baseTag, tag }); + assert.strictEqual(capturedHeaders.Authorization, 'Bearer test-token'); + } finally { + globalThis.fetch = originalFetch; + delete process.env.GITHUB_TOKEN; + } +}); + +test('fetchGithubCommits omits Authorization header when GITHUB_TOKEN is not set', async () => { + const originalFetch = globalThis.fetch; + let capturedHeaders; + + globalThis.fetch = async (url, init) => { + capturedHeaders = init?.headers ?? {}; + return { + ok: true, + headers: { get: () => null }, + json: async () => ({ commits: [mockAPIResponse.commits[0]] }), + }; + }; + + delete process.env.GITHUB_TOKEN; + + try { + await fetchCommits({ repository, baseTag, tag }); + assert.strictEqual(capturedHeaders.Authorization, undefined); + } finally { + globalThis.fetch = originalFetch; + } }); \ No newline at end of file diff --git a/generate-release-notes/generate-release-notes/test/fetchJiraIssuesTest.js b/generate-release-notes/generate-release-notes/test/fetchJiraIssuesTest.js index ed647e0..4048a88 100644 --- a/generate-release-notes/generate-release-notes/test/fetchJiraIssuesTest.js +++ b/generate-release-notes/generate-release-notes/test/fetchJiraIssuesTest.js @@ -2,29 +2,110 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import fetchJiraIssues from '../lib/fetchJiraIssues.js'; -test('fetchReleaseNotes', async (t) => { - const version = '17.0.6'; +const mockIssue = { + key: 'JDK-8280124', + fields: { + summary: 'Reduce branches decoding latin-1 chars', + priority: { id: '3' }, + components: [{ name: 'core-libs' }], + customfield_10008: { name: 'java.lang' }, + issuetype: { name: 'Backport' }, + issuelinks: [ + { + type: { name: 'Backport' }, + inwardIssue: { key: 'JDK-8280123' }, + }, + ], + }, +}; - const issues = await fetchJiraIssues(version); +test('fetchReleaseNotes', async (t) => { + const originalFetch = globalThis.fetch; - await t.test('returns an array', () => { - assert.ok(Array.isArray(issues), 'should return an array'); + globalThis.fetch = async (url) => ({ + ok: true, + status: 200, + headers: { get: () => 'application/json;charset=UTF-8' }, + json: async () => { + if (url.includes('startAt=0') && url.includes('maxResults=1&')) { + return { total: 1 }; + } + if (url.includes('startAt=0')) { + return { total: 1, issues: [mockIssue] }; + } + return { total: 1, issues: [] }; + }, }); - await t.test('first issue extracts expected values', () => { - if (issues.length > 0) { + try { + const version = '17.0.6'; + const issues = await fetchJiraIssues(version); + + await t.test('returns an array', () => { + assert.ok(Array.isArray(issues), 'should return an array'); + assert.strictEqual(issues.length, 1); + }); + + await t.test('first issue extracts expected values', () => { const firstIssue = issues[0]; - assert.ok(typeof firstIssue.id === 'string', 'issue should have an id'); - assert.ok(typeof firstIssue.title === 'string', 'issue should have a title'); - assert.ok(typeof firstIssue.priority === 'string', 'issue should have a priority'); - assert.ok(typeof firstIssue.component === 'string', 'issue should have a component'); - assert.ok(typeof firstIssue.subcomponent === 'string', 'issue should have a subcomponent'); - assert.ok(typeof firstIssue.link === 'string', 'issue should have a link'); - assert.ok(typeof firstIssue.type === 'string', 'issue should have a type'); - assert.ok(typeof firstIssue.backportOf === 'string' || firstIssue.backportOf === null, 'issue should have a backportOf value'); - } else { - assert.fail('no issues were returned'); - } + assert.strictEqual(firstIssue.id, 'JDK-8280124'); + assert.strictEqual(firstIssue.title, 'Reduce branches decoding latin-1 chars'); + assert.strictEqual(firstIssue.priority, '3'); + assert.strictEqual(firstIssue.component, 'core-libs'); + assert.strictEqual(firstIssue.subcomponent, 'core-libs/java.lang'); + assert.strictEqual(firstIssue.link, 'https://bugs.openjdk.org/browse/JDK-8280124'); + assert.strictEqual(firstIssue.type, 'Backport'); + assert.strictEqual(firstIssue.backportOf, 'JDK-8280123'); + }); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test('fetchJiraIssues throws a clear error on HTTP error response', async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = async () => ({ + ok: false, + status: 400, + headers: { get: () => 'text/html' }, }); + + try { + await assert.rejects( + () => fetchJiraIssues('17.0.6'), + (err) => { + assert.ok(err.message.includes('400'), `Expected 400 in error message, got: ${err.message}`); + return true; + }, + ); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test('fetchJiraIssues throws a clear error when response is HTML not JSON', async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = async () => ({ + ok: true, + status: 200, + headers: { get: () => 'text/html; charset=utf-8' }, + text: async () => 'Service Unavailable', + json: async () => { throw new SyntaxError('Unexpected token <'); }, + }); + + try { + await assert.rejects( + () => fetchJiraIssues('17.0.6', 1), + (err) => { + assert.ok(err.message.includes('non-JSON'), `Expected "non-JSON" in error message, got: ${err.message}`); + assert.ok(err.message.includes('text/html'), `Expected content-type in error message, got: ${err.message}`); + return true; + }, + ); + } finally { + globalThis.fetch = originalFetch; + } }); From 9dbcbe85d0ef3b067cdcccc1ff6c1d46d709d80e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:14:43 -0400 Subject: [PATCH 12/13] Bump actions/setup-java from 6.0.0 to 6.0.1 (#232) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/dd06d9cba3e5552c54d9f8ea23572deb30010f7c...de7274f081f381c8f8158605e0321c36c376e2e6) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 859fe65..a870a6b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: 'temurin' java-version: '21' From b260ea90da972e6647432f260043e727808377a8 Mon Sep 17 00:00:00 2001 From: smlambert Date: Wed, 26 Aug 2026 13:21:56 -0400 Subject: [PATCH 13/13] fix #200 --- .../generate-release-notes/fetchReleaseNotes.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js index b2244a4..323547d 100755 --- a/generate-release-notes/generate-release-notes/fetchReleaseNotes.js +++ b/generate-release-notes/generate-release-notes/fetchReleaseNotes.js @@ -65,8 +65,15 @@ for (const commit of commits) { backportOf: null, backportedBy: backportIssueId, }; - } else { - releaseNote = { ...releaseNote, backportedBy: null }; + } else if (releaseNote.type === 'Backport' && releaseNote.backportOf) { + // For backport issues, use the master bug ID and link so that the release + // notes refer to the canonical bug with a description rather than the + // backport ticket which is usually empty. + releaseNote = { + ...releaseNote, + id: releaseNote.backportOf, + link: `https://bugs.openjdk.org/browse/${releaseNote.backportOf}`, + }; } output.push(releaseNote);