resolve merge conflicts
This commit is contained in:
@@ -129,11 +129,12 @@ Start by specifying that you will be using an external configuration file:
|
|||||||
config-file: './.github/dependency-review-config.yml'
|
config-file: './.github/dependency-review-config.yml'
|
||||||
```
|
```
|
||||||
|
|
||||||
And then create the file in the path you just specified:
|
And then create the file in the path you just specified. Please note
|
||||||
|
that the **option names in external files use underscores instead of dashes**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
fail-on-severity: 'critical'
|
fail_on_severity: 'critical'
|
||||||
allow-licenses:
|
allow_licenses:
|
||||||
- 'GPL-3.0'
|
- 'GPL-3.0'
|
||||||
- 'BSD-3-Clause'
|
- 'BSD-3-Clause'
|
||||||
- 'MIT'
|
- 'MIT'
|
||||||
|
|||||||
@@ -2,35 +2,7 @@ import {expect, test, beforeEach} from '@jest/globals'
|
|||||||
import {readConfig} from '../src/config'
|
import {readConfig} from '../src/config'
|
||||||
import {getRefs} from '../src/git-refs'
|
import {getRefs} from '../src/git-refs'
|
||||||
import * as Utils from '../src/utils'
|
import * as Utils from '../src/utils'
|
||||||
|
import {setInput, clearInputs} from './test-helpers'
|
||||||
// GitHub Action inputs come in the form of environment variables
|
|
||||||
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY)
|
|
||||||
function setInput(input: string, value: string): void {
|
|
||||||
process.env[`INPUT_${input.toUpperCase()}`] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want a clean ENV before each test. We use `delete`
|
|
||||||
// since we want `undefined` values and not empty strings.
|
|
||||||
function clearInputs(): void {
|
|
||||||
const allowedOptions = [
|
|
||||||
'FAIL-ON-SEVERITY',
|
|
||||||
'FAIL-ON-SCOPES',
|
|
||||||
'ALLOW-LICENSES',
|
|
||||||
'DENY-LICENSES',
|
|
||||||
'ALLOW-GHSAS',
|
|
||||||
'LICENSE-CHECK',
|
|
||||||
'VULNERABILITY-CHECK',
|
|
||||||
'CONFIG-FILE',
|
|
||||||
'BASE-REF',
|
|
||||||
'HEAD-REF',
|
|
||||||
'COMMENT-SUMMARY-IN-PR'
|
|
||||||
]
|
|
||||||
|
|
||||||
// eslint-disable-next-line github/array-foreach
|
|
||||||
allowedOptions.forEach(option => {
|
|
||||||
delete process.env[`INPUT_${option.toUpperCase()}`]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
|
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
|
||||||
@@ -105,60 +77,6 @@ test('it raises an error when no refs are provided and the event is not a pull r
|
|||||||
).toThrow()
|
).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it reads an external config file', async () => {
|
|
||||||
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
|
||||||
|
|
||||||
const config = await readConfig()
|
|
||||||
expect(config.fail_on_severity).toEqual('critical')
|
|
||||||
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('raises an error when the config file was not found', async () => {
|
|
||||||
setInput('config-file', 'fixtures/i-dont-exist')
|
|
||||||
await expect(readConfig()).rejects.toThrow(/Unable to fetch/)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it parses options from both sources', async () => {
|
|
||||||
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
|
||||||
|
|
||||||
let config = await readConfig()
|
|
||||||
expect(config.fail_on_severity).toEqual('critical')
|
|
||||||
|
|
||||||
setInput('base-ref', 'a-custom-base-ref')
|
|
||||||
config = await readConfig()
|
|
||||||
expect(config.base_ref).toEqual('a-custom-base-ref')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('in case of conflicts, the inline config is the source of truth', async () => {
|
|
||||||
setInput('fail-on-severity', 'low')
|
|
||||||
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml') // this will set fail-on-severity to 'critical'
|
|
||||||
|
|
||||||
const config = await readConfig()
|
|
||||||
expect(config.fail_on_severity).toEqual('low')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it uses the default values when loading external files', async () => {
|
|
||||||
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
|
||||||
let config = await readConfig()
|
|
||||||
expect(config.allow_licenses).toEqual(undefined)
|
|
||||||
expect(config.deny_licenses).toEqual(undefined)
|
|
||||||
|
|
||||||
setInput('config-file', './__tests__/fixtures/license-config-sample.yml')
|
|
||||||
config = await readConfig()
|
|
||||||
expect(config.fail_on_severity).toEqual('low')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it accepts an external configuration filename', async () => {
|
|
||||||
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
|
||||||
const config = await readConfig()
|
|
||||||
expect(config.fail_on_severity).toEqual('critical')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it raises an error when given an unknown severity in an external config file', async () => {
|
|
||||||
setInput('config-file', './__tests__/fixtures/invalid-severity-config.yml')
|
|
||||||
await expect(readConfig()).rejects.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it defaults to runtime scope', async () => {
|
test('it defaults to runtime scope', async () => {
|
||||||
const config = await readConfig()
|
const config = await readConfig()
|
||||||
expect(config.fail_on_scopes).toEqual(['runtime'])
|
expect(config.fail_on_scopes).toEqual(['runtime'])
|
||||||
@@ -234,16 +152,6 @@ test('it is not possible to disable both checks', async () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it supports comma-separated lists', async () => {
|
|
||||||
setInput(
|
|
||||||
'config-file',
|
|
||||||
'./__tests__/fixtures/inline-license-config-sample.yml'
|
|
||||||
)
|
|
||||||
const config = await readConfig()
|
|
||||||
|
|
||||||
expect(config.allow_licenses).toEqual(['MIT', 'GPL-2.0-only'])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('licenses that are not valid SPDX licenses', () => {
|
describe('licenses that are not valid SPDX licenses', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(false)
|
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(false)
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import {expect, test, beforeEach} from '@jest/globals'
|
||||||
|
import {readConfig} from '../src/config'
|
||||||
|
import * as Utils from '../src/utils'
|
||||||
|
import {setInput, clearInputs} from './test-helpers'
|
||||||
|
|
||||||
|
const externalConfig = `fail_on_severity: 'high'
|
||||||
|
allow_licenses: ['GPL-2.0-only']
|
||||||
|
`
|
||||||
|
const mockOctokit = {
|
||||||
|
rest: {
|
||||||
|
repos: {
|
||||||
|
getContent: jest.fn().mockReturnValue({data: externalConfig})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('octokit', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||||
|
Octokit: class {
|
||||||
|
constructor() {
|
||||||
|
return mockOctokit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clearInputs()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it reads an external config file', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
||||||
|
|
||||||
|
const config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
|
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('raises an error when the config file was not found', async () => {
|
||||||
|
setInput('config-file', 'fixtures/i-dont-exist')
|
||||||
|
await expect(readConfig()).rejects.toThrow(/Unable to fetch/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it parses options from both sources', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
||||||
|
|
||||||
|
let config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
|
|
||||||
|
setInput('base-ref', 'a-custom-base-ref')
|
||||||
|
config = await readConfig()
|
||||||
|
expect(config.base_ref).toEqual('a-custom-base-ref')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('in case of conflicts, the inline config is the source of truth', async () => {
|
||||||
|
setInput('fail-on-severity', 'low')
|
||||||
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml') // this will set fail-on-severity to 'critical'
|
||||||
|
|
||||||
|
const config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('low')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it uses the default values when loading external files', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
||||||
|
let config = await readConfig()
|
||||||
|
expect(config.allow_licenses).toEqual(undefined)
|
||||||
|
expect(config.deny_licenses).toEqual(undefined)
|
||||||
|
|
||||||
|
setInput('config-file', './__tests__/fixtures/license-config-sample.yml')
|
||||||
|
config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('low')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it accepts an external configuration filename', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
||||||
|
const config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it raises an error when given an unknown severity in an external config file', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/invalid-severity-config.yml')
|
||||||
|
await expect(readConfig()).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it supports comma-separated lists', async () => {
|
||||||
|
setInput(
|
||||||
|
'config-file',
|
||||||
|
'./__tests__/fixtures/inline-license-config-sample.yml'
|
||||||
|
)
|
||||||
|
const config = await readConfig()
|
||||||
|
|
||||||
|
expect(config.allow_licenses).toEqual(['MIT', 'GPL-2.0-only'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it reads a config file hosted in another repo', async () => {
|
||||||
|
setInput(
|
||||||
|
'config-file',
|
||||||
|
'future-funk/anyone-cualkiera/external-config.yml@main'
|
||||||
|
)
|
||||||
|
setInput('external-repo-token', 'gh_viptoken')
|
||||||
|
|
||||||
|
const config = await readConfig()
|
||||||
|
|
||||||
|
expect(config.fail_on_severity).toEqual('high')
|
||||||
|
expect(config.allow_licenses).toEqual(['GPL-2.0-only'])
|
||||||
|
})
|
||||||
@@ -1 +1 @@
|
|||||||
allow-licenses: MIT, GPL-2.0-only
|
allow-licenses: "MIT, GPL-2.0-only"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
fail-on-severity: 'so many zombies'
|
fail_on_severity: 'so many zombies'
|
||||||
deny-licenses:
|
deny_licenses:
|
||||||
- MIT
|
- MIT
|
||||||
|
|||||||
@@ -27,6 +27,45 @@ const defaultConfig: ConfigurationOptions = {
|
|||||||
comment_summary_in_pr: true
|
comment_summary_in_pr: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changesWithEmptyManifests: Changes = [
|
||||||
|
{
|
||||||
|
change_type: 'added',
|
||||||
|
manifest: '',
|
||||||
|
ecosystem: 'unknown',
|
||||||
|
name: 'castore',
|
||||||
|
version: '0.1.17',
|
||||||
|
package_url: 'pkg:hex/[email protected]',
|
||||||
|
license: null,
|
||||||
|
source_repository_url: null,
|
||||||
|
scope: 'runtime',
|
||||||
|
vulnerabilities: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
change_type: 'added',
|
||||||
|
manifest: '',
|
||||||
|
ecosystem: 'unknown',
|
||||||
|
name: 'connection',
|
||||||
|
version: '1.1.0',
|
||||||
|
package_url: 'pkg:hex/[email protected]',
|
||||||
|
license: null,
|
||||||
|
source_repository_url: null,
|
||||||
|
scope: 'runtime',
|
||||||
|
vulnerabilities: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
change_type: 'added',
|
||||||
|
manifest: 'python/dist-info/METADATA',
|
||||||
|
ecosystem: 'pip',
|
||||||
|
name: 'pygments',
|
||||||
|
version: '2.6.1',
|
||||||
|
package_url: 'pkg:pypi/[email protected]',
|
||||||
|
license: 'BSD-2-Clause',
|
||||||
|
source_repository_url: 'https://github.com/pygments/pygments',
|
||||||
|
scope: 'runtime',
|
||||||
|
vulnerabilities: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
test('prints headline as h1', () => {
|
test('prints headline as h1', () => {
|
||||||
summary.addSummaryToSummary(
|
summary.addSummaryToSummary(
|
||||||
emptyChanges,
|
emptyChanges,
|
||||||
@@ -65,6 +104,22 @@ test('only includes "No license issues found"-message if "vulnerability_check" i
|
|||||||
expect(text).toContain('✅ No license issues found.')
|
expect(text).toContain('✅ No license issues found.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('groups dependencies with empty manifest paths together', () => {
|
||||||
|
summary.addSummaryToSummary(
|
||||||
|
changesWithEmptyManifests,
|
||||||
|
emptyInvalidLicenseChanges,
|
||||||
|
defaultConfig
|
||||||
|
)
|
||||||
|
summary.addScannedDependencies(changesWithEmptyManifests)
|
||||||
|
const text = core.summary.stringify()
|
||||||
|
|
||||||
|
expect(text).toContain('<summary>Unnamed Manifest</summary>')
|
||||||
|
expect(text).toContain('castore')
|
||||||
|
expect(text).toContain('connection')
|
||||||
|
expect(text).toContain('<summary>python/dist-info/METADATA</summary>')
|
||||||
|
expect(text).toContain('pygments')
|
||||||
|
})
|
||||||
|
|
||||||
test('does not include status section if nothing was found', () => {
|
test('does not include status section if nothing was found', () => {
|
||||||
summary.addSummaryToSummary(
|
summary.addSummaryToSummary(
|
||||||
emptyChanges,
|
emptyChanges,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// GitHub Action inputs come in the form of environment variables
|
||||||
|
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY)
|
||||||
|
export function setInput(input: string, value: string): void {
|
||||||
|
process.env[`INPUT_${input.toUpperCase()}`] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want a clean ENV before each test. We use `delete`
|
||||||
|
// since we want `undefined` values and not empty strings.
|
||||||
|
export function clearInputs(): void {
|
||||||
|
const allowedOptions = [
|
||||||
|
'FAIL-ON-SEVERITY',
|
||||||
|
'FAIL-ON-SCOPES',
|
||||||
|
'ALLOW-LICENSES',
|
||||||
|
'DENY-LICENSES',
|
||||||
|
'ALLOW-GHSAS',
|
||||||
|
'LICENSE-CHECK',
|
||||||
|
'VULNERABILITY-CHECK',
|
||||||
|
'CONFIG-FILE',
|
||||||
|
'BASE-REF',
|
||||||
|
'HEAD-REF',
|
||||||
|
'COMMENT-SUMMARY-IN-PR'
|
||||||
|
]
|
||||||
|
|
||||||
|
// eslint-disable-next-line github/array-foreach
|
||||||
|
allowedOptions.forEach(option => {
|
||||||
|
delete process.env[`INPUT_${option.toUpperCase()}`]
|
||||||
|
})
|
||||||
|
}
|
||||||
+2
-2
@@ -1,3 +1,5 @@
|
|||||||
|
# Avoid using default values for options here since they will
|
||||||
|
# end up overriding external configurations.
|
||||||
name: 'Dependency Review'
|
name: 'Dependency Review'
|
||||||
description: 'Prevent the introduction of dependencies with known vulnerabilities'
|
description: 'Prevent the introduction of dependencies with known vulnerabilities'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
@@ -9,11 +11,9 @@ inputs:
|
|||||||
fail-on-severity:
|
fail-on-severity:
|
||||||
description: Don't block PRs below this severity. Possible values are `low`, `moderate`, `high`, `critical`.
|
description: Don't block PRs below this severity. Possible values are `low`, `moderate`, `high`, `critical`.
|
||||||
required: false
|
required: false
|
||||||
default: 'low'
|
|
||||||
fail-on-scopes:
|
fail-on-scopes:
|
||||||
description: Dependency scopes to block PRs on. Comma-separated list. Possible values are 'unknown', 'runtime', and 'development' (e.g. "runtime, development")
|
description: Dependency scopes to block PRs on. Comma-separated list. Possible values are 'unknown', 'runtime', and 'development' (e.g. "runtime, development")
|
||||||
required: false
|
required: false
|
||||||
default: 'runtime'
|
|
||||||
base-ref:
|
base-ref:
|
||||||
description: The base git ref to be used for this check. Has a default value when the workflow event is `pull_request` or `pull_request_target`. Must be provided otherwise.
|
description: The base git ref to be used for this check. Has a default value when the workflow event is `pull_request` or `pull_request_target`. Must be provided otherwise.
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
+52
-8
@@ -181,15 +181,28 @@ const githubUtils = __importStar(__nccwpck_require__(3030));
|
|||||||
const retry = __importStar(__nccwpck_require__(6298));
|
const retry = __importStar(__nccwpck_require__(6298));
|
||||||
const schemas_1 = __nccwpck_require__(8774);
|
const schemas_1 = __nccwpck_require__(8774);
|
||||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||||
|
const SnapshotWarningsHeader = 'x-github-dependency-graph-snapshot-warnings';
|
||||||
const octo = new retryingOctokit(githubUtils.getOctokitOptions(core.getInput('repo-token', { required: true })));
|
const octo = new retryingOctokit(githubUtils.getOctokitOptions(core.getInput('repo-token', { required: true })));
|
||||||
function compare({ owner, repo, baseRef, headRef }) {
|
function compare({ owner, repo, baseRef, headRef }) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const changes = yield octo.paginate('GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}', {
|
let snapshot_warnings = '';
|
||||||
|
const changes = yield octo.paginate({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/repos/{owner}/{repo}/dependency-graph/compare/{basehead}',
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
basehead: `${baseRef}...${headRef}`
|
basehead: `${baseRef}...${headRef}`
|
||||||
|
}, response => {
|
||||||
|
if (response.headers[SnapshotWarningsHeader] &&
|
||||||
|
typeof response.headers[SnapshotWarningsHeader] === 'string') {
|
||||||
|
snapshot_warnings = Buffer.from(response.headers[SnapshotWarningsHeader], 'base64').toString('utf-8');
|
||||||
|
}
|
||||||
|
return schemas_1.ChangesSchema.parse(response.data);
|
||||||
|
});
|
||||||
|
return schemas_1.ComparisonResponseSchema.parse({
|
||||||
|
changes,
|
||||||
|
snapshot_warnings
|
||||||
});
|
});
|
||||||
return schemas_1.ChangesSchema.parse(changes);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports.compare = compare;
|
exports.compare = compare;
|
||||||
@@ -472,12 +485,14 @@ function run() {
|
|||||||
try {
|
try {
|
||||||
const config = yield (0, config_1.readConfig)();
|
const config = yield (0, config_1.readConfig)();
|
||||||
const refs = (0, git_refs_1.getRefs)(config, github.context);
|
const refs = (0, git_refs_1.getRefs)(config, github.context);
|
||||||
const changes = yield dependencyGraph.compare({
|
const comparison = yield dependencyGraph.compare({
|
||||||
owner: github.context.repo.owner,
|
owner: github.context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: github.context.repo.repo,
|
||||||
baseRef: refs.base,
|
baseRef: refs.base,
|
||||||
headRef: refs.head
|
headRef: refs.head
|
||||||
});
|
});
|
||||||
|
const changes = comparison.changes;
|
||||||
|
const snapshot_warnings = comparison.snapshot_warnings;
|
||||||
if (!changes) {
|
if (!changes) {
|
||||||
core.info('No Dependency Changes found. Skipping Dependency Review.');
|
core.info('No Dependency Changes found. Skipping Dependency Review.');
|
||||||
return;
|
return;
|
||||||
@@ -494,6 +509,9 @@ function run() {
|
|||||||
licenseExclusions: config.allow_dependencies_licenses
|
licenseExclusions: config.allow_dependencies_licenses
|
||||||
});
|
});
|
||||||
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
||||||
|
if (snapshot_warnings) {
|
||||||
|
summary.addSnapshotWarnings(snapshot_warnings);
|
||||||
|
}
|
||||||
if (config.vulnerability_check) {
|
if (config.vulnerability_check) {
|
||||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
|
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
|
||||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity);
|
printVulnerabilitiesBlock(vulnerableChanges, minSeverity);
|
||||||
@@ -651,7 +669,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.ChangesSchema = exports.ConfigurationOptionsSchema = exports.PullRequestSchema = exports.ChangeSchema = exports.SeveritySchema = exports.SCOPES = exports.SEVERITIES = void 0;
|
exports.ComparisonResponseSchema = exports.ChangesSchema = exports.ConfigurationOptionsSchema = exports.PullRequestSchema = exports.ChangeSchema = exports.SeveritySchema = exports.SCOPES = exports.SEVERITIES = void 0;
|
||||||
const z = __importStar(__nccwpck_require__(3301));
|
const z = __importStar(__nccwpck_require__(3301));
|
||||||
exports.SEVERITIES = ['critical', 'high', 'moderate', 'low'];
|
exports.SEVERITIES = ['critical', 'high', 'moderate', 'low'];
|
||||||
exports.SCOPES = ['unknown', 'runtime', 'development'];
|
exports.SCOPES = ['unknown', 'runtime', 'development'];
|
||||||
@@ -718,6 +736,10 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||||
|
exports.ComparisonResponseSchema = z.object({
|
||||||
|
changes: z.array(exports.ChangeSchema),
|
||||||
|
snapshot_warnings: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -751,7 +773,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.addScannedDependencies = exports.addLicensesToSummary = exports.addChangeVulnerabilitiesToSummary = exports.addSummaryToSummary = void 0;
|
exports.addSnapshotWarnings = exports.addScannedDependencies = exports.addLicensesToSummary = exports.addChangeVulnerabilitiesToSummary = exports.addSummaryToSummary = void 0;
|
||||||
const core = __importStar(__nccwpck_require__(2186));
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
const utils_1 = __nccwpck_require__(918);
|
const utils_1 = __nccwpck_require__(918);
|
||||||
const icons = {
|
const icons = {
|
||||||
@@ -912,6 +934,20 @@ function addScannedDependencies(changes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.addScannedDependencies = addScannedDependencies;
|
exports.addScannedDependencies = addScannedDependencies;
|
||||||
|
function addSnapshotWarnings(warnings) {
|
||||||
|
// For now, we want to ignore warnings that just complain
|
||||||
|
// about missing snapshots on the head SHA. This is a product
|
||||||
|
// decision to avoid presenting warnings to users who simply
|
||||||
|
// don't use snapshots.
|
||||||
|
const ignore_regex = new RegExp(/No.*snapshot.*found.*head.*/, 'i');
|
||||||
|
if (ignore_regex.test(warnings)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.summary.addHeading('Snapshot Warnings', 2);
|
||||||
|
core.summary.addQuote(`${icons.warning}: ${warnings}`);
|
||||||
|
core.summary.addRaw('Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.');
|
||||||
|
}
|
||||||
|
exports.addSnapshotWarnings = addSnapshotWarnings;
|
||||||
function countLicenseIssues(invalidLicenseChanges) {
|
function countLicenseIssues(invalidLicenseChanges) {
|
||||||
return Object.values(invalidLicenseChanges).reduce((acc, val) => acc + val.length, 0);
|
return Object.values(invalidLicenseChanges).reduce((acc, val) => acc + val.length, 0);
|
||||||
}
|
}
|
||||||
@@ -965,7 +1001,9 @@ function groupDependenciesByManifest(changes) {
|
|||||||
var _a;
|
var _a;
|
||||||
const dependencies = new Map();
|
const dependencies = new Map();
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const manifestName = change.manifest;
|
// If the manifest is null or empty, give it a name now to avoid
|
||||||
|
// breaking the HTML rendering later
|
||||||
|
const manifestName = change.manifest || 'Unnamed Manifest';
|
||||||
if (dependencies.get(manifestName) === undefined) {
|
if (dependencies.get(manifestName) === undefined) {
|
||||||
dependencies.set(manifestName, []);
|
dependencies.set(manifestName, []);
|
||||||
}
|
}
|
||||||
@@ -45496,7 +45534,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.ChangesSchema = exports.ConfigurationOptionsSchema = exports.PullRequestSchema = exports.ChangeSchema = exports.SeveritySchema = exports.SCOPES = exports.SEVERITIES = void 0;
|
exports.ComparisonResponseSchema = exports.ChangesSchema = exports.ConfigurationOptionsSchema = exports.PullRequestSchema = exports.ChangeSchema = exports.SeveritySchema = exports.SCOPES = exports.SEVERITIES = void 0;
|
||||||
const z = __importStar(__nccwpck_require__(3301));
|
const z = __importStar(__nccwpck_require__(3301));
|
||||||
exports.SEVERITIES = ['critical', 'high', 'moderate', 'low'];
|
exports.SEVERITIES = ['critical', 'high', 'moderate', 'low'];
|
||||||
exports.SCOPES = ['unknown', 'runtime', 'development'];
|
exports.SCOPES = ['unknown', 'runtime', 'development'];
|
||||||
@@ -45563,6 +45601,10 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||||
|
exports.ComparisonResponseSchema = z.object({
|
||||||
|
changes: z.array(exports.ChangeSchema),
|
||||||
|
snapshot_warnings: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -45607,7 +45649,9 @@ function groupDependenciesByManifest(changes) {
|
|||||||
var _a;
|
var _a;
|
||||||
const dependencies = new Map();
|
const dependencies = new Map();
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const manifestName = change.manifest;
|
// If the manifest is null or empty, give it a name now to avoid
|
||||||
|
// breaking the HTML rendering later
|
||||||
|
const manifestName = change.manifest || 'Unnamed Manifest';
|
||||||
if (dependencies.get(manifestName) === undefined) {
|
if (dependencies.get(manifestName) === undefined) {
|
||||||
dependencies.set(manifestName, []);
|
dependencies.set(manifestName, []);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Generated
+6685
-212
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -32,29 +32,31 @@
|
|||||||
"ansi-styles": "^6.2.1",
|
"ansi-styles": "^6.2.1",
|
||||||
"got": "^12.6.0",
|
"got": "^12.6.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"octokit": "^2.0.14",
|
"octokit": "^2.0.16",
|
||||||
"packageurl-js": "^1.0.2",
|
"packageurl-js": "^1.0.2",
|
||||||
"spdx-expression-parse": "^3.0.1",
|
"spdx-expression-parse": "^3.0.1",
|
||||||
"spdx-satisfies": "^5.0.1",
|
"spdx-satisfies": "^5.0.1",
|
||||||
"yaml": "^2.2.1",
|
"yaml": "^2.3.1",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^16.18.23",
|
"@types/node": "^16.18.34",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||||
|
"@typescript-eslint/parser": "^5.48.0",
|
||||||
"@types/spdx-expression-parse": "^3.0.2",
|
"@types/spdx-expression-parse": "^3.0.2",
|
||||||
"@types/spdx-satisfies": "^0.1.0",
|
"@types/spdx-satisfies": "^0.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
||||||
"@typescript-eslint/parser": "^5.57.0",
|
"@typescript-eslint/parser": "^5.59.8",
|
||||||
"@vercel/ncc": "^0.36.1",
|
"@vercel/ncc": "^0.36.1",
|
||||||
"esbuild-register": "^3.4.2",
|
"esbuild-register": "^3.4.2",
|
||||||
"eslint": "^8.37.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-plugin-github": "^4.7.0",
|
"eslint-plugin-github": "^4.7.0",
|
||||||
"eslint-plugin-jest": "^27.2.1",
|
"eslint-plugin-jest": "^27.2.1",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"prettier": "2.8.7",
|
"prettier": "2.8.8",
|
||||||
"ts-jest": "^27.1.4",
|
"ts-jest": "^27.1.4",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-4
@@ -1,9 +1,14 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as githubUtils from '@actions/github/lib/utils'
|
import * as githubUtils from '@actions/github/lib/utils'
|
||||||
import * as retry from '@octokit/plugin-retry'
|
import * as retry from '@octokit/plugin-retry'
|
||||||
import {Changes, ChangesSchema} from './schemas'
|
import {
|
||||||
|
ChangesSchema,
|
||||||
|
ComparisonResponse,
|
||||||
|
ComparisonResponseSchema
|
||||||
|
} from './schemas'
|
||||||
|
|
||||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry)
|
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry)
|
||||||
|
const SnapshotWarningsHeader = 'x-github-dependency-graph-snapshot-warnings'
|
||||||
const octo = new retryingOctokit(
|
const octo = new retryingOctokit(
|
||||||
githubUtils.getOctokitOptions(core.getInput('repo-token', {required: true}))
|
githubUtils.getOctokitOptions(core.getInput('repo-token', {required: true}))
|
||||||
)
|
)
|
||||||
@@ -18,14 +23,31 @@ export async function compare({
|
|||||||
repo: string
|
repo: string
|
||||||
baseRef: string
|
baseRef: string
|
||||||
headRef: string
|
headRef: string
|
||||||
}): Promise<Changes> {
|
}): Promise<ComparisonResponse> {
|
||||||
|
let snapshot_warnings = ''
|
||||||
const changes = await octo.paginate(
|
const changes = await octo.paginate(
|
||||||
'GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}',
|
|
||||||
{
|
{
|
||||||
|
method: 'GET',
|
||||||
|
url: '/repos/{owner}/{repo}/dependency-graph/compare/{basehead}',
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
basehead: `${baseRef}...${headRef}`
|
basehead: `${baseRef}...${headRef}`
|
||||||
|
},
|
||||||
|
response => {
|
||||||
|
if (
|
||||||
|
response.headers[SnapshotWarningsHeader] &&
|
||||||
|
typeof response.headers[SnapshotWarningsHeader] === 'string'
|
||||||
|
) {
|
||||||
|
snapshot_warnings = Buffer.from(
|
||||||
|
response.headers[SnapshotWarningsHeader],
|
||||||
|
'base64'
|
||||||
|
).toString('utf-8')
|
||||||
|
}
|
||||||
|
return ChangesSchema.parse(response.data)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return ChangesSchema.parse(changes)
|
return ComparisonResponseSchema.parse({
|
||||||
|
changes,
|
||||||
|
snapshot_warnings
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -23,12 +23,14 @@ async function run(): Promise<void> {
|
|||||||
|
|
||||||
const refs = getRefs(config, github.context)
|
const refs = getRefs(config, github.context)
|
||||||
|
|
||||||
const changes = await dependencyGraph.compare({
|
const comparison = await dependencyGraph.compare({
|
||||||
owner: github.context.repo.owner,
|
owner: github.context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: github.context.repo.repo,
|
||||||
baseRef: refs.base,
|
baseRef: refs.base,
|
||||||
headRef: refs.head
|
headRef: refs.head
|
||||||
})
|
})
|
||||||
|
const changes = comparison.changes
|
||||||
|
const snapshot_warnings = comparison.snapshot_warnings
|
||||||
|
|
||||||
if (!changes) {
|
if (!changes) {
|
||||||
core.info('No Dependency Changes found. Skipping Dependency Review.')
|
core.info('No Dependency Changes found. Skipping Dependency Review.')
|
||||||
@@ -67,6 +69,10 @@ async function run(): Promise<void> {
|
|||||||
config
|
config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (snapshot_warnings) {
|
||||||
|
summary.addSnapshotWarnings(snapshot_warnings)
|
||||||
|
}
|
||||||
|
|
||||||
if (config.vulnerability_check) {
|
if (config.vulnerability_check) {
|
||||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
|
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
|
||||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
|
printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
|
||||||
|
|||||||
@@ -74,9 +74,14 @@ export const ConfigurationOptionsSchema = z
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const ChangesSchema = z.array(ChangeSchema)
|
export const ChangesSchema = z.array(ChangeSchema)
|
||||||
|
export const ComparisonResponseSchema = z.object({
|
||||||
|
changes: z.array(ChangeSchema),
|
||||||
|
snapshot_warnings: z.string()
|
||||||
|
})
|
||||||
|
|
||||||
export type Change = z.infer<typeof ChangeSchema>
|
export type Change = z.infer<typeof ChangeSchema>
|
||||||
export type Changes = z.infer<typeof ChangesSchema>
|
export type Changes = z.infer<typeof ChangesSchema>
|
||||||
|
export type ComparisonResponse = z.infer<typeof ComparisonResponseSchema>
|
||||||
export type ConfigurationOptions = z.infer<typeof ConfigurationOptionsSchema>
|
export type ConfigurationOptions = z.infer<typeof ConfigurationOptionsSchema>
|
||||||
export type Severity = z.infer<typeof SeveritySchema>
|
export type Severity = z.infer<typeof SeveritySchema>
|
||||||
export type Scope = (typeof SCOPES)[number]
|
export type Scope = (typeof SCOPES)[number]
|
||||||
|
|||||||
@@ -222,6 +222,23 @@ export function addScannedDependencies(changes: Changes): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addSnapshotWarnings(warnings: string): void {
|
||||||
|
// For now, we want to ignore warnings that just complain
|
||||||
|
// about missing snapshots on the head SHA. This is a product
|
||||||
|
// decision to avoid presenting warnings to users who simply
|
||||||
|
// don't use snapshots.
|
||||||
|
const ignore_regex = new RegExp(/No.*snapshot.*found.*head.*/, 'i')
|
||||||
|
if (ignore_regex.test(warnings)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
core.summary.addHeading('Snapshot Warnings', 2)
|
||||||
|
core.summary.addQuote(`${icons.warning}: ${warnings}`)
|
||||||
|
core.summary.addRaw(
|
||||||
|
'Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function countLicenseIssues(
|
function countLicenseIssues(
|
||||||
invalidLicenseChanges: InvalidLicenseChanges
|
invalidLicenseChanges: InvalidLicenseChanges
|
||||||
): number {
|
): number {
|
||||||
|
|||||||
+3
-1
@@ -8,7 +8,9 @@ export function groupDependenciesByManifest(
|
|||||||
): Map<string, Changes> {
|
): Map<string, Changes> {
|
||||||
const dependencies: Map<string, Changes> = new Map()
|
const dependencies: Map<string, Changes> = new Map()
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const manifestName = change.manifest
|
// If the manifest is null or empty, give it a name now to avoid
|
||||||
|
// breaking the HTML rendering later
|
||||||
|
const manifestName = change.manifest || 'Unnamed Manifest'
|
||||||
|
|
||||||
if (dependencies.get(manifestName) === undefined) {
|
if (dependencies.get(manifestName) === undefined) {
|
||||||
dependencies.set(manifestName, [])
|
dependencies.set(manifestName, [])
|
||||||
|
|||||||
Reference in New Issue
Block a user