diff --git a/dist/index.js b/dist/index.js index 6cd64e4e..0f6a803d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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)); }))); } } diff --git a/src/dependabot/update_metadata.test.ts b/src/dependabot/update_metadata.test.ts index 63a8cb94..0a1dcaf3 100644 --- a/src/dependabot/update_metadata.test.ts +++ b/src/dependabot/update_metadata.test.ts @@ -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] ' + + 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] ' + + 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] ' + + 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] ' + + 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('') diff --git a/src/dependabot/update_metadata.ts b/src/dependabot/update_metadata.ts index 64174ed2..1c1739e3 100644 --- a/src/dependabot/update_metadata.ts +++ b/src/dependabot/update_metadata.ts @@ -28,6 +28,32 @@ export interface scoreLookup { (dependencyName: string, previousVersion: string, newVersion: string, ecosystem: string): Promise; } +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> { const bumpFragment = commitMessage.match(/^Bumps .* from (?v?\d[^ ]*) to (?v?\d[^ ]*)\.$/m) const updateFragment = commitMessage.match(/^Update .* requirement from \S*? ?(?v?\d\S*) to \S*? ?(?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) } })) diff --git a/src/dry-run.ts b/src/dry-run.ts index 50d3859a..d36fd713 100755 --- a/src/dry-run.ts +++ b/src/dry-run.ts @@ -93,4 +93,5 @@ require('yargs')(hideBin(process.argv)) }) .demandCommand(1) .help() + .strict() .argv diff --git a/src/main.test.ts b/src/main.test.ts index fb73e7c4..93238867 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -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) ))