mirror of
https://github.com/dependabot/fetch-metadata.git
synced 2026-03-13 18:17:13 -04:00
feat: Parse versions from metadata links
Dependabot PRs that update a **single** dependency include version details in the commit message introduction, e.g., > "Bumps `<dependency>` from `<prevVersion>` to `<newVersion>`" This is the format generated by the [`commit_message_intro`](cc4b4eaade/common/lib/dependabot/pull_request_creator/message_builder.rb (L320-L325)) method in Dependabot Core. However, when **multiple dependencies** are updated in a single PR, this format isn't used consistently, which limits the action’s ability to extract accurate version information. This change improves version parsing for multi-dependency PRs by introducing two additional detection strategies: 1. **YAML metadata parsing** Dependabot includes a YAML block in the commit message with structured details for each updated dependency: ```yaml updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-version: 1.18.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: non-breaking ``` This is the most reliable and stable source for the **new** version of each dependency, though it does **not** include the previous version. 2. **Metadata links parsing** In multi-dependency updates, Dependabot also appends “metadata links” with a format like: > "Updates `<dependencyName>` from `<prevVersion>` to `<newVersion>`" These lines are generated bythe [`metadata_links`](cc4b4eaade/common/lib/dependabot/pull_request_creator/message_builder.rb (L664-L678)) method and provide **both** the old and new versions. By combining these sources, the action now supports version parsing for PRs with multiple updated dependencies—broadening its coverage and improving reliability. Closes #402
This commit is contained in:
@@ -507,3 +507,54 @@ test('calculateUpdateType should handle all paths', () => {
|
||||
expect(updateMetadata.calculateUpdateType('1.1.1', '1.1.2')).toEqual('version-update:semver-patch')
|
||||
expect(updateMetadata.calculateUpdateType('1.1.1.1', '1.1.1.2')).toEqual('version-update:semver-patch')
|
||||
})
|
||||
|
||||
test('it handles versions from `metadataLinks`', async () => {
|
||||
const commitMessage = `Bump the non-breaking group in /log4j-parent with 2 updates
|
||||
|
||||
Bumps the non-breaking group in /log4j-parent with 2 updates:
|
||||
|
||||
|
||||
Updates \`commons-codec:commons-codec\` from 1.17.0 to 1.18.0
|
||||
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
|
||||
- [Commits](apache/commons-codec@rel/commons-codec-1.17.0...rel/commons-codec-1.18.0)
|
||||
|
||||
Updates \`org.apache.commons:commons-compress\` to 1.27.1
|
||||
|
||||
---
|
||||
updated-dependencies:
|
||||
- dependency-name: commons-codec:commons-codec
|
||||
- dependency-name: org.apache.commons:commons-compress
|
||||
...
|
||||
`
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, '', 'dependabot/maven/non-breaking-cc60d48967', '2.x')
|
||||
expect(updatedDependencies).toHaveLength(2)
|
||||
expect(updatedDependencies[0].dependencyName).toEqual('commons-codec:commons-codec')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('1.17.0')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('1.18.0')
|
||||
expect(updatedDependencies[1].dependencyName).toEqual('org.apache.commons:commons-compress')
|
||||
expect(updatedDependencies[1].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[1].newVersion).toEqual('1.27.1')
|
||||
})
|
||||
|
||||
test('it handles new versions from YAML', async () => {
|
||||
const commitMessage = `Bump the non-breaking group in /log4j-parent with 2 updates
|
||||
|
||||
Bumps the non-breaking group in /log4j-parent with 2 updates:
|
||||
|
||||
---
|
||||
updated-dependencies:
|
||||
- dependency-name: commons-codec:commons-codec
|
||||
dependency-version: 1.18.0
|
||||
- dependency-name: org.apache.commons:commons-compress
|
||||
dependency-version: 1.27.1
|
||||
...
|
||||
`
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, '', 'dependabot/maven/non-breaking-cc60d48967', '2.x')
|
||||
expect(updatedDependencies).toHaveLength(2)
|
||||
expect(updatedDependencies[0].dependencyName).toEqual('commons-codec:commons-codec')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('1.18.0')
|
||||
expect(updatedDependencies[1].dependencyName).toEqual('org.apache.commons:commons-compress')
|
||||
expect(updatedDependencies[1].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[1].newVersion).toEqual('1.27.1')
|
||||
})
|
||||
|
||||
@@ -6,15 +6,18 @@ export interface dependencyAlert {
|
||||
cvss: number
|
||||
}
|
||||
|
||||
export interface updatedDependency extends dependencyAlert {
|
||||
interface dependencyVersions {
|
||||
prevVersion: string,
|
||||
newVersion: string
|
||||
}
|
||||
|
||||
export interface updatedDependency extends dependencyAlert, dependencyVersions {
|
||||
dependencyName: string,
|
||||
dependencyType: string,
|
||||
updateType: string,
|
||||
directory: string,
|
||||
packageEcosystem: string,
|
||||
targetBranch: string,
|
||||
prevVersion: string,
|
||||
newVersion: string,
|
||||
compatScore: number,
|
||||
maintainerChanges: boolean,
|
||||
dependencyGroup: string
|
||||
@@ -83,11 +86,14 @@ export async function parse (commitMessage: string, body: string, branchName: st
|
||||
const dependencyGroup = groupName?.groups?.name ?? ''
|
||||
|
||||
if (data['updated-dependencies']) {
|
||||
const updatedVersions = parseMetadataLinks(commitMessage)
|
||||
const dirname = branchNameToDirectoryName(chunks, delim, data['updated-dependencies'], dependencyGroup)
|
||||
|
||||
return await Promise.all(data['updated-dependencies'].map(async (dependency, index) => {
|
||||
const lastVersion = index === 0 ? prev : ''
|
||||
const nextVersion = index === 0 ? next : ''
|
||||
const dependencyName = dependency['dependency-name']
|
||||
const updatedVersion = updatedVersions.get(dependencyName)
|
||||
const lastVersion = updatedVersion?.prevVersion || (index === 0 ? prev : '')
|
||||
const nextVersion = dependency['dependency-version'] || updatedVersion?.newVersion || (index === 0 ? next : '')
|
||||
const updateType = dependency['update-type'] || calculateUpdateType(lastVersion, nextVersion)
|
||||
return {
|
||||
dependencyName: dependency['dependency-name'],
|
||||
@@ -110,6 +116,35 @@ export async function parse (commitMessage: string, body: string, branchName: st
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the human-readable metadata links from a commit message.
|
||||
* <p>
|
||||
* See {@code Dependabot::PullRequestCreator::MessageBuilder#metadata_links} in the Ruby codebase for more details
|
||||
* on the current format.
|
||||
* </p>
|
||||
* <p>
|
||||
* <strong>Note:</strong> This data is only available if more than one dependency is updated in a single PR.
|
||||
* </>
|
||||
* @param commitMessage - The commit message containing metadata links.
|
||||
* @returns A map from the name of the dependency to an updatedDependency object containing the old and new versions.
|
||||
*/
|
||||
function parseMetadataLinks(commitMessage: string): Map<string, dependencyVersions> {
|
||||
const updates: Map<string, dependencyVersions> = new Map()
|
||||
const updatesExpr: RegExp = /^Updates `(?<dependencyName>\S+)` (from (?<from>\S+) )?to (?<to>\S+)$/gm
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = updatesExpr.exec(commitMessage)) !== null) {
|
||||
const groups = match.groups
|
||||
if (groups) {
|
||||
const dependencyName = groups.dependencyName
|
||||
updates.set(dependencyName, {
|
||||
prevVersion: groups.from ?? '',
|
||||
newVersion: groups.to
|
||||
})
|
||||
}
|
||||
}
|
||||
return updates
|
||||
}
|
||||
|
||||
export function calculateUpdateType (lastVersion: string, nextVersion: string) {
|
||||
if (!lastVersion || !nextVersion || lastVersion === nextVersion) {
|
||||
return ''
|
||||
|
||||
Reference in New Issue
Block a user