Compare commits

..
Author SHA1 Message Date
Jon Janego b1c898035c Update summary.ts
correct max length
2024-09-03 11:45:57 -05:00
Jon Janego bb49ef985c Update main.ts 2024-09-03 11:43:30 -05:00
Jon Janego 431ce2fe42 Update summary.ts 2024-09-03 10:47:30 -05:00
20 changed files with 2978 additions and 2617 deletions
-5
View File
@@ -12,8 +12,3 @@ updates:
ignore: ignore:
- dependency-name: '@types/node' - dependency-name: '@types/node'
update-types: ['version-update:semver-major'] update-types: ['version-update:semver-major']
groups:
minor-updates:
update-types:
- "minor"
- "patch"
+2 -5
View File
@@ -12,15 +12,12 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9.1.0 - uses: actions/stale@v9.0.0
name: Clean up stale PRs and Issues name: Clean up stale PRs and Issues
with: with:
stale-pr-message: "👋 This pull request has been marked as stale because it has been open with no activity for 180 days. You can: comment on the PR or remove the stale label to hold stalebot off for a while, add the `Keep` label to hold stale off permanently, or do nothing. If you do nothing, this pull request will be closed eventually by the stalebot. Please see CONTRIBUTING.md for more policy details." stale-pr-message: "👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the `Keep` label to hold stale off permanently, or do nothing. If you do nothing, this pull request will be closed eventually by the stale bot. Please see CONTRIBUTING.md for more policy details."
stale-pr-label: "Stale" stale-pr-label: "Stale"
close-pr-message: "👋 This pull request has been closed by stalebot because it has been open with no activity for over 180 days. Please see CONTRIBUTING.md for more policy details."
stale-issue-label: "Stale" stale-issue-label: "Stale"
stale-issue-message: "👋 This issue has been marked as stale because it has been open with no activity for 180 days. You can: comment on the issue or remove the stale label to hold stalebot off for a while, add the `Keep` label to hold stale off permanently, or do nothing. If you do nothing, this issue will be closed eventually by the stalebot. Please see CONTRIBUTING.md for more policy details."
close-issue-message: "👋 This issue has been closed by stalebot because it has been open with no activity for over 180 days. Please see CONTRIBUTING.md for more policy details."
exempt-pr-labels: "Keep" # a "Keep" label will keep the PR from being closed as stale exempt-pr-labels: "Keep" # a "Keep" label will keep the PR from being closed as stale
exempt-issue-labels: "Keep" # a "Keep" label will keep the issue from being closed as stale exempt-issue-labels: "Keep" # a "Keep" label will keep the issue from being closed as stale
days-before-pr-stale: 180 # when the PR is considered stale days-before-pr-stale: 180 # when the PR is considered stale
+3 -3
View File
@@ -35,11 +35,11 @@ Ready to contribute to `dependency-review-action`? Here is some information to
This action makes an authenticated query to the [Dependency Review API](https://docs.github.com/en/rest/dependency-graph/dependency-review) endpoint (`GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}`) to find out the set of added and removed dependencies for each manifest. This action makes an authenticated query to the [Dependency Review API](https://docs.github.com/en/rest/dependency-graph/dependency-review) endpoint (`GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}`) to find out the set of added and removed dependencies for each manifest.
The action then evaluates the differences between the pushes based on the rules defined in the action configuration, and summarizes the differences and any violations of the rules you have defined as a comment in the pull request that triggered it and the action outputs. The action then evaluates the differences between the pushes based on the the rules defined in the action configuration, and summarizes the differences and any violations of the rules you have defined as a comment in the pull request that triggered it and the action outputs.
### Local Development ### Local Development
Before you begin, you need to have [Node.js](https://nodejs.org/en/) installed, minimum version 20. Before you begin, you need to have [Node.js](https://nodejs.org/en/) installed, minimum version 18.
#### Bootstrapping the project #### Bootstrapping the project
@@ -81,7 +81,7 @@ $ GITHUB_TOKEN=<token> ./scripts/scan_pr --config-file my_custom_config.yml <pr_
npm run test npm run test
``` ```
_Note_: We don't have a very comprehensive test suite, so any contributions to the existing tests are welcome! _Note_: We don't a very comprehensive test suite, so any contributions to the existing tests are welcome!
### Submitting a pull request ### Submitting a pull request
+6 -33
View File
@@ -124,7 +124,11 @@ test('it raises an error when no refs are provided and the event is not a pull r
).toThrow() ).toThrow()
}) })
const pullRequestLikeEvents = ['pull_request', 'pull_request_target'] const pullRequestLikeEvents = [
'pull_request',
'pull_request_target',
'merge_group'
]
test.each(pullRequestLikeEvents)( test.each(pullRequestLikeEvents)(
'it uses the given refs even when the event is %s', 'it uses the given refs even when the event is %s',
@@ -148,7 +152,7 @@ test.each(pullRequestLikeEvents)(
) )
test.each(pullRequestLikeEvents)( test.each(pullRequestLikeEvents)(
'it uses the event refs when the event is %s and no refs are provided in config', 'it uses the event refs when the event is %s and the no refs are input',
async eventName => { async eventName => {
const refs = getRefs(await readConfig(), { const refs = getRefs(await readConfig(), {
payload: { payload: {
@@ -165,37 +169,6 @@ test.each(pullRequestLikeEvents)(
} }
) )
test('it uses the given refs even when the event is merge_group', async () => {
setInput('base-ref', 'a-custom-base-ref')
setInput('head-ref', 'a-custom-head-ref')
const refs = getRefs(await readConfig(), {
payload: {
merge_group: {
base_sha: 'pr-base-ref',
head_sha: 'pr-head-ref'
}
},
eventName: 'merge_group'
})
expect(refs.base).toEqual('a-custom-base-ref')
expect(refs.head).toEqual('a-custom-head-ref')
})
test('it uses the event refs when the event is merge_group and no refs are provided in config', async () => {
const refs = getRefs(await readConfig(), {
payload: {
merge_group: {
base_sha: 'pr-base-ref',
head_sha: 'pr-head-ref'
}
},
eventName: 'merge_group'
})
expect(refs.base).toEqual('pr-base-ref')
expect(refs.head).toEqual('pr-head-ref')
})
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'])
-59
View File
@@ -134,62 +134,3 @@ test('allows packages not defined in the deny packages and groups list', async (
expect(deniedChanges.length).toEqual(0) expect(deniedChanges.length).toEqual(0)
}) })
test('deny packages does not prevent removal of denied packages', async () => {
const changes: Changes = [
createTestChange({
change_type: 'added',
name: 'deny-by-name-and-version',
version: '1.0.0',
ecosystem: 'npm'
}),
createTestChange({
change_type: 'removed',
name: 'pass-by-name-and-version',
version: '1.0.0',
ecosystem: 'npm'
}),
createTestChange({
change_type: 'added',
name: 'deny-by-name',
version: '1.0.0',
ecosystem: 'npm'
}),
createTestChange({
change_type: 'removed',
name: 'pass-by-name',
version: '1.0.0',
ecosystem: 'npm'
}),
createTestChange({
change_type: 'added',
package_url: 'pkg:npm/org.test.deny.by.namespace/[email protected]',
ecosystem: 'npm'
}),
createTestChange({
change_type: 'removed',
package_url: 'pkg:npm/org.test.pass.by.namespace/[email protected]',
ecosystem: 'npm'
})
]
const deniedPackages = createTestPURLs([
'pkg:npm/org.test.deny.by/[email protected]',
'pkg:npm/org.test.pass.by/[email protected]',
'pkg:npm/org.test.deny.by/deny-by-name',
'pkg:npm/org.test.pass.by/pass-by-name'
])
const deniedGroups = createTestPURLs([
'pkg:npm/org.test.deny.by.namespace/',
'pkg:npm/org.test.pass.by.namespace/'
])
const deniedChanges = await getDeniedChanges(
changes,
deniedPackages,
deniedGroups
)
expect(deniedChanges.length).toEqual(3)
expect(deniedChanges[0]).toBe(changes[0])
expect(deniedChanges[1]).toBe(changes[2])
expect(deniedChanges[2]).toBe(changes[4])
})
+43 -3
View File
@@ -109,6 +109,42 @@ test('prints headline as h1', () => {
expect(text).toContain('<h1>Dependency Review</h1>') expect(text).toContain('<h1>Dependency Review</h1>')
}) })
test('returns minimal summary in case the core.summary is too large for a PR comment', () => {
let changes: Changes = [
createTestChange({name: 'lodash', version: '1.2.3'}),
createTestChange({name: 'colors', version: '2.3.4'}),
createTestChange({name: '@foo/bar', version: '*'})
]
let minSummary: string = summary.addSummaryToSummary(
changes,
emptyInvalidLicenseChanges,
emptyChanges,
scorecard,
defaultConfig
)
// side effect DR report into core.summary as happens in main.ts
summary.addScannedDependencies(changes)
const text = core.summary.stringify()
expect(text).toContain('<h1>Dependency Review</h1>')
expect(minSummary).toContain('# Dependency Review')
expect(text).toContain('❌ 3 vulnerable package(s)')
expect(text).not.toContain('* ❌ 3 vulnerable package(s)')
expect(text).toContain('lodash')
expect(text).toContain('colors')
expect(text).toContain('@foo/bar')
expect(minSummary).toContain('* ❌ 3 vulnerable package(s)')
expect(minSummary).not.toContain('lodash')
expect(minSummary).not.toContain('colors')
expect(minSummary).not.toContain('@foo/bar')
expect(text.length).toBeGreaterThan(minSummary.length)
})
test('returns minimal summary formatted for posting as a PR comment', () => { test('returns minimal summary formatted for posting as a PR comment', () => {
const OLD_ENV = process.env const OLD_ENV = process.env
@@ -196,10 +232,14 @@ test('groups dependencies with empty manifest paths together', () => {
emptyScorecard, emptyScorecard,
defaultConfig defaultConfig
) )
summary.addScannedFiles(changesWithEmptyManifests) summary.addScannedDependencies(changesWithEmptyManifests)
const text = core.summary.stringify() const text = core.summary.stringify()
expect(text).toContain('Unnamed Manifest')
expect(text).toContain('python/dist-info/METADATA') 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', () => {
Generated Vendored
+2444 -1940
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+20 -1
View File
@@ -1460,7 +1460,7 @@ lru-cache
ISC ISC
The ISC License The ISC License
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -1764,6 +1764,25 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
yallist
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
yaml yaml
ISC ISC
Copyright Eemeli Aro <[email protected]> Copyright Eemeli Aro <[email protected]>
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -1,4 +1,4 @@
# Examples of how to use the Dependency Review Action # Examples on how to use the Dependency Review Action
## Basic Usage ## Basic Usage
@@ -89,7 +89,7 @@ The following example will use a configuration file from an external public GitH
Let's say that the configuration file is located in `github/octorepo/dependency-review-config.yml@main` Let's say that the configuration file is located in `github/octorepo/dependency-review-config.yml@main`
The Dependency Review Action workflow file will then look like this: The Dependancy Review Action workflow file will then look like this:
```yaml ```yaml
name: 'Dependency Review' name: 'Dependency Review'
@@ -116,7 +116,7 @@ The following example will use a configuration file from an external private Gti
Let's say that the configuration file is located in `github/octorepo-private/dependency-review-config.yml@main` Let's say that the configuration file is located in `github/octorepo-private/dependency-review-config.yml@main`
The Dependency Review Action workflow file will then look like this: The Dependancy Review Action workflow file will then look like this:
```yaml ```yaml
name: 'Dependency Review' name: 'Dependency Review'
+385 -442
View File
File diff suppressed because it is too large Load Diff
+10 -14
View File
@@ -1,6 +1,6 @@
{ {
"name": "dependency-review-action", "name": "dependency-review-action",
"version": "4.5.0", "version": "4.3.4",
"private": true, "private": true,
"description": "A GitHub Action for Dependency Review", "description": "A GitHub Action for Dependency Review",
"main": "lib/main.js", "main": "lib/main.js",
@@ -27,18 +27,18 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@octokit/plugin-retry": "^6.1.0", "@octokit/plugin-retry": "^6.0.1",
"@octokit/request-error": "^5.1.1", "@octokit/request-error": "^5.0.1",
"@onebeyond/spdx-license-satisfies": "^1.0.1", "@onebeyond/spdx-license-satisfies": "^1.0.1",
"ansi-styles": "^6.2.1", "ansi-styles": "^6.2.1",
"got": "^14.4.5", "got": "^14.4.1",
"jest": "^29.7.0", "jest": "^29.7.0",
"octokit": "^3.1.2", "octokit": "^3.1.2",
"spdx-expression-parse": "^3.0.1", "spdx-expression-parse": "^3.0.1",
"spdx-satisfies": "^5.0.1", "spdx-satisfies": "^5.0.1",
"ts-jest": "^29.2.5", "ts-jest": "^29.1.2",
"yaml": "^2.3.4", "yaml": "^2.3.4",
"zod": "^3.24.1" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
@@ -47,19 +47,15 @@
"@types/spdx-satisfies": "^0.1.1", "@types/spdx-satisfies": "^0.1.1",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.21.0",
"@vercel/ncc": "^0.38.3", "@vercel/ncc": "^0.38.0",
"esbuild-register": "^3.6.0", "esbuild-register": "^3.5.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-github": "^4.10.2", "eslint-plugin-github": "^4.10.2",
"eslint-plugin-jest": "^28.8.3", "eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"nodemon": "^3.1.9", "nodemon": "^3.1.0",
"prettier": "3.2.5", "prettier": "3.2.5",
"typescript": "^5.4.5" "typescript": "^5.4.5"
},
"overrides": {
"cross-spawn": ">=7.0.5",
"@octokit/[email protected]": "5.1.1"
} }
} }
+1 -1
View File
@@ -143,7 +143,7 @@ async function createSummary(
...licenseIssues.unlicensed ...licenseIssues.unlicensed
] ]
summary.addScannedFiles(allChanges) summary.addScannedDependencies(allChanges)
const text = core.summary.stringify() const text = core.summary.stringify()
await fs.promises.writeFile(path.resolve(tmpDir, fileName), text, { await fs.promises.writeFile(path.resolve(tmpDir, fileName), text, {
+3 -3
View File
@@ -17,13 +17,13 @@ const COMMENT_MARKER = '<!-- dependency-review-pr-comment-marker -->'
export async function commentPr( export async function commentPr(
commentContent: string, commentContent: string,
config: ConfigurationOptions, config: ConfigurationOptions
issueFound: boolean
): Promise<void> { ): Promise<void> {
if ( if (
!( !(
config.comment_summary_in_pr === 'always' || config.comment_summary_in_pr === 'always' ||
(config.comment_summary_in_pr === 'on-failure' && issueFound) (config.comment_summary_in_pr === 'on-failure' &&
process.exitCode === core.ExitCode.Failure)
) )
) { ) {
return return
+9 -4
View File
@@ -9,17 +9,15 @@ export async function getDeniedChanges(
): Promise<Change[]> { ): Promise<Change[]> {
const changesDenied: Change[] = [] const changesDenied: Change[] = []
let hasDeniedPackage = false
for (const change of changes) { for (const change of changes) {
if (change.change_type === 'removed') {
continue
}
for (const denied of deniedPackages) { for (const denied of deniedPackages) {
if ( if (
(!denied.version || change.version === denied.version) && (!denied.version || change.version === denied.version) &&
change.name === denied.name change.name === denied.name
) { ) {
changesDenied.push(change) changesDenied.push(change)
hasDeniedPackage = true
} }
} }
@@ -32,10 +30,17 @@ export async function getDeniedChanges(
} }
if (namespace && namespace === denied.namespace) { if (namespace && namespace === denied.namespace) {
changesDenied.push(change) changesDenied.push(change)
hasDeniedPackage = true
} }
} }
} }
if (hasDeniedPackage) {
core.setFailed('Dependency review detected denied packages.')
} else {
core.info('Dependency review did not detect any denied packages')
}
return changesDenied return changesDenied
} }
+10 -22
View File
@@ -1,34 +1,22 @@
import { import {PullRequestSchema, ConfigurationOptions} from './schemas'
PullRequestSchema,
ConfigurationOptions,
MergeGroupSchema
} from './schemas'
export function getRefs( export function getRefs(
config: ConfigurationOptions, config: ConfigurationOptions,
context: { context: {payload: {pull_request?: unknown}; eventName: string}
payload: {pull_request?: unknown; merge_group?: unknown}
eventName: string
}
): {base: string; head: string} { ): {base: string; head: string} {
let base_ref = config.base_ref let base_ref = config.base_ref
let head_ref = config.head_ref let head_ref = config.head_ref
// If possible, source default base & head refs from the GitHub event. // If possible, source default base & head refs from the GitHub event.
// The base/head ref from the config take priority, if provided. // The base/head ref from the config take priority, if provided.
if (!base_ref && !head_ref) { if (
if ( context.eventName === 'pull_request' ||
context.eventName === 'pull_request' || context.eventName === 'pull_request_target' ||
context.eventName === 'pull_request_target' context.eventName === 'merge_group'
) { ) {
const pull_request = PullRequestSchema.parse(context.payload.pull_request) const pull_request = PullRequestSchema.parse(context.payload.pull_request)
base_ref = base_ref || pull_request.base.sha base_ref = base_ref || pull_request.base.sha
head_ref = head_ref || pull_request.head.sha head_ref = head_ref || pull_request.head.sha
} else if (context.eventName === 'merge_group') {
const merge_group = MergeGroupSchema.parse(context.payload.merge_group)
base_ref = base_ref || merge_group.base_sha
head_ref = head_ref || merge_group.head_sha
}
} }
if (!base_ref && !head_ref) { if (!base_ref && !head_ref) {
+24 -47
View File
@@ -24,6 +24,7 @@ import {getRefs} from './git-refs'
import {groupDependenciesByManifest} from './utils' import {groupDependenciesByManifest} from './utils'
import {commentPr, MAX_COMMENT_LENGTH} from './comment-pr' import {commentPr, MAX_COMMENT_LENGTH} from './comment-pr'
import {getDeniedChanges} from './deny' import {getDeniedChanges} from './deny'
import {MAX_SUMMARY_LENGTH} from './summary'
async function delay(ms: number): Promise<void> { async function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))
@@ -141,16 +142,10 @@ async function run(): Promise<void> {
summary.addSnapshotWarnings(config, snapshot_warnings) summary.addSnapshotWarnings(config, snapshot_warnings)
} }
let issueFound = false
if (config.vulnerability_check) { if (config.vulnerability_check) {
core.setOutput('vulnerable-changes', JSON.stringify(vulnerableChanges)) core.setOutput('vulnerable-changes', JSON.stringify(vulnerableChanges))
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity) summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
issueFound ||= await printVulnerabilitiesBlock( printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly)
vulnerableChanges,
minSeverity,
warnOnly
)
} }
if (config.license_check) { if (config.license_check) {
core.setOutput( core.setOutput(
@@ -158,12 +153,12 @@ async function run(): Promise<void> {
JSON.stringify(invalidLicenseChanges) JSON.stringify(invalidLicenseChanges)
) )
summary.addLicensesToSummary(invalidLicenseChanges, config) summary.addLicensesToSummary(invalidLicenseChanges, config)
issueFound ||= await printLicensesBlock(invalidLicenseChanges, warnOnly) printLicensesBlock(invalidLicenseChanges, warnOnly)
} }
if (config.deny_packages || config.deny_groups) { if (config.deny_packages || config.deny_groups) {
core.setOutput('denied-changes', JSON.stringify(deniedChanges)) core.setOutput('denied-changes', JSON.stringify(deniedChanges))
summary.addDeniedToSummary(deniedChanges) summary.addDeniedToSummary(deniedChanges)
issueFound ||= await printDeniedDependencies(deniedChanges, config) printDeniedDependencies(deniedChanges, config)
} }
if (config.show_openssf_scorecard) { if (config.show_openssf_scorecard) {
summary.addScorecardToSummary(scorecard, config) summary.addScorecardToSummary(scorecard, config)
@@ -172,7 +167,7 @@ async function run(): Promise<void> {
} }
core.setOutput('dependency-changes', JSON.stringify(changes)) core.setOutput('dependency-changes', JSON.stringify(changes))
summary.addScannedFiles(changes) summary.addScannedDependencies(changes)
printScannedDependencies(changes) printScannedDependencies(changes)
// include full summary in output; Actions will truncate if oversized // include full summary in output; Actions will truncate if oversized
@@ -188,7 +183,7 @@ async function run(): Promise<void> {
} }
// update the PR comment if needed with the right-sized summary // update the PR comment if needed with the right-sized summary
await commentPr(rendered, config, issueFound) await commentPr(rendered, config)
} catch (error) { } catch (error) {
if (error instanceof RequestError && error.status === 404) { if (error instanceof RequestError && error.status === 404) {
core.setFailed( core.setFailed(
@@ -196,7 +191,7 @@ async function run(): Promise<void> {
) )
} else if (error instanceof RequestError && error.status === 403) { } else if (error instanceof RequestError && error.status === 403) {
core.setFailed( core.setFailed(
`Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled along with GitHub Advanced Security on private repositories, see ${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/settings/security_analysis` `Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled along with GitHub Advanced Security on private repositories, see https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/settings/security_analysis`
) )
} else { } else {
if (error instanceof Error) { if (error instanceof Error) {
@@ -206,20 +201,22 @@ async function run(): Promise<void> {
} }
} }
} finally { } finally {
await core.summary.write() await core.summary.write() // need to write the minSummary content to this
} }
} }
async function printVulnerabilitiesBlock( function printVulnerabilitiesBlock(
addedChanges: Changes, addedChanges: Changes,
minSeverity: Severity, minSeverity: Severity,
warnOnly: boolean warnOnly: boolean
): Promise<boolean> { ): void {
return core.group('Vulnerabilities', async () => { let vulFound = false
let vulFound = false core.group('Vulnerabilities', async () => {
if (addedChanges.length > 0) {
for (const change of addedChanges) { for (const change of addedChanges) {
vulFound ||= printChangeVulnerabilities(change) printChangeVulnerabilities(change)
}
vulFound = true
} }
if (vulFound) { if (vulFound) {
@@ -234,12 +231,10 @@ async function printVulnerabilitiesBlock(
`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.` `Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`
) )
} }
return vulFound
}) })
} }
function printChangeVulnerabilities(change: Change): boolean { function printChangeVulnerabilities(change: Change): void {
for (const vuln of change.vulnerabilities) { for (const vuln of change.vulnerabilities) {
core.info( core.info(
`${styles.bold.open}${change.manifest} » ${change.name}@${ `${styles.bold.open}${change.manifest} » ${change.name}@${
@@ -250,18 +245,14 @@ function printChangeVulnerabilities(change: Change): boolean {
) )
core.info(`${vuln.advisory_url}`) core.info(`${vuln.advisory_url}`)
} }
return change.vulnerabilities.length > 0
} }
async function printLicensesBlock( function printLicensesBlock(
invalidLicenseChanges: Record<string, Changes>, invalidLicenseChanges: Record<string, Changes>,
warnOnly: boolean warnOnly: boolean
): Promise<boolean> { ): void {
return core.group('Licenses', async () => { core.group('Licenses', async () => {
let issueFound = false
if (invalidLicenseChanges.forbidden.length > 0) { if (invalidLicenseChanges.forbidden.length > 0) {
issueFound = true
core.info('\nThe following dependencies have incompatible licenses:') core.info('\nThe following dependencies have incompatible licenses:')
printLicensesError(invalidLicenseChanges.forbidden) printLicensesError(invalidLicenseChanges.forbidden)
const msg = 'Dependency review detected incompatible licenses.' const msg = 'Dependency review detected incompatible licenses.'
@@ -272,7 +263,6 @@ async function printLicensesBlock(
} }
} }
if (invalidLicenseChanges.unresolved.length > 0) { if (invalidLicenseChanges.unresolved.length > 0) {
issueFound = true
core.warning( core.warning(
'\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:' '\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:'
) )
@@ -282,8 +272,6 @@ async function printLicensesBlock(
) )
} }
printNullLicenses(invalidLicenseChanges.unlicensed) printNullLicenses(invalidLicenseChanges.unlicensed)
return issueFound
}) })
} }
@@ -383,13 +371,11 @@ function printScannedDependencies(changes: Changes): void {
}) })
} }
async function printDeniedDependencies( function printDeniedDependencies(
changes: Changes, changes: Changes,
config: ConfigurationOptions config: ConfigurationOptions
): Promise<boolean> { ): void {
return core.group('Denied', async () => { core.group('Denied', async () => {
let issueFound = false
for (const denied of config.deny_packages) { for (const denied of config.deny_packages) {
core.info(`Config: ${denied}`) core.info(`Config: ${denied}`)
} }
@@ -398,15 +384,6 @@ async function printDeniedDependencies(
core.info(`Change: ${change.name}@${change.version} is denied`) core.info(`Change: ${change.name}@${change.version} is denied`)
core.info(`Change: ${change.package_url} is denied`) core.info(`Change: ${change.package_url} is denied`)
} }
if (changes.length > 0) {
issueFound = true
core.setFailed('Dependency review detected denied packages.')
} else {
core.info('Dependency review did not detect any denied packages')
}
return issueFound
}) })
} }
-5
View File
@@ -91,11 +91,6 @@ export const PullRequestSchema = z.object({
head: z.object({sha: z.string()}) head: z.object({sha: z.string()})
}) })
export const MergeGroupSchema = z.object({
base_sha: z.string(),
head_sha: z.string()
})
export const ConfigurationOptionsSchema = z export const ConfigurationOptionsSchema = z
.object({ .object({
fail_on_severity: SeveritySchema, fail_on_severity: SeveritySchema,
+13 -25
View File
@@ -1,7 +1,7 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {ConfigurationOptions, Changes, Change, Scorecard} from './schemas'
import {SummaryTableRow} from '@actions/core/lib/summary' import {SummaryTableRow} from '@actions/core/lib/summary'
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses' import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
import {Change, Changes, ConfigurationOptions, Scorecard} from './schemas'
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils' import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
const icons = { const icons = {
@@ -10,7 +10,7 @@ const icons = {
warning: '⚠️' warning: '⚠️'
} }
const MAX_SCANNED_FILES_BYTES = 1048576 export const MAX_SUMMARY_LENGTH = 1048576
// generates the DR report summmary and caches it to the Action's core.summary. // generates the DR report summmary and caches it to the Action's core.summary.
// returns the DR summary string, ready to be posted as a PR comment if the // returns the DR summary string, ready to be posted as a PR comment if the
@@ -265,33 +265,21 @@ function formatLicense(license: string | null): string {
return license return license
} }
export function addScannedFiles(changes: Changes): void { export function addScannedDependencies(changes: Changes): void {
const manifests = Array.from( const dependencies = groupDependenciesByManifest(changes)
groupDependenciesByManifest(changes).keys() const manifests = dependencies.keys()
).sort()
let sf_size = 0 const summary = core.summary.addHeading('Scanned Manifest Files', 2)
let trunc_at = -1
for (const [index, entry] of manifests.entries()) { for (const manifest of manifests) {
if (sf_size + entry.length >= MAX_SCANNED_FILES_BYTES) { const deps = dependencies.get(manifest)
trunc_at = index if (deps) {
break const dependencyNames = deps.map(
} dependency => `<li>${dependency.name}@${dependency.version}</li>`
sf_size += entry.length )
} summary.addDetails(manifest, `<ul>${dependencyNames.join('')}</ul>`)
if (trunc_at >= 0) {
// truncate the manifests list if it will overflow the summary output
manifests.slice(0, trunc_at)
// if there's room between cutoff size and list size, add a warning
const size_diff = MAX_SCANNED_FILES_BYTES - sf_size
if (size_diff < 12) {
manifests.push('(truncated)')
} }
} }
core.summary.addHeading('Scanned Files', 2).addList(manifests)
} }
function snapshotWarningRecommendation( function snapshotWarningRecommendation(