Handle branch names containing hyphen separators

This commit is contained in:
Thomas Spencer
2024-04-24 11:56:55 +08:00
parent 518993c026
commit a44a9dfd95
5 changed files with 154 additions and 6 deletions

25
dist/index.js generated vendored
View File

@@ -10089,6 +10089,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.calculateUpdateType = exports.parse = void 0;
const YAML = __importStar(__nccwpck_require__(4083));
function branchNameToDirectoryName(chunks, delimiter, updatedDependencies) {
// We can always slice after the first 2 pieces, because they will always contain "dependabot" followed by the name
// of the package ecosystem. e.g. "dependabot/npm_and_yarn".
const sliceStart = 2;
let sliceEnd = chunks.length;
// If the delimiter is "-", we assume the last piece of the branch name is a version number.
if (delimiter === '-') {
sliceEnd -= 1;
}
// If there is more than 1 dependency name being updated, we assume 1 piece of the branch name will be "and".
if (updatedDependencies.length > 1) {
sliceEnd -= 1;
}
updatedDependencies.forEach(dependency => {
// After replacing "/" in the dependency name with the delimiter, which could also be "/", we count how many pieces
// the dependency name would split into by the delimiter, and slicing that amount off the end of the branch name.
// e.g. "@types/twilio-video" and a delimiter of "-" would show up in the branch name as "types-twilio-video".
sliceEnd -= dependency['dependency-name'].replace('/', delimiter).split(delimiter).length;
});
return `/${chunks.slice(sliceStart, sliceEnd).join('/')}`;
}
function parse(commitMessage, body, branchName, mainBranch, lookup, getScore) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
return __awaiter(this, void 0, void 0, function* () {
@@ -10108,12 +10129,12 @@ function parse(commitMessage, body, branchName, mainBranch, lookup, getScore) {
const next = (_f = (_e = bumpFragment === null || bumpFragment === void 0 ? void 0 : bumpFragment.groups) === null || _e === void 0 ? void 0 : _e.to) !== null && _f !== void 0 ? _f : ((_h = (_g = updateFragment === null || updateFragment === void 0 ? void 0 : updateFragment.groups) === null || _g === void 0 ? void 0 : _g.to) !== null && _h !== void 0 ? _h : '');
const dependencyGroup = (_k = (_j = groupName === null || groupName === void 0 ? void 0 : groupName.groups) === null || _j === void 0 ? void 0 : _j.name) !== null && _k !== void 0 ? _k : '';
if (data['updated-dependencies']) {
const dirname = branchNameToDirectoryName(chunks, delim, data['updated-dependencies']);
return yield Promise.all(data['updated-dependencies'].map((dependency, index) => __awaiter(this, void 0, void 0, function* () {
const dirname = `/${chunks.slice(2, -1 * (1 + (dependency['dependency-name'].match(/\//g) || []).length)).join(delim) || ''}`;
const lastVersion = index === 0 ? prev : '';
const nextVersion = index === 0 ? next : '';
const updateType = dependency['update-type'] || calculateUpdateType(lastVersion, nextVersion);
return Object.assign({ dependencyName: dependency['dependency-name'], dependencyType: dependency['dependency-type'], updateType, directory: dirname, packageEcosystem: chunks[1], targetBranch: mainBranch, prevVersion: lastVersion, newVersion: nextVersion, compatScore: yield scoreFn(dependency['dependency-name'], lastVersion, nextVersion, chunks[1]), maintainerChanges: newMaintainer, dependencyGroup: dependencyGroup }, yield lookupFn(dependency['dependency-name'], lastVersion, dirname));
return Object.assign({ dependencyName: dependency['dependency-name'], dependencyType: dependency['dependency-type'], updateType, directory: dirname, packageEcosystem: chunks[1], targetBranch: mainBranch, prevVersion: lastVersion, newVersion: nextVersion, compatScore: yield scoreFn(dependency['dependency-name'], lastVersion, nextVersion, chunks[1]), maintainerChanges: newMaintainer, dependencyGroup }, yield lookupFn(dependency['dependency-name'], lastVersion, dirname));
})));
}
}

View File

@@ -106,7 +106,7 @@ test('it supports multiple dependencies within a single fragment', async () => {
return Promise.resolve(0)
}
const updatedDependencies = await updateMetadata.parse(commitMessage, body, 'dependabot/nuget/api/main/coffee-rails', 'main', getAlert, getScore)
const updatedDependencies = await updateMetadata.parse(commitMessage, body, 'dependabot/nuget/api/main/coffee-rails/and/coffeescript', 'main', getAlert, getScore)
expect(updatedDependencies).toHaveLength(2)
@@ -299,6 +299,105 @@ test('it properly handles dependencies which contain slashes', async () => {
expect(updatedDependencies[0].dependencyGroup).toEqual('')
})
test('it handles branch names with hyphen separator', async () => {
const commitMessage =
'- [Release notes](https://github.com/fsevents/fsevents/releases)\n' +
'- [Commits](fsevents/fsevents@v1.2.9...v1.2.13)\n' +
'\n' +
'---\n' +
'updated-dependencies:\n' +
'- dependency-name: fsevents\n' +
' dependency-type: indirect\n' +
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const getAlert = async () => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
const getScore = async () => Promise.resolve(0)
const updatedDependencies = await updateMetadata.parse(commitMessage, '', 'dependabot-npm_and_yarn-fsevents-1.2.13', 'master', getAlert, getScore)
expect(updatedDependencies[0].directory).toEqual('/')
})
test('it handles branch names with hyphen separator and manifest files in nested directories', async () => {
const commitMessage =
'- [Release notes](https://github.com/fsevents/fsevents/releases)\n' +
'- [Commits](fsevents/fsevents@v1.2.9...v1.2.13)\n' +
'\n' +
'---\n' +
'updated-dependencies:\n' +
'- dependency-name: fsevents\n' +
' dependency-type: indirect\n' +
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const getAlert = async () => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
const getScore = async () => Promise.resolve(0)
const updatedDependencies = await updateMetadata.parse(commitMessage, '', 'dependabot-npm_and_yarn-nested-nested-fsevents-1.2.13', 'master', getAlert, getScore)
expect(updatedDependencies[0].directory).toEqual('/nested/nested')
})
test('it handles branch names with hyphen separator and dependency names with forward slashes', async () => {
const commitMessage =
'- [Release notes](https://github.com/composer/composer/releases)\n' +
'- [Changelog](https://github.com/composer/composer/blob/main/CHANGELOG.md)\n' +
'- [Commits](composer/composer@1.10.26...2.6.5)\n' +
'\n' +
'---\n' +
'updated-dependencies:\n' +
'- dependency-name: composer/composer\n' +
' dependency-type: indirect\n' +
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const getAlert = async () => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
const getScore = async () => Promise.resolve(0)
const updatedDependencies = await updateMetadata.parse(commitMessage, '', 'dependabot-composer-composer-composer-2.6.5', 'master', getAlert, getScore)
expect(updatedDependencies[0].directory).toEqual('/')
})
test('it handles branch names with hyphen separator and multiple dependencies', async () => {
const commitMessage =
'Updates `twilio-video` from 2.7.0 to 2.28.1\n' +
'- [Release notes](https://github.com/twilio/twilio-video.js/releases)\n' +
'- [Changelog](https://github.com/twilio/twilio-video.js/blob/master/CHANGELOG.md)\n' +
'- [Commits](twilio/twilio-video.js@2.7.0...2.28.1)\n' +
'\n' +
'Updates `@types/twilio-video` from 2.7.0 to 2.11.0\n' +
'- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)\n' +
'- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/twilio-video)\n' +
'\n' +
'---\n' +
'updated-dependencies:\n' +
'- dependency-name: twilio-video\n' +
' dependency-type: direct:production\n' +
' update-type: version-update:semver-minor\n' +
'- dependency-name: "@types/twilio-video"\n' +
' dependency-type: direct:development\n' +
' update-type: version-update:semver-minor\n' +
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const getAlert = async () => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
const getScore = async () => Promise.resolve(0)
const updatedDependencies = await updateMetadata.parse(
commitMessage,
'',
'dependabot-npm_and_yarn-twilio-video-and-types-twilio-video-2.28.1',
'master',
getAlert,
getScore
)
expect(updatedDependencies[0].directory).toEqual('/')
})
test('calculateUpdateType should handle all paths', () => {
expect(updateMetadata.calculateUpdateType('', '')).toEqual('')
expect(updateMetadata.calculateUpdateType('', '1')).toEqual('')

View File

@@ -28,6 +28,32 @@ export interface scoreLookup {
(dependencyName: string, previousVersion: string, newVersion: string, ecosystem: string): Promise<number>;
}
function branchNameToDirectoryName (chunks: string[], delimiter: string, updatedDependencies: any): string {
// We can always slice after the first 2 pieces, because they will always contain "dependabot" followed by the name
// of the package ecosystem. e.g. "dependabot/npm_and_yarn".
const sliceStart = 2
let sliceEnd = chunks.length
// If the delimiter is "-", we assume the last piece of the branch name is a version number.
if (delimiter === '-') {
sliceEnd -= 1
}
// If there is more than 1 dependency name being updated, we assume 1 piece of the branch name will be "and".
if (updatedDependencies.length > 1) {
sliceEnd -= 1
}
updatedDependencies.forEach(dependency => {
// After replacing "/" in the dependency name with the delimiter, which could also be "/", we count how many pieces
// the dependency name would split into by the delimiter, and slicing that amount off the end of the branch name.
// e.g. "@types/twilio-video" and a delimiter of "-" would show up in the branch name as "types-twilio-video".
sliceEnd -= dependency['dependency-name'].replace('/', delimiter).split(delimiter).length
})
return `/${chunks.slice(sliceStart, sliceEnd).join('/')}`
}
export async function parse (commitMessage: string, body: string, branchName: string, mainBranch: string, lookup?: alertLookup, getScore?: scoreLookup): Promise<Array<updatedDependency>> {
const bumpFragment = commitMessage.match(/^Bumps .* from (?<from>v?\d[^ ]*) to (?<to>v?\d[^ ]*)\.$/m)
const updateFragment = commitMessage.match(/^Update .* requirement from \S*? ?(?<from>v?\d\S*) to \S*? ?(?<to>v?\d\S*)$/m)
@@ -48,8 +74,9 @@ export async function parse (commitMessage: string, body: string, branchName: st
const dependencyGroup = groupName?.groups?.name ?? ''
if (data['updated-dependencies']) {
const dirname = branchNameToDirectoryName(chunks, delim, data['updated-dependencies'])
return await Promise.all(data['updated-dependencies'].map(async (dependency, index) => {
const dirname = `/${chunks.slice(2, -1 * (1 + (dependency['dependency-name'].match(/\//g) || []).length)).join(delim) || ''}`
const lastVersion = index === 0 ? prev : ''
const nextVersion = index === 0 ? next : ''
const updateType = dependency['update-type'] || calculateUpdateType(lastVersion, nextVersion)
@@ -64,7 +91,7 @@ export async function parse (commitMessage: string, body: string, branchName: st
newVersion: nextVersion,
compatScore: await scoreFn(dependency['dependency-name'], lastVersion, nextVersion, chunks[1]),
maintainerChanges: newMaintainer,
dependencyGroup: dependencyGroup,
dependencyGroup,
...await lookupFn(dependency['dependency-name'], lastVersion, dirname)
}
}))

View File

@@ -93,4 +93,5 @@ require('yargs')(hideBin(process.argv))
})
.demandCommand(1)
.help()
.strict()
.argv

View File

@@ -415,7 +415,7 @@ test('if there are multiple dependencies, it summarizes them', async () => {
const mockAlert = { alertState: '', ghsaId: '', cvss: 0 }
jest.spyOn(core, 'getInput').mockReturnValue('mock-token')
jest.spyOn(util, 'getBranchNames').mockReturnValue({ headName: 'dependabot/npm_and_yarn/api/main/feature1', baseName: 'trunk' })
jest.spyOn(util, 'getBranchNames').mockReturnValue({ headName: 'dependabot/npm_and_yarn/api/main/coffee-rails/and/coffeescript', baseName: 'trunk' })
jest.spyOn(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
() => Promise.resolve(mockCommitMessage)
))