mirror of
https://github.com/dependabot/fetch-metadata.git
synced 2026-03-12 18:07:12 -04:00
Merge pull request #144 from pangaeatech/flag-security-alerts
Flag security alerts and pass versions through
This commit is contained in:
19
README.md
19
README.md
@@ -25,8 +25,17 @@ jobs:
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
alert-lookup: true
|
||||
```
|
||||
|
||||
Supported inputs are:
|
||||
|
||||
- `github-token` (REQUIRED string)
|
||||
- The `GITHUB_TOKEN` secret
|
||||
- `alert-lookup` (boolean)
|
||||
- If `true`, then call populate the `alert-state`, `ghsa-id` and `cvss` outputs.
|
||||
- Defaults to `false`
|
||||
|
||||
Subsequent actions will have access to the following outputs:
|
||||
|
||||
- `steps.dependabot-metadata.outputs.dependency-names`
|
||||
@@ -43,6 +52,16 @@ Subsequent actions will have access to the following outputs:
|
||||
- The `package-ecosystem` configuration that was used by dependabot for this updated Dependency.
|
||||
- `steps.dependabot-metadata.outputs.target-branch`
|
||||
- The `target-branch` configuration that was used by dependabot for this updated Dependency.
|
||||
- `steps.dependabot-metadata.outputs.previous-version`
|
||||
- The version that this PR updates the dependency from.
|
||||
- `steps.dependabot-metadata.outputs.new-version`
|
||||
- The version that this PR updates the dependency to.
|
||||
- `steps.dependabot-metadata.outputs.alert-state`
|
||||
- If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the current state of that alert (OPEN, FIXED or DISMISSED).
|
||||
- `steps.dependabot-metadata.outputs.ghsa-id`
|
||||
- If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the GHSA-ID of that alert.
|
||||
- `steps.dependabot-metadata.outputs.cvss`
|
||||
- If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the CVSS value of that alert (otherwise it contains 0).
|
||||
|
||||
**Note:** These outputs will only be populated if the target Pull Request was opened by Dependabot and contains
|
||||
**only** Dependabot-created commits.
|
||||
|
||||
13
action.yml
13
action.yml
@@ -4,6 +4,9 @@ branding:
|
||||
icon: 'search'
|
||||
color: 'blue'
|
||||
inputs:
|
||||
alert-lookup:
|
||||
type: boolean
|
||||
description: 'If true, then call populate the `alert-state`, `ghsa-id` and `cvss` outputs'
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
@@ -22,6 +25,16 @@ outputs:
|
||||
description: 'The `package-ecosystem` configuration that was used by dependabot for this updated Dependency.'
|
||||
target-branch:
|
||||
description: 'The `target-branch` configuration that was used by dependabot for this updated Dependency.'
|
||||
previous-version:
|
||||
description: 'The version that this PR updates the dependency from.'
|
||||
new-version:
|
||||
description: 'The version that this PR updates the dependency to.'
|
||||
alert-state:
|
||||
description: 'If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the current state of that alert (OPEN, FIXED or DISMISSED).'
|
||||
ghsa-id:
|
||||
description: 'If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the GHSA-ID of that alert.'
|
||||
cvss:
|
||||
description: 'If this PR is associated with a security alert and `alert-lookup` is `true`, this contains the CVSS value of that alert (otherwise it contains 0).'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
|
||||
113
dist/index.js
generated
vendored
113
dist/index.js
generated
vendored
@@ -8956,6 +8956,11 @@ function set(updatedDependencies) {
|
||||
const directory = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.directory;
|
||||
const ecosystem = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.packageEcosystem;
|
||||
const target = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.targetBranch;
|
||||
const prevVersion = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.prevVersion;
|
||||
const newVersion = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.newVersion;
|
||||
const alertState = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.alertState;
|
||||
const ghsaId = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.ghsaId;
|
||||
const cvss = firstDependency === null || firstDependency === void 0 ? void 0 : firstDependency.cvss;
|
||||
core.startGroup(`Outputting metadata for ${(0, pluralize_1.default)('updated dependency', updatedDependencies.length, true)}`);
|
||||
core.info(`outputs.dependency-names: ${dependencyNames}`);
|
||||
core.info(`outputs.dependency-type: ${dependencyType}`);
|
||||
@@ -8963,6 +8968,11 @@ function set(updatedDependencies) {
|
||||
core.info(`outputs.directory: ${directory}`);
|
||||
core.info(`outputs.package-ecosystem: ${ecosystem}`);
|
||||
core.info(`outputs.target-branch: ${target}`);
|
||||
core.info(`outputs.previous-version: ${prevVersion}`);
|
||||
core.info(`outputs.new-version: ${newVersion}`);
|
||||
core.info(`outputs.alert-state: ${alertState}`);
|
||||
core.info(`outputs.ghsa-id: ${ghsaId}`);
|
||||
core.info(`outputs.cvss: ${cvss}`);
|
||||
core.endGroup();
|
||||
core.setOutput('updated-dependencies-json', updatedDependencies);
|
||||
core.setOutput('dependency-names', dependencyNames);
|
||||
@@ -8971,6 +8981,11 @@ function set(updatedDependencies) {
|
||||
core.setOutput('directory', directory);
|
||||
core.setOutput('package-ecosystem', ecosystem);
|
||||
core.setOutput('target-branch', target);
|
||||
core.setOutput('previous-version', prevVersion);
|
||||
core.setOutput('new-version', newVersion);
|
||||
core.setOutput('alert-state', alertState);
|
||||
core.setOutput('ghsa-id', ghsaId);
|
||||
core.setOutput('cvss', cvss);
|
||||
}
|
||||
exports.set = set;
|
||||
function maxDependencyTypes(updatedDependencies) {
|
||||
@@ -9015,31 +9030,40 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.parse = void 0;
|
||||
const YAML = __importStar(__nccwpck_require__(4603));
|
||||
function parse(commitMessage, branchName, mainBranch) {
|
||||
const yamlFragment = commitMessage.match(/^-{3}\n(?<dependencies>[\S|\s]*?)\n^\.{3}\n/m);
|
||||
if ((yamlFragment === null || yamlFragment === void 0 ? void 0 : yamlFragment.groups) && branchName.startsWith('dependabot')) {
|
||||
const data = YAML.parse(yamlFragment.groups.dependencies);
|
||||
// Since we are on the `dependabot` branch (9 letters), the 10th letter in the branch name is the delimiter
|
||||
const delim = branchName[10];
|
||||
const chunks = branchName.split(delim);
|
||||
const dirname = chunks.slice(2, -1).join(delim) || '/';
|
||||
if (data['updated-dependencies']) {
|
||||
return data['updated-dependencies'].map(dependency => {
|
||||
return {
|
||||
dependencyName: dependency['dependency-name'],
|
||||
dependencyType: dependency['dependency-type'],
|
||||
updateType: dependency['update-type'],
|
||||
directory: dirname,
|
||||
packageEcosystem: chunks[1],
|
||||
targetBranch: mainBranch
|
||||
};
|
||||
});
|
||||
function parse(commitMessage, branchName, mainBranch, lookup) {
|
||||
var _a, _b, _c, _d;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const bumpFragment = commitMessage.match(/^Bumps .* from (?<from>\d[^ ]*) to (?<to>\d[^ ]*)\.$/m);
|
||||
const yamlFragment = commitMessage.match(/^-{3}\n(?<dependencies>[\S|\s]*?)\n^\.{3}\n/m);
|
||||
const lookupFn = lookup !== null && lookup !== void 0 ? lookup : (() => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 }));
|
||||
if ((yamlFragment === null || yamlFragment === void 0 ? void 0 : yamlFragment.groups) && branchName.startsWith('dependabot')) {
|
||||
const data = YAML.parse(yamlFragment.groups.dependencies);
|
||||
// Since we are on the `dependabot` branch (9 letters), the 10th letter in the branch name is the delimiter
|
||||
const delim = branchName[10];
|
||||
const chunks = branchName.split(delim);
|
||||
const prev = (_b = (_a = bumpFragment === null || bumpFragment === void 0 ? void 0 : bumpFragment.groups) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : '';
|
||||
const next = (_d = (_c = bumpFragment === null || bumpFragment === void 0 ? void 0 : bumpFragment.groups) === null || _c === void 0 ? void 0 : _c.to) !== null && _d !== void 0 ? _d : '';
|
||||
if (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) || ''}`;
|
||||
return Object.assign({ dependencyName: dependency['dependency-name'], dependencyType: dependency['dependency-type'], updateType: dependency['update-type'], directory: dirname, packageEcosystem: chunks[1], targetBranch: mainBranch, prevVersion: index === 0 ? prev : '', newVersion: index === 0 ? next : '' }, yield lookupFn(dependency['dependency-name'], index === 0 ? prev : '', dirname));
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
return Promise.resolve([]);
|
||||
});
|
||||
}
|
||||
exports.parse = parse;
|
||||
|
||||
@@ -9104,7 +9128,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getMessage = void 0;
|
||||
exports.trimSlashes = exports.getAlert = exports.getMessage = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const DEPENDABOT_LOGIN = 'dependabot[bot]';
|
||||
function getMessage(client, context) {
|
||||
@@ -9151,6 +9175,45 @@ function warnOtherCommits() {
|
||||
"Try using '@dependabot rebase' to remove merge commits or '@dependabot recreate' to remove " +
|
||||
'any non-Dependabot changes.');
|
||||
}
|
||||
function getAlert(name, version, directory, client, context) {
|
||||
var _a, _b, _c, _d, _e;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const alerts = yield client.graphql(`
|
||||
{
|
||||
repository(owner: "${context.repo.owner}", name: "${context.repo.repo}") {
|
||||
vulnerabilityAlerts(first: 100) {
|
||||
nodes {
|
||||
vulnerableManifestFilename
|
||||
vulnerableManifestPath
|
||||
vulnerableRequirements
|
||||
state
|
||||
securityVulnerability {
|
||||
package { name }
|
||||
}
|
||||
securityAdvisory {
|
||||
cvss { score }
|
||||
ghsaId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
const nodes = (_b = (_a = alerts === null || alerts === void 0 ? void 0 : alerts.repository) === null || _a === void 0 ? void 0 : _a.vulnerabilityAlerts) === null || _b === void 0 ? void 0 : _b.nodes;
|
||||
const found = nodes.find(a => (version === '' || a.vulnerableRequirements === `= ${version}`) &&
|
||||
trimSlashes(a.vulnerableManifestPath) === `${trimSlashes(directory)}/${a.vulnerableManifestFilename}` &&
|
||||
a.securityVulnerability.package.name === name);
|
||||
return {
|
||||
alertState: (_c = found === null || found === void 0 ? void 0 : found.state) !== null && _c !== void 0 ? _c : '',
|
||||
ghsaId: (_d = found === null || found === void 0 ? void 0 : found.securityAdvisory.ghsaId) !== null && _d !== void 0 ? _d : '',
|
||||
cvss: (_e = found === null || found === void 0 ? void 0 : found.securityAdvisory.cvss.score) !== null && _e !== void 0 ? _e : 0.0
|
||||
};
|
||||
});
|
||||
}
|
||||
exports.getAlert = getAlert;
|
||||
function trimSlashes(value) {
|
||||
return value.replace(/^\/+/, '').replace(/\/+$/, '');
|
||||
}
|
||||
exports.trimSlashes = trimSlashes;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -9211,10 +9274,14 @@ function run() {
|
||||
// Validate the job
|
||||
const commitMessage = yield verifiedCommits.getMessage(githubClient, github.context);
|
||||
const branchNames = util.getBranchNames(github.context);
|
||||
let alertLookup;
|
||||
if (core.getInput('alert-lookup')) {
|
||||
alertLookup = (name, version, directory) => verifiedCommits.getAlert(name, version, directory, githubClient, github.context);
|
||||
}
|
||||
if (commitMessage) {
|
||||
// Parse metadata
|
||||
core.info('Parsing Dependabot metadata');
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName);
|
||||
const updatedDependencies = yield updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName, alertLookup);
|
||||
if (updatedDependencies.length > 0) {
|
||||
output.set(updatedDependencies);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,20 @@ beforeEach(() => {
|
||||
jest.spyOn(core, 'startGroup').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
const baseDependency = {
|
||||
dependencyName: '',
|
||||
dependencyType: '',
|
||||
updateType: '',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: '',
|
||||
prevVersion: '',
|
||||
newVersion: '',
|
||||
alertState: '',
|
||||
ghsaId: '',
|
||||
cvss: 0
|
||||
}
|
||||
|
||||
test('when given a single dependency it sets its values', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
@@ -17,7 +31,12 @@ test('when given a single dependency it sets its values', async () => {
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: 'wwwroot',
|
||||
packageEcosystem: 'nuget',
|
||||
targetBranch: 'main'
|
||||
targetBranch: 'main',
|
||||
prevVersion: '1.0.2',
|
||||
newVersion: '1.1.3-beta',
|
||||
alertState: 'FIXED',
|
||||
ghsaId: 'VERY_LONG_ID',
|
||||
cvss: 4.6
|
||||
}
|
||||
]
|
||||
|
||||
@@ -35,41 +54,38 @@ test('when given a single dependency it sets its values', async () => {
|
||||
expect(core.setOutput).toBeCalledWith('directory', 'wwwroot')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', 'nuget')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', 'main')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '1.0.2')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '1.1.3-beta')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', 'FIXED')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', 'VERY_LONG_ID')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 4.6)
|
||||
})
|
||||
|
||||
test('when given a multiple dependencies, it uses the highest values for types', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'rspec',
|
||||
dependencyType: 'direct:development',
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-major',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-major'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'rspec-coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-patch',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-patch'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -83,17 +99,19 @@ test('when given a multiple dependencies, it uses the highest values for types',
|
||||
expect(core.setOutput).toBeCalledWith('directory', '')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', '')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', '')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', '')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', '')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 0)
|
||||
})
|
||||
|
||||
test('when the dependency has no update type', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'direct:production',
|
||||
updateType: '',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
dependencyType: 'direct:production'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -111,41 +129,36 @@ test('when the dependency has no update type', async () => {
|
||||
expect(core.setOutput).toBeCalledWith('directory', '')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', '')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', '')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', '')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', '')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 0)
|
||||
})
|
||||
|
||||
test('when given a multiple dependencies, and some do not have update types', async () => {
|
||||
const updatedDependencies = [
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'rspec',
|
||||
dependencyType: 'direct:development',
|
||||
updateType: '',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
dependencyType: 'direct:development'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-minor'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: '',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
dependencyType: 'indirect'
|
||||
},
|
||||
{
|
||||
...baseDependency,
|
||||
dependencyName: 'rspec-coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-patch',
|
||||
directory: '',
|
||||
packageEcosystem: '',
|
||||
targetBranch: ''
|
||||
updateType: 'version-update:semver-patch'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -159,4 +172,9 @@ test('when given a multiple dependencies, and some do not have update types', as
|
||||
expect(core.setOutput).toBeCalledWith('directory', '')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', '')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', '')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', '')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', '')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 0)
|
||||
})
|
||||
|
||||
@@ -24,6 +24,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
|
||||
const directory = firstDependency?.directory
|
||||
const ecosystem = firstDependency?.packageEcosystem
|
||||
const target = firstDependency?.targetBranch
|
||||
const prevVersion = firstDependency?.prevVersion
|
||||
const newVersion = firstDependency?.newVersion
|
||||
const alertState = firstDependency?.alertState
|
||||
const ghsaId = firstDependency?.ghsaId
|
||||
const cvss = firstDependency?.cvss
|
||||
|
||||
core.startGroup(`Outputting metadata for ${Pluralize('updated dependency', updatedDependencies.length, true)}`)
|
||||
core.info(`outputs.dependency-names: ${dependencyNames}`)
|
||||
@@ -32,6 +37,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
|
||||
core.info(`outputs.directory: ${directory}`)
|
||||
core.info(`outputs.package-ecosystem: ${ecosystem}`)
|
||||
core.info(`outputs.target-branch: ${target}`)
|
||||
core.info(`outputs.previous-version: ${prevVersion}`)
|
||||
core.info(`outputs.new-version: ${newVersion}`)
|
||||
core.info(`outputs.alert-state: ${alertState}`)
|
||||
core.info(`outputs.ghsa-id: ${ghsaId}`)
|
||||
core.info(`outputs.cvss: ${cvss}`)
|
||||
core.endGroup()
|
||||
|
||||
core.setOutput('updated-dependencies-json', updatedDependencies)
|
||||
@@ -41,6 +51,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
|
||||
core.setOutput('directory', directory)
|
||||
core.setOutput('package-ecosystem', ecosystem)
|
||||
core.setOutput('target-branch', target)
|
||||
core.setOutput('previous-version', prevVersion)
|
||||
core.setOutput('new-version', newVersion)
|
||||
core.setOutput('alert-state', alertState)
|
||||
core.setOutput('ghsa-id', ghsaId)
|
||||
core.setOutput('cvss', cvss)
|
||||
}
|
||||
|
||||
function maxDependencyTypes (updatedDependencies: Array<updatedDependency>): string {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as updateMetadata from './update_metadata'
|
||||
|
||||
test('it returns an empty array for a blank string', async () => {
|
||||
expect(updateMetadata.parse('', 'dependabot/nuget/feature1', 'main')).toEqual([])
|
||||
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
|
||||
expect(updateMetadata.parse('', 'dependabot/nuget/coffee-rails', 'main', getAlert)).resolves.toEqual([])
|
||||
})
|
||||
|
||||
test('it returns an empty array for commit message with no dependabot yaml fragment', async () => {
|
||||
@@ -12,7 +13,8 @@ test('it returns an empty array for commit message with no dependabot yaml fragm
|
||||
|
||||
Signed-off-by: dependabot[bot] <support@github.com>`
|
||||
|
||||
expect(updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main')).toEqual([])
|
||||
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
|
||||
expect(updateMetadata.parse(commitMessage, 'dependabot/nuget/coffee-rails', 'main', getAlert)).resolves.toEqual([])
|
||||
})
|
||||
|
||||
test('it returns the updated dependency information when there is a yaml fragment', async () => {
|
||||
@@ -31,7 +33,8 @@ test('it returns the updated dependency information when there is a yaml fragmen
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main')
|
||||
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot/nuget/coffee-rails', 'main', getAlert)
|
||||
|
||||
expect(updatedDependencies).toHaveLength(1)
|
||||
|
||||
@@ -41,6 +44,11 @@ test('it returns the updated dependency information when there is a yaml fragmen
|
||||
expect(updatedDependencies[0].directory).toEqual('/')
|
||||
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
|
||||
expect(updatedDependencies[0].targetBranch).toEqual('main')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('4.0.1')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('4.2.2')
|
||||
expect(updatedDependencies[0].alertState).toEqual('DISMISSED')
|
||||
expect(updatedDependencies[0].ghsaId).toEqual('GHSA-III-BBB')
|
||||
expect(updatedDependencies[0].cvss).toEqual(4.6)
|
||||
})
|
||||
|
||||
test('it supports multiple dependencies within a single fragment', async () => {
|
||||
@@ -62,28 +70,45 @@ test('it supports multiple dependencies within a single fragment', async () => {
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot/nuget/api/main/feature1', 'main')
|
||||
const getAlert = async (name: string) => {
|
||||
if (name === 'coffee-rails') {
|
||||
return Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
|
||||
}
|
||||
|
||||
return Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
|
||||
}
|
||||
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot/nuget/api/main/coffee-rails', 'main', getAlert)
|
||||
|
||||
expect(updatedDependencies).toHaveLength(2)
|
||||
|
||||
expect(updatedDependencies[0].dependencyName).toEqual('coffee-rails')
|
||||
expect(updatedDependencies[0].dependencyType).toEqual('direct:production')
|
||||
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
|
||||
expect(updatedDependencies[0].directory).toEqual('api/main')
|
||||
expect(updatedDependencies[0].directory).toEqual('/api/main')
|
||||
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
|
||||
expect(updatedDependencies[0].targetBranch).toEqual('main')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('4.0.1')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('4.2.2')
|
||||
expect(updatedDependencies[0].alertState).toEqual('DISMISSED')
|
||||
expect(updatedDependencies[0].ghsaId).toEqual('GHSA-III-BBB')
|
||||
expect(updatedDependencies[0].cvss).toEqual(4.6)
|
||||
|
||||
expect(updatedDependencies[1].dependencyName).toEqual('coffeescript')
|
||||
expect(updatedDependencies[1].dependencyType).toEqual('indirect')
|
||||
expect(updatedDependencies[1].updateType).toEqual('version-update:semver-patch')
|
||||
expect(updatedDependencies[1].directory).toEqual('api/main')
|
||||
expect(updatedDependencies[1].directory).toEqual('/api/main')
|
||||
expect(updatedDependencies[1].packageEcosystem).toEqual('nuget')
|
||||
expect(updatedDependencies[1].targetBranch).toEqual('main')
|
||||
expect(updatedDependencies[1].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[1].newVersion).toEqual('')
|
||||
expect(updatedDependencies[1].alertState).toEqual('')
|
||||
expect(updatedDependencies[1].ghsaId).toEqual('')
|
||||
expect(updatedDependencies[1].cvss).toEqual(0)
|
||||
})
|
||||
|
||||
test('it only returns information within the first fragment if there are multiple yaml documents', async () => {
|
||||
const commitMessage =
|
||||
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
|
||||
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
|
||||
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
|
||||
'- [Commits](rails/coffee-rails@v4.0.1...v4.2.2)\n' +
|
||||
@@ -104,14 +129,52 @@ test('it only returns information within the first fragment if there are multipl
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot|nuget|api|feature1', 'main')
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot|nuget|coffee-rails', 'main', undefined)
|
||||
|
||||
expect(updatedDependencies).toHaveLength(1)
|
||||
|
||||
expect(updatedDependencies[0].dependencyName).toEqual('coffee-rails')
|
||||
expect(updatedDependencies[0].dependencyType).toEqual('direct:production')
|
||||
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
|
||||
expect(updatedDependencies[0].directory).toEqual('api')
|
||||
expect(updatedDependencies[0].directory).toEqual('/')
|
||||
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
|
||||
expect(updatedDependencies[0].targetBranch).toEqual('main')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('')
|
||||
expect(updatedDependencies[0].alertState).toEqual('')
|
||||
expect(updatedDependencies[0].ghsaId).toEqual('')
|
||||
expect(updatedDependencies[0].cvss).toEqual(0)
|
||||
})
|
||||
|
||||
test('it properly handles dependencies which contain slashes', async () => {
|
||||
const commitMessage =
|
||||
'- [Release notes](https://github.com/rails/coffee/releases)\n' +
|
||||
'- [Changelog](https://github.com/rails/coffee/blob/master/CHANGELOG.md)\n' +
|
||||
'- [Commits](rails/coffee@v4.0.1...v4.2.2)\n' +
|
||||
'\n' +
|
||||
'---\n' +
|
||||
'updated-dependencies:\n' +
|
||||
'- dependency-name: rails/coffee\n' +
|
||||
' dependency-type: direct:production\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 updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot/nuget/api/rails/coffee', 'main', getAlert)
|
||||
|
||||
expect(updatedDependencies).toHaveLength(1)
|
||||
|
||||
expect(updatedDependencies[0].dependencyName).toEqual('rails/coffee')
|
||||
expect(updatedDependencies[0].dependencyType).toEqual('direct:production')
|
||||
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
|
||||
expect(updatedDependencies[0].directory).toEqual('/api')
|
||||
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
|
||||
expect(updatedDependencies[0].targetBranch).toEqual('main')
|
||||
expect(updatedDependencies[0].prevVersion).toEqual('')
|
||||
expect(updatedDependencies[0].newVersion).toEqual('')
|
||||
expect(updatedDependencies[0].alertState).toEqual('')
|
||||
expect(updatedDependencies[0].ghsaId).toEqual('')
|
||||
expect(updatedDependencies[0].cvss).toEqual(0)
|
||||
})
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
import * as YAML from 'yaml'
|
||||
|
||||
export interface updatedDependency {
|
||||
export interface dependencyAlert {
|
||||
alertState: string,
|
||||
ghsaId: string,
|
||||
cvss: number
|
||||
}
|
||||
|
||||
export interface updatedDependency extends dependencyAlert {
|
||||
dependencyName: string,
|
||||
dependencyType: string,
|
||||
updateType: string,
|
||||
directory: string,
|
||||
packageEcosystem: string,
|
||||
targetBranch: string
|
||||
targetBranch: string,
|
||||
prevVersion: string,
|
||||
newVersion: string
|
||||
}
|
||||
|
||||
export function parse (commitMessage: string, branchName: string, mainBranch: string): Array<updatedDependency> {
|
||||
export interface alertLookup {
|
||||
(dependencyName: string, dependencyVersion: string, directory: string): Promise<dependencyAlert>;
|
||||
}
|
||||
|
||||
export async function parse (commitMessage: string, branchName: string, mainBranch: string, lookup?: alertLookup): Promise<Array<updatedDependency>> {
|
||||
const bumpFragment = commitMessage.match(/^Bumps .* from (?<from>\d[^ ]*) to (?<to>\d[^ ]*)\.$/m)
|
||||
const yamlFragment = commitMessage.match(/^-{3}\n(?<dependencies>[\S|\s]*?)\n^\.{3}\n/m)
|
||||
const lookupFn = lookup ?? (() => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 }))
|
||||
|
||||
if (yamlFragment?.groups && branchName.startsWith('dependabot')) {
|
||||
const data = YAML.parse(yamlFragment.groups.dependencies)
|
||||
@@ -18,21 +32,26 @@ export function parse (commitMessage: string, branchName: string, mainBranch: st
|
||||
// Since we are on the `dependabot` branch (9 letters), the 10th letter in the branch name is the delimiter
|
||||
const delim = branchName[10]
|
||||
const chunks = branchName.split(delim)
|
||||
const dirname = chunks.slice(2, -1).join(delim) || '/'
|
||||
const prev = bumpFragment?.groups?.from ?? ''
|
||||
const next = bumpFragment?.groups?.to ?? ''
|
||||
|
||||
if (data['updated-dependencies']) {
|
||||
return data['updated-dependencies'].map(dependency => {
|
||||
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) || ''}`
|
||||
return {
|
||||
dependencyName: dependency['dependency-name'],
|
||||
dependencyType: dependency['dependency-type'],
|
||||
updateType: dependency['update-type'],
|
||||
directory: dirname,
|
||||
packageEcosystem: chunks[1],
|
||||
targetBranch: mainBranch
|
||||
targetBranch: mainBranch,
|
||||
prevVersion: index === 0 ? prev : '',
|
||||
newVersion: index === 0 ? next : '',
|
||||
...await lookupFn(dependency['dependency-name'], index === 0 ? prev : '', dirname)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as github from '@actions/github'
|
||||
import * as core from '@actions/core'
|
||||
import nock from 'nock'
|
||||
import { Context } from '@actions/github/lib/context'
|
||||
import { getMessage } from './verified_commits'
|
||||
import { getAlert, getMessage, trimSlashes } from './verified_commits'
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect()
|
||||
@@ -130,6 +130,70 @@ test('it returns the commit message for a PR authored exclusively by Dependabot
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toEqual('Bump lodash from 1.0.0 to 2.0.0')
|
||||
})
|
||||
|
||||
const query = '{"query":"\\n {\\n repository(owner: \\"dependabot\\", name: \\"dependabot\\") { \\n vulnerabilityAlerts(first: 100) {\\n nodes {\\n vulnerableManifestFilename\\n vulnerableManifestPath\\n vulnerableRequirements\\n state\\n securityVulnerability { \\n package { name } \\n }\\n securityAdvisory { \\n cvss { score }\\n ghsaId \\n }\\n }\\n }\\n }\\n }"}'
|
||||
|
||||
const response = {
|
||||
data: {
|
||||
repository: {
|
||||
vulnerabilityAlerts: {
|
||||
nodes: [
|
||||
{
|
||||
vulnerableManifestFilename: 'package.json',
|
||||
vulnerableManifestPath: 'wwwroot/package.json',
|
||||
vulnerableRequirements: '= 4.0.1',
|
||||
state: 'DISMISSED',
|
||||
securityVulnerability: { package: { name: 'coffee-script' } },
|
||||
securityAdvisory: { cvss: { score: 4.5 }, ghsaId: 'FOO' }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('it returns the alert state if it matches all 3', async () => {
|
||||
nock('https://api.github.com').post('/graphql', query)
|
||||
.reply(200, response)
|
||||
|
||||
expect(await getAlert('coffee-script', '4.0.1', '/wwwroot', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: 'DISMISSED', cvss: 4.5, ghsaId: 'FOO' })
|
||||
})
|
||||
|
||||
test('it returns the alert state if it matches 2 and the version is blank', async () => {
|
||||
nock('https://api.github.com').post('/graphql', query)
|
||||
.reply(200, response)
|
||||
|
||||
expect(await getAlert('coffee-script', '', '/wwwroot', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: 'DISMISSED', cvss: 4.5, ghsaId: 'FOO' })
|
||||
})
|
||||
|
||||
test('it returns default if it does not match the version', async () => {
|
||||
nock('https://api.github.com').post('/graphql', query)
|
||||
.reply(200, response)
|
||||
|
||||
expect(await getAlert('coffee-script', '4.0.2', '/wwwroot', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: '', cvss: 0, ghsaId: '' })
|
||||
})
|
||||
|
||||
test('it returns default if it does not match the directory', async () => {
|
||||
nock('https://api.github.com').post('/graphql', query)
|
||||
.reply(200, response)
|
||||
|
||||
expect(await getAlert('coffee-script', '4.0.1', '/', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: '', cvss: 0, ghsaId: '' })
|
||||
})
|
||||
|
||||
test('it returns default if it does not match the name', async () => {
|
||||
nock('https://api.github.com').post('/graphql', query)
|
||||
.reply(200, response)
|
||||
|
||||
expect(await getAlert('coffee', '4.0.1', '/wwwroot', mockGitHubClient, mockGitHubPullContext())).toEqual({ alertState: '', cvss: 0, ghsaId: '' })
|
||||
})
|
||||
|
||||
test('trimSlashes should only trim slashes from both ends', () => {
|
||||
expect(trimSlashes('')).toEqual('')
|
||||
expect(trimSlashes('///')).toEqual('')
|
||||
expect(trimSlashes('/abc/')).toEqual('abc')
|
||||
expect(trimSlashes('/a/b/c/')).toEqual('a/b/c')
|
||||
expect(trimSlashes('//a//b//c//')).toEqual('a//b//c')
|
||||
})
|
||||
|
||||
const mockGitHubClient = github.getOctokit('mock-token')
|
||||
|
||||
function mockGitHubOtherContext (): Context {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as core from '@actions/core'
|
||||
import { GitHub } from '@actions/github/lib/utils'
|
||||
import { Context } from '@actions/github/lib/context'
|
||||
import type { dependencyAlert } from './update_metadata'
|
||||
|
||||
const DEPENDABOT_LOGIN = 'dependabot[bot]'
|
||||
|
||||
@@ -61,3 +62,41 @@ function warnOtherCommits (): void {
|
||||
'any non-Dependabot changes.'
|
||||
)
|
||||
}
|
||||
|
||||
export async function getAlert (name: string, version: string, directory: string, client: InstanceType<typeof GitHub>, context: Context): Promise<dependencyAlert> {
|
||||
const alerts: any = await client.graphql(`
|
||||
{
|
||||
repository(owner: "${context.repo.owner}", name: "${context.repo.repo}") {
|
||||
vulnerabilityAlerts(first: 100) {
|
||||
nodes {
|
||||
vulnerableManifestFilename
|
||||
vulnerableManifestPath
|
||||
vulnerableRequirements
|
||||
state
|
||||
securityVulnerability {
|
||||
package { name }
|
||||
}
|
||||
securityAdvisory {
|
||||
cvss { score }
|
||||
ghsaId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
const nodes = alerts?.repository?.vulnerabilityAlerts?.nodes
|
||||
const found = nodes.find(a => (version === '' || a.vulnerableRequirements === `= ${version}`) &&
|
||||
trimSlashes(a.vulnerableManifestPath) === `${trimSlashes(directory)}/${a.vulnerableManifestFilename}` &&
|
||||
a.securityVulnerability.package.name === name)
|
||||
|
||||
return {
|
||||
alertState: found?.state ?? '',
|
||||
ghsaId: found?.securityAdvisory.ghsaId ?? '',
|
||||
cvss: found?.securityAdvisory.cvss.score ?? 0.0
|
||||
}
|
||||
}
|
||||
|
||||
export function trimSlashes (value: string): string {
|
||||
return value.replace(/^\/+/, '').replace(/\/+$/, '')
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as dotenv from 'dotenv'
|
||||
import { Argv } from 'yargs'
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
|
||||
import { getMessage } from './dependabot/verified_commits'
|
||||
import { getMessage, getAlert } from './dependabot/verified_commits'
|
||||
import { parse } from './dependabot/update_metadata'
|
||||
import { getBranchNames, parseNwo } from './dependabot/util'
|
||||
|
||||
@@ -51,8 +51,9 @@ async function check (args: any): Promise<void> {
|
||||
if (commitMessage) {
|
||||
console.log('This appears to be a valid Dependabot Pull Request.')
|
||||
const branchNames = getBranchNames(newContext)
|
||||
const lookupFn = (name, version, directory) => getAlert(name, version, directory, githubClient, actionContext)
|
||||
|
||||
const updatedDependencies = parse(commitMessage, branchNames.headName, branchNames.baseName)
|
||||
const updatedDependencies = await parse(commitMessage, branchNames.headName, branchNames.baseName, lookupFn)
|
||||
|
||||
if (updatedDependencies.length > 0) {
|
||||
console.log('Updated dependencies:')
|
||||
|
||||
@@ -23,6 +23,7 @@ test('it early exits with an error if github-token is not set', async () => {
|
||||
)
|
||||
/* eslint-disable no-unused-expressions */
|
||||
expect(dependabotCommits.getMessage).not.toHaveBeenCalled
|
||||
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
|
||||
/* eslint-enable no-unused-expressions */
|
||||
})
|
||||
|
||||
@@ -38,6 +39,9 @@ test('it does nothing if the PR is not verified as from Dependabot', async () =>
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('PR is not from Dependabot, nothing to do.')
|
||||
)
|
||||
/* eslint-disable no-unused-expressions */
|
||||
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
|
||||
/* eslint-enable no-unused-expressions */
|
||||
})
|
||||
|
||||
test('it does nothing if there is no metadata in the commit', async () => {
|
||||
@@ -52,6 +56,9 @@ test('it does nothing if there is no metadata in the commit', async () => {
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('PR does not contain metadata, nothing to do.')
|
||||
)
|
||||
/* eslint-disable no-unused-expressions */
|
||||
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
|
||||
/* eslint-enable no-unused-expressions */
|
||||
})
|
||||
|
||||
test('it sets the updated dependency as an output for subsequent actions', async () => {
|
||||
@@ -69,12 +76,16 @@ test('it sets the updated dependency as an output for subsequent actions', async
|
||||
'...\n' +
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
const mockAlert = { alertState: 'FIXED', ghsaId: 'GSHA', cvss: 3.4 }
|
||||
|
||||
jest.spyOn(core, 'getInput').mockReturnValue('mock-token')
|
||||
jest.spyOn(core, 'getInput').mockImplementation(jest.fn((name) => { return name === 'github-token' ? 'mock-token' : '' }))
|
||||
jest.spyOn(util, 'getBranchNames').mockReturnValue({ headName: 'dependabot|nuget|feature1', baseName: 'main' })
|
||||
jest.spyOn(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
|
||||
() => Promise.resolve(mockCommitMessage)
|
||||
))
|
||||
jest.spyOn(dependabotCommits, 'getAlert').mockImplementation(jest.fn(
|
||||
() => Promise.resolve(mockAlert)
|
||||
))
|
||||
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
|
||||
|
||||
await run()
|
||||
@@ -92,7 +103,12 @@ test('it sets the updated dependency as an output for subsequent actions', async
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: '/',
|
||||
packageEcosystem: 'nuget',
|
||||
targetBranch: 'main'
|
||||
targetBranch: 'main',
|
||||
prevVersion: '4.0.1',
|
||||
newVersion: '4.2.2',
|
||||
alertState: '',
|
||||
ghsaId: '',
|
||||
cvss: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -103,10 +119,16 @@ test('it sets the updated dependency as an output for subsequent actions', async
|
||||
expect(core.setOutput).toBeCalledWith('directory', '/')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', 'nuget')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', 'main')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '4.0.1')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '4.2.2')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', '')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', '')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 0)
|
||||
})
|
||||
|
||||
test('if there are multiple dependencies, it summarizes them', async () => {
|
||||
const mockCommitMessage =
|
||||
'Bump coffee-rails from 4.0.1 to 4.2.2 in api/main\n' +
|
||||
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
|
||||
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
|
||||
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
|
||||
@@ -123,12 +145,16 @@ test('if there are multiple dependencies, it summarizes them', async () => {
|
||||
'...\n' +
|
||||
'\n' +
|
||||
'Signed-off-by: dependabot[bot] <support@github.com>'
|
||||
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(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
|
||||
() => Promise.resolve(mockCommitMessage)
|
||||
))
|
||||
jest.spyOn(dependabotCommits, 'getAlert').mockImplementation(jest.fn(
|
||||
() => Promise.resolve(mockAlert)
|
||||
))
|
||||
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
|
||||
|
||||
await run()
|
||||
@@ -144,17 +170,27 @@ test('if there are multiple dependencies, it summarizes them', async () => {
|
||||
dependencyName: 'coffee-rails',
|
||||
dependencyType: 'direct:production',
|
||||
updateType: 'version-update:semver-minor',
|
||||
directory: 'api/main',
|
||||
directory: '/api/main',
|
||||
packageEcosystem: 'npm_and_yarn',
|
||||
targetBranch: 'trunk'
|
||||
targetBranch: 'trunk',
|
||||
prevVersion: '4.0.1',
|
||||
newVersion: '4.2.2',
|
||||
alertState: '',
|
||||
ghsaId: '',
|
||||
cvss: 0
|
||||
},
|
||||
{
|
||||
dependencyName: 'coffeescript',
|
||||
dependencyType: 'indirect',
|
||||
updateType: 'version-update:semver-major',
|
||||
directory: 'api/main',
|
||||
directory: '/api/main',
|
||||
packageEcosystem: 'npm_and_yarn',
|
||||
targetBranch: 'trunk'
|
||||
targetBranch: 'trunk',
|
||||
prevVersion: '',
|
||||
newVersion: '',
|
||||
alertState: '',
|
||||
ghsaId: '',
|
||||
cvss: 0
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -162,9 +198,14 @@ test('if there are multiple dependencies, it summarizes them', async () => {
|
||||
expect(core.setOutput).toBeCalledWith('dependency-names', 'coffee-rails, coffeescript')
|
||||
expect(core.setOutput).toBeCalledWith('dependency-type', 'direct:production')
|
||||
expect(core.setOutput).toBeCalledWith('update-type', 'version-update:semver-major')
|
||||
expect(core.setOutput).toBeCalledWith('directory', 'api/main')
|
||||
expect(core.setOutput).toBeCalledWith('directory', '/api/main')
|
||||
expect(core.setOutput).toBeCalledWith('package-ecosystem', 'npm_and_yarn')
|
||||
expect(core.setOutput).toBeCalledWith('target-branch', 'trunk')
|
||||
expect(core.setOutput).toBeCalledWith('previous-version', '4.0.1')
|
||||
expect(core.setOutput).toBeCalledWith('new-version', '4.2.2')
|
||||
expect(core.setOutput).toBeCalledWith('alert-state', '')
|
||||
expect(core.setOutput).toBeCalledWith('ghsa-id', '')
|
||||
expect(core.setOutput).toBeCalledWith('cvss', 0)
|
||||
})
|
||||
|
||||
test('it sets the action to failed if there is an unexpected exception', async () => {
|
||||
@@ -179,6 +220,9 @@ test('it sets the action to failed if there is an unexpected exception', async (
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Something bad happened!')
|
||||
)
|
||||
/* eslint-disable no-unused-expressions */
|
||||
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
|
||||
/* eslint-enable no-unused-expressions */
|
||||
})
|
||||
|
||||
test('it sets the action to failed if there is a request error', async () => {
|
||||
@@ -202,4 +246,7 @@ test('it sets the action to failed if there is a request error', async () => {
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
expect.stringContaining('(500) Something bad happened!')
|
||||
)
|
||||
/* eslint-disable no-unused-expressions */
|
||||
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
|
||||
/* eslint-enable no-unused-expressions */
|
||||
})
|
||||
|
||||
@@ -24,12 +24,16 @@ export async function run (): Promise<void> {
|
||||
// Validate the job
|
||||
const commitMessage = await verifiedCommits.getMessage(githubClient, github.context)
|
||||
const branchNames = util.getBranchNames(github.context)
|
||||
let alertLookup: updateMetadata.alertLookup | undefined
|
||||
if (core.getInput('alert-lookup')) {
|
||||
alertLookup = (name, version, directory) => verifiedCommits.getAlert(name, version, directory, githubClient, github.context)
|
||||
}
|
||||
|
||||
if (commitMessage) {
|
||||
// Parse metadata
|
||||
core.info('Parsing Dependabot metadata')
|
||||
|
||||
const updatedDependencies = updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName)
|
||||
const updatedDependencies = await updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName, alertLookup)
|
||||
|
||||
if (updatedDependencies.length > 0) {
|
||||
output.set(updatedDependencies)
|
||||
|
||||
Reference in New Issue
Block a user