Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5951e7db04 | |||
| 4f537bf170 | |||
| 25f22ad0c7 | |||
| 2878425083 | |||
| 862b667fee | |||
| f7c42c00ca | |||
| 00dbe9df8d | |||
| f89a053f16 | |||
| 700f66ed8f | |||
| fc5eaef91a | |||
| 087d445ca8 | |||
| 95fa321e74 | |||
| 429d317ccc | |||
| 6b34d93738 | |||
| b7a25f4e9b | |||
| 9f0792541a | |||
| 5e6910e937 | |||
| 715956774a | |||
| 94e6fb6deb | |||
| 1090cda9d5 | |||
| 6315b3822f | |||
| c5dab80dd4 | |||
| b089c5b002 | |||
| 6e66d136ec | |||
| 1b9faef957 | |||
| 748b8a5c33 | |||
| 1639aef23d | |||
| f5f33b0c33 | |||
| 2d8dd98fa8 | |||
| 182833caa3 | |||
| b0bc193e06 | |||
| f3146217d6 | |||
| c5b1778acb | |||
| 19ee172e7e | |||
| 23c92ea3fe | |||
| 1af3349db2 | |||
| 2238302e66 | |||
| e4158c9844 | |||
| 7f874fd2fb | |||
| 9928099802 | |||
| d9d1c4ba24 | |||
| a3ee6a76df | |||
| f69167c9be | |||
| 1c85e9db8d |
@@ -100,3 +100,5 @@ Thumbs.db
|
||||
# Ignore built ts files
|
||||
__tests__/runner/*
|
||||
lib/**/*
|
||||
|
||||
tmp
|
||||
|
||||
@@ -66,25 +66,25 @@ jobs:
|
||||
|
||||
Configure this action by either inlining these options in your workflow file, or by using an external configuration file. All configuration options are optional.
|
||||
|
||||
| Option | Usage | Possible values | Default value |
|
||||
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|---------------|
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes`† | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. |`runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
|
||||
| Option | Usage | Possible values | Default value |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------- |
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes`† | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. | `runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
|
||||
| `comment-summary-in-pr` | Enable or disable reporting the review summary as a comment in the pull request. If enabled, you must give the workflow or job permission `pull-requests: write`. | `true`, `false` | `false` |
|
||||
|
||||
*not supported for use with GitHub Enterprise Server
|
||||
|
||||
†will be supported with GitHub Enterprise Server 3.8
|
||||
|
||||
†will be supported with GitHub Enterprise Server 3.8
|
||||
|
||||
### Inline Configuration
|
||||
|
||||
You can pass options to the Dependency Review GitHub Action using your workflow file.
|
||||
You can pass options to the Dependency Review GitHub Action using your workflow file.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -112,10 +112,10 @@ jobs:
|
||||
|
||||
You can use an external configuration file to specify the settings for this action. It can be a local file or a file in an external repository. Refer to the following options for the specification.
|
||||
|
||||
| Option | Usage | Possible values |
|
||||
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `config-file` | A path to a file in the current repository or an external repository. Use this syntax for external files: `OWNER/REPOSITORY/FILENAME@BRANCH` | **Local file**: `./.github/dependency-review-config.yml` <br> **External repo**: `github/octorepo/dependency-review-config.yml@main` |
|
||||
| `external-repo-token` | Specifies a token for fetching the configuration file. It is required if the file resides in a private external repository and for all GitHub Enterprise Server repositories. Create a token in [developer settings](https://github.com/settings/tokens). | Any token with `read` permissions to the repository hosting the config file. |
|
||||
| Option | Usage | Possible values |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `config-file` | A path to a file in the current repository or an external repository. Use this syntax for external files: `OWNER/REPOSITORY/FILENAME@BRANCH` | **Local file**: `./.github/dependency-review-config.yml` <br> **External repo**: `github/octorepo/dependency-review-config.yml@main` |
|
||||
| `external-repo-token` | Specifies a token for fetching the configuration file. It is required if the file resides in a private external repository and for all GitHub Enterprise Server repositories. Create a token in [developer settings](https://github.com/settings/tokens). | Any token with `read` permissions to the repository hosting the config file. |
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -128,7 +128,7 @@ Start by specifying that you will be using an external configuration file:
|
||||
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:
|
||||
|
||||
```yaml
|
||||
fail-on-severity: 'critical'
|
||||
|
||||
@@ -5,13 +5,13 @@ import * as Utils from '../src/utils'
|
||||
|
||||
// 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) {
|
||||
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() {
|
||||
function clearInputs(): void {
|
||||
const allowedOptions = [
|
||||
'FAIL-ON-SEVERITY',
|
||||
'FAIL-ON-SCOPES',
|
||||
@@ -22,9 +22,11 @@ function clearInputs() {
|
||||
'VULNERABILITY-CHECK',
|
||||
'CONFIG-FILE',
|
||||
'BASE-REF',
|
||||
'HEAD-REF'
|
||||
'HEAD-REF',
|
||||
'COMMENT-SUMMARY-IN-PR'
|
||||
]
|
||||
|
||||
// eslint-disable-next-line github/array-foreach
|
||||
allowedOptions.forEach(option => {
|
||||
delete process.env[`INPUT_${option.toUpperCase()}`]
|
||||
})
|
||||
@@ -237,7 +239,7 @@ test('it supports comma-separated lists', async () => {
|
||||
'config-file',
|
||||
'./__tests__/fixtures/inline-license-config-sample.yml'
|
||||
)
|
||||
let config = await readConfig()
|
||||
const config = await readConfig()
|
||||
|
||||
expect(config.allow_licenses).toEqual(['MIT', 'GPL-2.0-only'])
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {expect, test} from '@jest/globals'
|
||||
import {Change, Changes} from '../src/schemas'
|
||||
import {Change} from '../src/schemas'
|
||||
import {
|
||||
filterChangesBySeverity,
|
||||
filterChangesByScopes,
|
||||
filterAllowedAdvisories
|
||||
} from '../src/filter'
|
||||
|
||||
let npmChange: Change = {
|
||||
const npmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
change_type: 'added',
|
||||
ecosystem: 'npm',
|
||||
@@ -26,7 +26,7 @@ let npmChange: Change = {
|
||||
]
|
||||
}
|
||||
|
||||
let rubyChange: Change = {
|
||||
const rubyChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'Gemfile.lock',
|
||||
ecosystem: 'rubygems',
|
||||
@@ -52,7 +52,7 @@ let rubyChange: Change = {
|
||||
]
|
||||
}
|
||||
|
||||
let noVulnNpmChange: Change = {
|
||||
const noVulnNpmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
change_type: 'added',
|
||||
ecosystem: 'npm',
|
||||
@@ -92,7 +92,7 @@ test('it properly filters changes by scope', async () => {
|
||||
|
||||
test('it properly handles undefined advisory IDs', async () => {
|
||||
const changes = [npmChange, rubyChange, noVulnNpmChange]
|
||||
let result = filterAllowedAdvisories(undefined, changes)
|
||||
const result = filterAllowedAdvisories(undefined, changes)
|
||||
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import {Change} from '../../src/schemas'
|
||||
import {createTestVulnerability} from './create-test-vulnerability'
|
||||
|
||||
const defaultChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'package.json',
|
||||
ecosystem: 'npm',
|
||||
name: 'lodash',
|
||||
version: '4.17.20',
|
||||
package_url: 'pkg:npm/lodash@4.17.20',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'https://github.com/lodash/lodash',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({
|
||||
severity: 'high',
|
||||
advisory_ghsa_id: 'GHSA-35jh-r3h4-6jhm',
|
||||
advisory_summary: 'Command Injection in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-35jh-r3h4-6jhm'
|
||||
}),
|
||||
createTestVulnerability({
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'GHSA-29mw-wpgm-hmr9',
|
||||
advisory_summary:
|
||||
'Regular Expression Denial of Service (ReDoS) in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-29mw-wpgm-hmr9'
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
const createTestChange = (overwrites: Partial<Change> = {}): Change => ({
|
||||
...defaultChange,
|
||||
...overwrites
|
||||
})
|
||||
|
||||
export {createTestChange}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {Change} from '../../src/schemas'
|
||||
|
||||
type Vulnerability = Change['vulnerabilities'][0]
|
||||
|
||||
const defaultTestVulnerability: Vulnerability = {
|
||||
severity: 'high',
|
||||
advisory_ghsa_id: 'GHSA-35jh-r3h4-6jhm',
|
||||
advisory_summary: 'Command Injection in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-35jh-r3h4-6jhm'
|
||||
}
|
||||
|
||||
const createTestVulnerability = (
|
||||
overwrites: Partial<Vulnerability> = {}
|
||||
): Vulnerability => ({
|
||||
...defaultTestVulnerability,
|
||||
...overwrites
|
||||
})
|
||||
|
||||
export {createTestVulnerability}
|
||||
@@ -3,7 +3,7 @@ import {Change, Changes} from '../src/schemas'
|
||||
|
||||
let getInvalidLicenseChanges: Function
|
||||
|
||||
let npmChange: Change = {
|
||||
const npmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
change_type: 'added',
|
||||
ecosystem: 'npm',
|
||||
@@ -23,7 +23,7 @@ let npmChange: Change = {
|
||||
]
|
||||
}
|
||||
|
||||
let rubyChange: Change = {
|
||||
const rubyChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'Gemfile.lock',
|
||||
ecosystem: 'rubygems',
|
||||
@@ -63,6 +63,7 @@ const mockOctokit = {
|
||||
|
||||
jest.mock('octokit', () => {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
Octokit: class {
|
||||
constructor() {
|
||||
return mockOctokit
|
||||
@@ -78,6 +79,7 @@ beforeEach(async () => {
|
||||
// true for BSD, false for all others
|
||||
return jest.fn((license: string, _: string): boolean => license === 'BSD')
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
;({getInvalidLicenseChanges} = require('../src/licenses'))
|
||||
})
|
||||
|
||||
@@ -140,6 +142,7 @@ test('it adds all licenses to unresolved if it is unable to determine the validi
|
||||
throw new Error('Some Error')
|
||||
})
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
;({getInvalidLicenseChanges} = require('../src/licenses'))
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const invalidLicenses = await getInvalidLicenseChanges(changes, {
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import * as summary from '../src/summary'
|
||||
import * as core from '@actions/core'
|
||||
import {createTestChange} from './fixtures/create-test-change'
|
||||
import {createTestVulnerability} from './fixtures/create-test-vulnerability'
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
core.summary.emptyBuffer()
|
||||
})
|
||||
|
||||
const emptyChanges: Changes = []
|
||||
const emptyInvalidLicenseChanges = {
|
||||
forbidden: [],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
const defaultConfig: ConfigurationOptions = {
|
||||
vulnerability_check: true,
|
||||
license_check: true,
|
||||
fail_on_severity: 'high',
|
||||
fail_on_scopes: ['runtime'],
|
||||
allow_ghsas: [],
|
||||
allow_licenses: [],
|
||||
deny_licenses: [],
|
||||
comment_summary_in_pr: true
|
||||
}
|
||||
|
||||
test('prints headline as h1', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('<h1>Dependency Review</h1>')
|
||||
})
|
||||
|
||||
test('only includes "No vulnerabilities or license issues found"-message if both are configured and nothing was found', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('✅ No vulnerabilities or license issues found.')
|
||||
})
|
||||
|
||||
test('only includes "No vulnerabilities found"-message if "license_check" is set to false and nothing was found', () => {
|
||||
const config = {...defaultConfig, license_check: false}
|
||||
summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('✅ No vulnerabilities found.')
|
||||
})
|
||||
|
||||
test('only includes "No license issues found"-message if "vulnerability_check" is set to false and nothing was found', () => {
|
||||
const config = {...defaultConfig, vulnerability_check: false}
|
||||
summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('✅ No license issues found.')
|
||||
})
|
||||
|
||||
test('does not include status section if nothing was found', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).not.toContain('The following issues were found:')
|
||||
})
|
||||
|
||||
test('includes count and status icons for all findings', () => {
|
||||
const vulnerabilities = [
|
||||
createTestChange({name: 'lodash'}),
|
||||
createTestChange({name: 'underscore', package_url: 'test-url'})
|
||||
]
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [createTestChange(), createTestChange()],
|
||||
unlicensed: [createTestChange(), createTestChange(), createTestChange()]
|
||||
}
|
||||
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, defaultConfig)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('❌ 2 vulnerable package(s)')
|
||||
expect(text).toContain(
|
||||
'❌ 2 package(s) with invalid SPDX license definitions'
|
||||
)
|
||||
expect(text).toContain('❌ 1 package(s) with incompatible licenses')
|
||||
expect(text).toContain('⚠️ 3 package(s) with unknown licenses')
|
||||
})
|
||||
|
||||
test('uses checkmarks for license issues if only vulnerabilities were found', () => {
|
||||
const vulnerabilities = [createTestChange()]
|
||||
|
||||
summary.addSummaryToSummary(
|
||||
vulnerabilities,
|
||||
emptyInvalidLicenseChanges,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('❌ 1 vulnerable package(s)')
|
||||
expect(text).toContain(
|
||||
'✅ 0 package(s) with invalid SPDX license definitions'
|
||||
)
|
||||
expect(text).toContain('✅ 0 package(s) with incompatible licenses')
|
||||
expect(text).toContain('✅ 0 package(s) with unknown licenses')
|
||||
})
|
||||
|
||||
test('uses checkmarks for vulnerabilities if only license issues were found', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
summary.addSummaryToSummary(emptyChanges, licenseIssues, defaultConfig)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('✅ 0 vulnerable package(s)')
|
||||
expect(text).toContain(
|
||||
'✅ 0 package(s) with invalid SPDX license definitions'
|
||||
)
|
||||
expect(text).toContain('❌ 1 package(s) with incompatible licenses')
|
||||
expect(text).toContain('✅ 0 package(s) with unknown licenses')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - only includes section if any vulnerabilites found', () => {
|
||||
summary.addChangeVulnerabilitiesToSummary(emptyChanges, 'low')
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toEqual('')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - includes all vulnerabilities', () => {
|
||||
const changes = [
|
||||
createTestChange({name: 'lodash'}),
|
||||
createTestChange({name: 'underscore', package_url: 'test-url'})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<h2>Vulnerabilities</h2>')
|
||||
expect(text).toContain('lodash')
|
||||
expect(text).toContain('underscore')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - includes advisory url if available', () => {
|
||||
const changes = [
|
||||
createTestChange({
|
||||
name: 'underscore',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({
|
||||
advisory_summary: 'test-summary',
|
||||
advisory_url: 'test-url'
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('lodash')
|
||||
expect(text).toContain('<a href="test-url">test-summary</a>')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - groups vulnerabilities of a single package', () => {
|
||||
const changes = [
|
||||
createTestChange({
|
||||
name: 'package-with-multiple-vulnerabilities',
|
||||
vulnerabilities: [
|
||||
createTestVulnerability({advisory_summary: 'test-summary-1'}),
|
||||
createTestVulnerability({advisory_summary: 'test-summary-2'})
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text.match('package-with-multiple-vulnerabilities')).toHaveLength(1)
|
||||
expect(text).toContain('test-summary-1')
|
||||
expect(text).toContain('test-summary-2')
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - prints severity statement if above low', () => {
|
||||
const changes = [createTestChange()]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'medium')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain(
|
||||
'Only included vulnerabilities with severity <strong>medium</strong> or higher.'
|
||||
)
|
||||
})
|
||||
|
||||
test('addChangeVulnerabilitiesToSummary() - does not print severity statment if it is set to "low"', () => {
|
||||
const changes = [createTestChange()]
|
||||
|
||||
summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).not.toContain('Only included vulnerabilities')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - does not include entire section if no license issues found', () => {
|
||||
summary.addLicensesToSummary(emptyInvalidLicenseChanges, defaultConfig)
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toEqual('')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - includes all license issues in table', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [createTestChange(), createTestChange()],
|
||||
unlicensed: [createTestChange(), createTestChange(), createTestChange()]
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, defaultConfig)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<h2>License Issues</h2>')
|
||||
expect(text).toContain('<td>Incompatible License</td>')
|
||||
expect(text).toContain('<td>Invalid SPDX License</td>')
|
||||
expect(text).toContain('<td>Unknown License</td>')
|
||||
})
|
||||
|
||||
test('addLicenseToSummary() - adds one table per manifest', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [
|
||||
createTestChange({manifest: 'package.json'}),
|
||||
createTestChange({manifest: '.github/workflows/test.yml'})
|
||||
],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, defaultConfig)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('<h4><em>package.json</em></h4>')
|
||||
expect(text).toContain('<h4><em>.github/workflows/test.yml</em></h4>')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - does not include specific license type sub-section if nothing is found', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [],
|
||||
unlicensed: [],
|
||||
unresolved: [createTestChange()]
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, defaultConfig)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).not.toContain('<td>Incompatible License</td>')
|
||||
expect(text).not.toContain('<td>Unknown License</td>')
|
||||
expect(text).toContain('<td>Invalid SPDX License</td>')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - includes list of configured allowed licenses', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
const config: ConfigurationOptions = {
|
||||
...defaultConfig,
|
||||
allow_licenses: ['MIT', 'Apache-2.0']
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, config)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<strong>Allowed Licenses</strong>: MIT, Apache-2.0')
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - includes configured denied license', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
const config: ConfigurationOptions = {
|
||||
...defaultConfig,
|
||||
deny_licenses: ['MIT']
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, config)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<strong>Denied Licenses</strong>: MIT')
|
||||
})
|
||||
@@ -41,6 +41,9 @@ inputs:
|
||||
vulnerability-check:
|
||||
description: A boolean to determine if vulnerability checks should be performed
|
||||
required: false
|
||||
comment-summary-in-pr:
|
||||
description: A boolean to determine if the report should be posted as a comment in the PR itself. Setting this to true requires you to give the workflow the write permissions for pull-requests
|
||||
required: false
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: 'dist/index.js'
|
||||
|
||||
+516
-99
@@ -1,6 +1,142 @@
|
||||
require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
|
||||
/******/ var __webpack_modules__ = ({
|
||||
|
||||
/***/ 5842:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__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());
|
||||
});
|
||||
};
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.commentPr = void 0;
|
||||
const github = __importStar(__nccwpck_require__(5438));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const githubUtils = __importStar(__nccwpck_require__(3030));
|
||||
const retry = __importStar(__nccwpck_require__(6298));
|
||||
const request_error_1 = __nccwpck_require__(537);
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||
const octo = new retryingOctokit(githubUtils.getOctokitOptions(core.getInput('repo-token', { required: true })));
|
||||
// Comment Marker to identify an existing comment to update, so we don't spam the PR with comments
|
||||
const COMMENT_MARKER = '<!-- dependency-review-pr-comment-marker -->';
|
||||
function commentPr(summary) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!github.context.payload.pull_request) {
|
||||
core.warning('Not in the context of a pull request. Skipping comment creation.');
|
||||
return;
|
||||
}
|
||||
const commentBody = `${summary.stringify()}\n\n${COMMENT_MARKER}`;
|
||||
try {
|
||||
const existingCommentId = yield findCommentByMarker(COMMENT_MARKER);
|
||||
if (existingCommentId) {
|
||||
yield octo.rest.issues.updateComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
comment_id: existingCommentId,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
else {
|
||||
yield octo.rest.issues.createComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: github.context.payload.pull_request.number,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof request_error_1.RequestError && error.status === 403) {
|
||||
core.warning(`Unable to write summary to pull-request. Make sure you are giving this workflow the permission 'pull-requests: write'.`);
|
||||
}
|
||||
else {
|
||||
if (error instanceof Error) {
|
||||
core.warning(`Unable to comment summary to pull-request, received error: ${error.message}`);
|
||||
}
|
||||
else {
|
||||
core.warning('Unable to comment summary to pull-request: Unexpected fatal error');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.commentPr = commentPr;
|
||||
function findCommentByMarker(commentBodyIncludes) {
|
||||
var _a, e_1, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const commentsIterator = octo.paginate.iterator(octo.rest.issues.listComments, {
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
// We are already checking if we are in the context of a pull request in the caller
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
issue_number: github.context.payload.pull_request.number
|
||||
});
|
||||
try {
|
||||
for (var _d = true, commentsIterator_1 = __asyncValues(commentsIterator), commentsIterator_1_1; commentsIterator_1_1 = yield commentsIterator_1.next(), _a = commentsIterator_1_1.done, !_a;) {
|
||||
_c = commentsIterator_1_1.value;
|
||||
_d = false;
|
||||
try {
|
||||
const { data: comments } = _c;
|
||||
const existingComment = comments.find(comment => { var _a; return (_a = comment.body) === null || _a === void 0 ? void 0 : _a.includes(commentBodyIncludes); });
|
||||
if (existingComment)
|
||||
return existingComment.id;
|
||||
}
|
||||
finally {
|
||||
_d = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = commentsIterator_1.return)) yield _b.call(commentsIterator_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4966:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
@@ -123,18 +259,6 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getInvalidLicenseChanges = void 0;
|
||||
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
/**
|
||||
* Loops through a list of changes, filtering and returning the
|
||||
* ones that don't conform to the licenses allow/deny lists.
|
||||
*
|
||||
* Keep in mind that we don't let users specify both an allow and a deny
|
||||
* list in their config files, so this code works under the assumption that
|
||||
* one of the two list parameters will be empty. If both lists are provided,
|
||||
* we will ignore the deny list.
|
||||
* @param {Change[]} changes The list of changes to filter.
|
||||
* @param { { allow?: string[], deny?: string[]}} licenses An object with `allow`/`deny` keys, each containing a list of licenses.
|
||||
* @returns {Promise<{Object.<string, Array.<Change>>}} A promise to a Record Object. The keys are strings, unlicensed, unresolved and forbidden. The values are a list of changes
|
||||
*/
|
||||
function getInvalidLicenseChanges(changes, licenses) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { allow, deny } = licenses;
|
||||
@@ -322,6 +446,7 @@ const licenses_1 = __nccwpck_require__(3247);
|
||||
const summary = __importStar(__nccwpck_require__(8608));
|
||||
const git_refs_1 = __nccwpck_require__(1086);
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
const comment_pr_1 = __nccwpck_require__(5842);
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
@@ -333,20 +458,24 @@ function run() {
|
||||
baseRef: refs.base,
|
||||
headRef: refs.head
|
||||
});
|
||||
if (!changes) {
|
||||
core.info('No Dependency Changes found. Skipping Dependency Review.');
|
||||
return;
|
||||
}
|
||||
const minSeverity = config.fail_on_severity;
|
||||
const scopedChanges = (0, filter_1.filterChangesByScopes)(config.fail_on_scopes, changes);
|
||||
const filteredChanges = (0, filter_1.filterAllowedAdvisories)(config.allow_ghsas, scopedChanges);
|
||||
const addedChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, filteredChanges).filter(change => change.change_type === 'added' &&
|
||||
const vulnerableChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, filteredChanges).filter(change => change.change_type === 'added' &&
|
||||
change.vulnerabilities !== undefined &&
|
||||
change.vulnerabilities.length > 0);
|
||||
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
|
||||
allow: config.allow_licenses,
|
||||
deny: config.deny_licenses
|
||||
});
|
||||
summary.addSummaryToSummary(config.vulnerability_check ? addedChanges : null, config.license_check ? invalidLicenseChanges : null);
|
||||
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
||||
if (config.vulnerability_check) {
|
||||
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity);
|
||||
printVulnerabilitiesBlock(addedChanges, minSeverity);
|
||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
|
||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity);
|
||||
}
|
||||
if (config.license_check) {
|
||||
summary.addLicensesToSummary(invalidLicenseChanges, config);
|
||||
@@ -354,6 +483,9 @@ function run() {
|
||||
}
|
||||
summary.addScannedDependencies(changes);
|
||||
printScannedDependencies(changes);
|
||||
if (config.comment_summary_in_pr) {
|
||||
yield (0, comment_pr_1.commentPr)(core.summary);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof request_error_1.RequestError && error.status === 404) {
|
||||
@@ -539,7 +671,8 @@ exports.ConfigurationOptionsSchema = z
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional()
|
||||
head_ref: z.string().optional(),
|
||||
comment_summary_in_pr: z.boolean().default(false)
|
||||
})
|
||||
.superRefine((config, context) => {
|
||||
if (config.allow_licenses && config.deny_licenses) {
|
||||
@@ -599,36 +732,54 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.addScannedDependencies = exports.addLicensesToSummary = exports.addChangeVulnerabilitiesToSummary = exports.addSummaryToSummary = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
function addSummaryToSummary(addedPackages, invalidLicenseChanges) {
|
||||
core.summary
|
||||
.addHeading('Dependency Review')
|
||||
.addRaw('We found:')
|
||||
.addList([
|
||||
...(addedPackages
|
||||
? [`${addedPackages.length} vulnerable package(s)`]
|
||||
: []),
|
||||
...(invalidLicenseChanges
|
||||
? [
|
||||
`${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`,
|
||||
`${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`,
|
||||
`${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
|
||||
]
|
||||
: [])
|
||||
]);
|
||||
}
|
||||
exports.addSummaryToSummary = addSummaryToSummary;
|
||||
function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
|
||||
const rows = [];
|
||||
const manifests = (0, utils_1.getManifestsSet)(addedPackages);
|
||||
core.summary
|
||||
.addHeading('Vulnerabilities')
|
||||
.addQuote(`Vulnerabilities were filtered by minimum severity <strong>${severity}</strong>.`);
|
||||
if (addedPackages.length === 0) {
|
||||
core.summary.addQuote('No vulnerabilities found in added packages.');
|
||||
const icons = {
|
||||
check: '✅',
|
||||
cross: '❌',
|
||||
warning: '⚠️'
|
||||
};
|
||||
function addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config) {
|
||||
core.summary.addHeading('Dependency Review', 1);
|
||||
if (vulnerableChanges.length === 0 &&
|
||||
countLicenseIssues(invalidLicenseChanges) === 0) {
|
||||
if (!config.license_check) {
|
||||
core.summary.addRaw(`${icons.check} No vulnerabilities found.`);
|
||||
}
|
||||
else if (!config.vulnerability_check) {
|
||||
core.summary.addRaw(`${icons.check} No license issues found.`);
|
||||
}
|
||||
else {
|
||||
core.summary.addRaw(`${icons.check} No vulnerabilities or license issues found.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
core.summary
|
||||
.addRaw('The following issues were found:')
|
||||
.addList([
|
||||
...(config.vulnerability_check
|
||||
? [
|
||||
`${checkOrFailIcon(vulnerableChanges.length)} ${vulnerableChanges.length} vulnerable package(s)`
|
||||
]
|
||||
: []),
|
||||
...(config.license_check
|
||||
? [
|
||||
`${checkOrFailIcon(invalidLicenseChanges.forbidden.length)} ${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`,
|
||||
`${checkOrFailIcon(invalidLicenseChanges.unresolved.length)} ${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`,
|
||||
`${checkOrWarnIcon(invalidLicenseChanges.unlicensed.length)} ${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
|
||||
]
|
||||
: [])
|
||||
])
|
||||
.addRaw('See the Details below.');
|
||||
}
|
||||
exports.addSummaryToSummary = addSummaryToSummary;
|
||||
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
||||
if (vulnerableChanges.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
const manifests = (0, utils_1.getManifestsSet)(vulnerableChanges);
|
||||
core.summary.addHeading('Vulnerabilities', 2);
|
||||
for (const manifest of manifests) {
|
||||
for (const change of addedPackages.filter(pkg => pkg.manifest === manifest)) {
|
||||
for (const change of vulnerableChanges.filter(pkg => pkg.manifest === manifest)) {
|
||||
let previous_package = '';
|
||||
let previous_version = '';
|
||||
for (const vuln of change.vulnerabilities) {
|
||||
@@ -653,7 +804,7 @@ function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
|
||||
previous_version = change.version;
|
||||
}
|
||||
}
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 3).addTable([
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4).addTable([
|
||||
[
|
||||
{ data: 'Name', header: true },
|
||||
{ data: 'Version', header: true },
|
||||
@@ -663,46 +814,58 @@ function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
|
||||
...rows
|
||||
]);
|
||||
}
|
||||
if (severity !== 'low') {
|
||||
core.summary.addQuote(`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`);
|
||||
}
|
||||
}
|
||||
exports.addChangeVulnerabilitiesToSummary = addChangeVulnerabilitiesToSummary;
|
||||
function addLicensesToSummary(invalidLicenseChanges, config) {
|
||||
core.summary.addHeading('Licenses');
|
||||
if (countLicenseIssues(invalidLicenseChanges) === 0) {
|
||||
return;
|
||||
}
|
||||
core.summary.addHeading('License Issues', 2);
|
||||
printLicenseViolations(invalidLicenseChanges);
|
||||
if (config.allow_licenses && config.allow_licenses.length > 0) {
|
||||
core.summary.addQuote(`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`);
|
||||
}
|
||||
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`);
|
||||
}
|
||||
if (Object.values(invalidLicenseChanges).every(item => item.length === 0)) {
|
||||
core.summary.addQuote('No license violations detected.');
|
||||
return;
|
||||
}
|
||||
core.debug(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
|
||||
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`);
|
||||
printLicenseViolation('Incompatible Licenses', invalidLicenseChanges.forbidden);
|
||||
printLicenseViolation('Unknown Licenses', invalidLicenseChanges.unlicensed);
|
||||
printLicenseViolation('Invalid SPDX License Definitions', invalidLicenseChanges.unresolved);
|
||||
}
|
||||
exports.addLicensesToSummary = addLicensesToSummary;
|
||||
function printLicenseViolation(heading, changes) {
|
||||
core.summary.addHeading(heading, 5).addSeparator();
|
||||
if (changes.length > 0) {
|
||||
const rows = [];
|
||||
const manifests = (0, utils_1.getManifestsSet)(changes);
|
||||
for (const manifest of manifests) {
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4);
|
||||
for (const change of changes.filter(pkg => pkg.manifest === manifest)) {
|
||||
rows.push([
|
||||
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
formatLicense(change.license)
|
||||
]);
|
||||
const licenseIssueTypes = [
|
||||
'forbidden',
|
||||
'unresolved',
|
||||
'unlicensed'
|
||||
];
|
||||
const issueTypeNames = {
|
||||
forbidden: 'Incompatible License',
|
||||
unresolved: 'Invalid SPDX License',
|
||||
unlicensed: 'Unknown License'
|
||||
};
|
||||
function printLicenseViolations(changes) {
|
||||
const rowsGroupedByManifest = {};
|
||||
for (const issueType of licenseIssueTypes) {
|
||||
for (const change of changes[issueType]) {
|
||||
if (!rowsGroupedByManifest[change.manifest]) {
|
||||
rowsGroupedByManifest[change.manifest] = [];
|
||||
}
|
||||
core.summary.addTable([['Package', 'Version', 'License'], ...rows]);
|
||||
rowsGroupedByManifest[change.manifest].push([
|
||||
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
formatLicense(change.license),
|
||||
issueTypeNames[issueType]
|
||||
]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`);
|
||||
for (const [manifest, rows] of Object.entries(rowsGroupedByManifest)) {
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4);
|
||||
core.summary.addTable([
|
||||
['Package', 'Version', 'License', 'Issue Type'],
|
||||
...rows
|
||||
]);
|
||||
}
|
||||
}
|
||||
function formatLicense(license) {
|
||||
@@ -714,9 +877,7 @@ function formatLicense(license) {
|
||||
function addScannedDependencies(changes) {
|
||||
const dependencies = (0, utils_1.groupDependenciesByManifest)(changes);
|
||||
const manifests = dependencies.keys();
|
||||
const summary = core.summary
|
||||
.addHeading('Scanned Dependencies')
|
||||
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5);
|
||||
const summary = core.summary.addHeading('Scanned Manifest Files', 2);
|
||||
for (const manifest of manifests) {
|
||||
const deps = dependencies.get(manifest);
|
||||
if (deps) {
|
||||
@@ -726,6 +887,15 @@ function addScannedDependencies(changes) {
|
||||
}
|
||||
}
|
||||
exports.addScannedDependencies = addScannedDependencies;
|
||||
function countLicenseIssues(invalidLicenseChanges) {
|
||||
return Object.values(invalidLicenseChanges).reduce((acc, val) => acc + val.length, 0);
|
||||
}
|
||||
function checkOrFailIcon(count) {
|
||||
return count === 0 ? icons.check : icons.cross;
|
||||
}
|
||||
function checkOrWarnIcon(count) {
|
||||
return count === 0 ? icons.check : icons.warning;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -41199,10 +41369,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.z = void 0;
|
||||
const mod = __importStar(__nccwpck_require__(9906));
|
||||
exports.z = mod;
|
||||
const z = __importStar(__nccwpck_require__(9906));
|
||||
exports.z = z;
|
||||
__exportStar(__nccwpck_require__(9906), exports);
|
||||
exports["default"] = mod;
|
||||
exports["default"] = z;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -41285,7 +41455,7 @@ const errorMap = (issue, _ctx) => {
|
||||
? `exactly equal to `
|
||||
: issue.inclusive
|
||||
? `greater than or equal to `
|
||||
: `greater than `}${new Date(issue.minimum)}`;
|
||||
: `greater than `}${new Date(Number(issue.minimum))}`;
|
||||
else
|
||||
message = "Invalid input";
|
||||
break;
|
||||
@@ -41300,12 +41470,18 @@ const errorMap = (issue, _ctx) => {
|
||||
: issue.inclusive
|
||||
? `less than or equal to`
|
||||
: `less than`} ${issue.maximum}`;
|
||||
else if (issue.type === "bigint")
|
||||
message = `BigInt must be ${issue.exact
|
||||
? `exactly`
|
||||
: issue.inclusive
|
||||
? `less than or equal to`
|
||||
: `less than`} ${issue.maximum}`;
|
||||
else if (issue.type === "date")
|
||||
message = `Date must be ${issue.exact
|
||||
? `exactly`
|
||||
: issue.inclusive
|
||||
? `smaller than or equal to`
|
||||
: `smaller than`} ${new Date(issue.maximum)}`;
|
||||
: `smaller than`} ${new Date(Number(issue.maximum))}`;
|
||||
else
|
||||
message = "Invalid input";
|
||||
break;
|
||||
@@ -41648,13 +41824,11 @@ const uuidRegex = /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-
|
||||
//old email regex
|
||||
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
|
||||
// eslint-disable-next-line
|
||||
const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|([^-]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}))$/;
|
||||
// interface IsDateStringOptions extends StringDateOptions {
|
||||
/**
|
||||
* Match any configuration
|
||||
*/
|
||||
// any?: boolean;
|
||||
// }
|
||||
const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
|
||||
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
|
||||
const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;
|
||||
const ipv4Regex = /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/;
|
||||
const ipv6Regex = /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/;
|
||||
// Adapted from https://stackoverflow.com/a/3143231
|
||||
const datetimeRegex = (args) => {
|
||||
if (args.precision) {
|
||||
@@ -41682,6 +41856,15 @@ const datetimeRegex = (args) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
function isValidIP(ip, version) {
|
||||
if ((version === "v4" || !version) && ipv4Regex.test(ip)) {
|
||||
return true;
|
||||
}
|
||||
if ((version === "v6" || !version) && ipv6Regex.test(ip)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
class ZodString extends ZodType {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
@@ -41699,6 +41882,14 @@ class ZodString extends ZodType {
|
||||
...this._def,
|
||||
checks: [...this._def.checks, { kind: "trim" }],
|
||||
});
|
||||
this.toLowerCase = () => new ZodString({
|
||||
...this._def,
|
||||
checks: [...this._def.checks, { kind: "toLowerCase" }],
|
||||
});
|
||||
this.toUpperCase = () => new ZodString({
|
||||
...this._def,
|
||||
checks: [...this._def.checks, { kind: "toUpperCase" }],
|
||||
});
|
||||
}
|
||||
_parse(input) {
|
||||
if (this._def.coerce) {
|
||||
@@ -41786,6 +41977,17 @@ class ZodString extends ZodType {
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else if (check.kind === "emoji") {
|
||||
if (!emojiRegex.test(input.data)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
(0, parseUtil_1.addIssueToContext)(ctx, {
|
||||
validation: "emoji",
|
||||
code: ZodError_1.ZodIssueCode.invalid_string,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else if (check.kind === "uuid") {
|
||||
if (!uuidRegex.test(input.data)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
@@ -41849,6 +42051,12 @@ class ZodString extends ZodType {
|
||||
else if (check.kind === "trim") {
|
||||
input.data = input.data.trim();
|
||||
}
|
||||
else if (check.kind === "toLowerCase") {
|
||||
input.data = input.data.toLowerCase();
|
||||
}
|
||||
else if (check.kind === "toUpperCase") {
|
||||
input.data = input.data.toUpperCase();
|
||||
}
|
||||
else if (check.kind === "startsWith") {
|
||||
if (!input.data.startsWith(check.value)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
@@ -41883,6 +42091,17 @@ class ZodString extends ZodType {
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else if (check.kind === "ip") {
|
||||
if (!isValidIP(input.data, check.version)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
(0, parseUtil_1.addIssueToContext)(ctx, {
|
||||
validation: "ip",
|
||||
code: ZodError_1.ZodIssueCode.invalid_string,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else {
|
||||
util_1.util.assertNever(check);
|
||||
}
|
||||
@@ -41901,6 +42120,9 @@ class ZodString extends ZodType {
|
||||
url(message) {
|
||||
return this._addCheck({ kind: "url", ...errorUtil_1.errorUtil.errToObj(message) });
|
||||
}
|
||||
emoji(message) {
|
||||
return this._addCheck({ kind: "emoji", ...errorUtil_1.errorUtil.errToObj(message) });
|
||||
}
|
||||
uuid(message) {
|
||||
return this._addCheck({ kind: "uuid", ...errorUtil_1.errorUtil.errToObj(message) });
|
||||
}
|
||||
@@ -41910,6 +42132,9 @@ class ZodString extends ZodType {
|
||||
cuid2(message) {
|
||||
return this._addCheck({ kind: "cuid2", ...errorUtil_1.errorUtil.errToObj(message) });
|
||||
}
|
||||
ip(options) {
|
||||
return this._addCheck({ kind: "ip", ...errorUtil_1.errorUtil.errToObj(options) });
|
||||
}
|
||||
datetime(options) {
|
||||
var _a;
|
||||
if (typeof options === "string") {
|
||||
@@ -41978,6 +42203,9 @@ class ZodString extends ZodType {
|
||||
get isURL() {
|
||||
return !!this._def.checks.find((ch) => ch.kind === "url");
|
||||
}
|
||||
get isEmoji() {
|
||||
return !!this._def.checks.find((ch) => ch.kind === "emoji");
|
||||
}
|
||||
get isUUID() {
|
||||
return !!this._def.checks.find((ch) => ch.kind === "uuid");
|
||||
}
|
||||
@@ -41987,6 +42215,9 @@ class ZodString extends ZodType {
|
||||
get isCUID2() {
|
||||
return !!this._def.checks.find((ch) => ch.kind === "cuid2");
|
||||
}
|
||||
get isIP() {
|
||||
return !!this._def.checks.find((ch) => ch.kind === "ip");
|
||||
}
|
||||
get minLength() {
|
||||
let min = null;
|
||||
for (const ch of this._def.checks) {
|
||||
@@ -42207,6 +42438,19 @@ class ZodNumber extends ZodType {
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
safe(message) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
inclusive: true,
|
||||
value: Number.MIN_SAFE_INTEGER,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
})._addCheck({
|
||||
kind: "max",
|
||||
inclusive: true,
|
||||
value: Number.MAX_SAFE_INTEGER,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
get minValue() {
|
||||
let min = null;
|
||||
for (const ch of this._def.checks) {
|
||||
@@ -42261,6 +42505,11 @@ ZodNumber.create = (params) => {
|
||||
});
|
||||
};
|
||||
class ZodBigInt extends ZodType {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.min = this.gte;
|
||||
this.max = this.lte;
|
||||
}
|
||||
_parse(input) {
|
||||
if (this._def.coerce) {
|
||||
input.data = BigInt(input.data);
|
||||
@@ -42275,13 +42524,155 @@ class ZodBigInt extends ZodType {
|
||||
});
|
||||
return parseUtil_1.INVALID;
|
||||
}
|
||||
return (0, parseUtil_1.OK)(input.data);
|
||||
let ctx = undefined;
|
||||
const status = new parseUtil_1.ParseStatus();
|
||||
for (const check of this._def.checks) {
|
||||
if (check.kind === "min") {
|
||||
const tooSmall = check.inclusive
|
||||
? input.data < check.value
|
||||
: input.data <= check.value;
|
||||
if (tooSmall) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
(0, parseUtil_1.addIssueToContext)(ctx, {
|
||||
code: ZodError_1.ZodIssueCode.too_small,
|
||||
type: "bigint",
|
||||
minimum: check.value,
|
||||
inclusive: check.inclusive,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else if (check.kind === "max") {
|
||||
const tooBig = check.inclusive
|
||||
? input.data > check.value
|
||||
: input.data >= check.value;
|
||||
if (tooBig) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
(0, parseUtil_1.addIssueToContext)(ctx, {
|
||||
code: ZodError_1.ZodIssueCode.too_big,
|
||||
type: "bigint",
|
||||
maximum: check.value,
|
||||
inclusive: check.inclusive,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else if (check.kind === "multipleOf") {
|
||||
if (input.data % check.value !== BigInt(0)) {
|
||||
ctx = this._getOrReturnCtx(input, ctx);
|
||||
(0, parseUtil_1.addIssueToContext)(ctx, {
|
||||
code: ZodError_1.ZodIssueCode.not_multiple_of,
|
||||
multipleOf: check.value,
|
||||
message: check.message,
|
||||
});
|
||||
status.dirty();
|
||||
}
|
||||
}
|
||||
else {
|
||||
util_1.util.assertNever(check);
|
||||
}
|
||||
}
|
||||
return { status: status.value, value: input.data };
|
||||
}
|
||||
gte(value, message) {
|
||||
return this.setLimit("min", value, true, errorUtil_1.errorUtil.toString(message));
|
||||
}
|
||||
gt(value, message) {
|
||||
return this.setLimit("min", value, false, errorUtil_1.errorUtil.toString(message));
|
||||
}
|
||||
lte(value, message) {
|
||||
return this.setLimit("max", value, true, errorUtil_1.errorUtil.toString(message));
|
||||
}
|
||||
lt(value, message) {
|
||||
return this.setLimit("max", value, false, errorUtil_1.errorUtil.toString(message));
|
||||
}
|
||||
setLimit(kind, value, inclusive, message) {
|
||||
return new ZodBigInt({
|
||||
...this._def,
|
||||
checks: [
|
||||
...this._def.checks,
|
||||
{
|
||||
kind,
|
||||
value,
|
||||
inclusive,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
_addCheck(check) {
|
||||
return new ZodBigInt({
|
||||
...this._def,
|
||||
checks: [...this._def.checks, check],
|
||||
});
|
||||
}
|
||||
positive(message) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
value: BigInt(0),
|
||||
inclusive: false,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
negative(message) {
|
||||
return this._addCheck({
|
||||
kind: "max",
|
||||
value: BigInt(0),
|
||||
inclusive: false,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
nonpositive(message) {
|
||||
return this._addCheck({
|
||||
kind: "max",
|
||||
value: BigInt(0),
|
||||
inclusive: true,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
nonnegative(message) {
|
||||
return this._addCheck({
|
||||
kind: "min",
|
||||
value: BigInt(0),
|
||||
inclusive: true,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
multipleOf(value, message) {
|
||||
return this._addCheck({
|
||||
kind: "multipleOf",
|
||||
value,
|
||||
message: errorUtil_1.errorUtil.toString(message),
|
||||
});
|
||||
}
|
||||
get minValue() {
|
||||
let min = null;
|
||||
for (const ch of this._def.checks) {
|
||||
if (ch.kind === "min") {
|
||||
if (min === null || ch.value > min)
|
||||
min = ch.value;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
get maxValue() {
|
||||
let max = null;
|
||||
for (const ch of this._def.checks) {
|
||||
if (ch.kind === "max") {
|
||||
if (max === null || ch.value < max)
|
||||
max = ch.value;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
exports.ZodBigInt = ZodBigInt;
|
||||
ZodBigInt.create = (params) => {
|
||||
var _a;
|
||||
return new ZodBigInt({
|
||||
checks: [],
|
||||
typeName: ZodFirstPartyTypeKind.ZodBigInt,
|
||||
coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false,
|
||||
...processCreateParams(params),
|
||||
@@ -43023,6 +43414,9 @@ class ZodObject extends ZodType {
|
||||
shape: () => shape,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
deepPartial() {
|
||||
return deepPartialify(this);
|
||||
}
|
||||
@@ -44194,29 +44588,45 @@ ZodDefault.create = (type, params) => {
|
||||
class ZodCatch extends ZodType {
|
||||
_parse(input) {
|
||||
const { ctx } = this._processInputParams(input);
|
||||
// newCtx is used to not collect issues from inner types in ctx
|
||||
const newCtx = {
|
||||
...ctx,
|
||||
common: {
|
||||
...ctx.common,
|
||||
issues: [],
|
||||
},
|
||||
};
|
||||
const result = this._def.innerType._parse({
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
data: newCtx.data,
|
||||
path: newCtx.path,
|
||||
parent: {
|
||||
...ctx,
|
||||
common: {
|
||||
...ctx.common,
|
||||
issues: [], // don't collect issues from inner type
|
||||
},
|
||||
...newCtx,
|
||||
},
|
||||
});
|
||||
if ((0, parseUtil_1.isAsync)(result)) {
|
||||
return result.then((result) => {
|
||||
return {
|
||||
status: "valid",
|
||||
value: result.status === "valid" ? result.value : this._def.catchValue(),
|
||||
value: result.status === "valid"
|
||||
? result.value
|
||||
: this._def.catchValue({
|
||||
get error() {
|
||||
return new ZodError_1.ZodError(newCtx.common.issues);
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
else {
|
||||
return {
|
||||
status: "valid",
|
||||
value: result.status === "valid" ? result.value : this._def.catchValue(),
|
||||
value: result.status === "valid"
|
||||
? result.value
|
||||
: this._def.catchValue({
|
||||
get error() {
|
||||
return new ZodError_1.ZodError(newCtx.common.issues);
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -44330,13 +44740,17 @@ class ZodPipeline extends ZodType {
|
||||
}
|
||||
}
|
||||
exports.ZodPipeline = ZodPipeline;
|
||||
const custom = (check, params = {}, fatal) => {
|
||||
const custom = (check, params = {},
|
||||
/* @deprecated */
|
||||
fatal) => {
|
||||
if (check)
|
||||
return ZodAny.create().superRefine((data, ctx) => {
|
||||
var _a, _b;
|
||||
if (!check(data)) {
|
||||
const p = typeof params === "function" ? params(data) : params;
|
||||
const _fatal = (_b = (_a = p.fatal) !== null && _a !== void 0 ? _a : fatal) !== null && _b !== void 0 ? _b : true;
|
||||
const p2 = typeof p === "string" ? { message: p } : p;
|
||||
ctx.addIssue({ code: "custom", ...p2, fatal });
|
||||
ctx.addIssue({ code: "custom", ...p2, fatal: _fatal });
|
||||
}
|
||||
});
|
||||
return ZodAny.create();
|
||||
@@ -44392,7 +44806,7 @@ const instanceOfType = (
|
||||
// const instanceOfType = <T extends new (...args: any[]) => any>(
|
||||
cls, params = {
|
||||
message: `Input not instance of ${cls.name}`,
|
||||
}) => (0, exports.custom)((data) => data instanceof cls, params, true);
|
||||
}) => (0, exports.custom)((data) => data instanceof cls, params);
|
||||
exports["instanceof"] = instanceOfType;
|
||||
const stringType = ZodString.create;
|
||||
exports.string = stringType;
|
||||
@@ -44555,6 +44969,7 @@ function readInlineConfig() {
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check');
|
||||
const base_ref = getOptionalInput('base-ref');
|
||||
const head_ref = getOptionalInput('head-ref');
|
||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr');
|
||||
validateLicenses('allow-licenses', allow_licenses);
|
||||
validateLicenses('deny-licenses', deny_licenses);
|
||||
const keys = {
|
||||
@@ -44566,7 +44981,8 @@ function readInlineConfig() {
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
base_ref,
|
||||
head_ref
|
||||
head_ref,
|
||||
comment_summary_in_pr
|
||||
};
|
||||
return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined));
|
||||
}
|
||||
@@ -44831,7 +45247,8 @@ exports.ConfigurationOptionsSchema = z
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional()
|
||||
head_ref: z.string().optional(),
|
||||
comment_summary_in_pr: z.boolean().default(false)
|
||||
})
|
||||
.superRefine((config, context) => {
|
||||
if (config.allow_licenses && config.deny_licenses) {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1052
-284
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -5,7 +5,7 @@
|
||||
"description": "A GitHub Action for Dependency Review",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"format": "prettier --write '**/*.ts'",
|
||||
"format-check": "prettier --check '**/*.ts'",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
@@ -30,31 +30,31 @@
|
||||
"@octokit/plugin-retry": "^4.1.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"got": "^12.5.3",
|
||||
"nodemon": "^2.0.20",
|
||||
"got": "^12.6.0",
|
||||
"nodemon": "^2.0.21",
|
||||
"octokit": "^2.0.14",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"yaml": "^2.2.1",
|
||||
"zod": "^3.20.6"
|
||||
"zod": "^3.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.12",
|
||||
"@types/node": "^16.18.14",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@types/spdx-expression-parse": "^3.0.2",
|
||||
"@types/spdx-satisfies": "^0.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||
"@typescript-eslint/parser": "^5.53.0",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"esbuild-register": "^3.4.2",
|
||||
"eslint": "^8.34.0",
|
||||
"eslint-plugin-github": "^4.6.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-github": "^4.6.1",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"jest": "^27.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"nodemon": "^2.0.21",
|
||||
"prettier": "2.8.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.9.5"
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* This scripts creates example markdown files for the summary in the ./tmp folder.
|
||||
* You can use it to preview changes to the summary.
|
||||
*
|
||||
* You can execute it like this:
|
||||
* npx ts-node scripts/create_summary.ts
|
||||
*/
|
||||
|
||||
import {Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import {createTestChange} from '../__tests__/fixtures/create-test-change'
|
||||
import {InvalidLicenseChanges} from '../src/licenses'
|
||||
import * as fs from 'fs'
|
||||
import * as core from '@actions/core'
|
||||
import * as summary from '../src/summary'
|
||||
import * as path from 'path'
|
||||
|
||||
const defaultConfig: ConfigurationOptions = {
|
||||
vulnerability_check: true,
|
||||
license_check: true,
|
||||
fail_on_severity: 'high',
|
||||
fail_on_scopes: ['runtime'],
|
||||
allow_ghsas: [],
|
||||
allow_licenses: ['MIT'],
|
||||
deny_licenses: [],
|
||||
comment_summary_in_pr: true
|
||||
}
|
||||
|
||||
const tmpDir = path.resolve(__dirname, '../tmp')
|
||||
|
||||
const createExampleSummaries = async (): Promise<void> => {
|
||||
await fs.promises.mkdir(tmpDir, {recursive: true})
|
||||
|
||||
await createNonIssueSummary()
|
||||
await createFullSummary()
|
||||
}
|
||||
|
||||
const createNonIssueSummary = async (): Promise<void> => {
|
||||
await createSummary(
|
||||
[],
|
||||
{forbidden: [], unresolved: [], unlicensed: []},
|
||||
defaultConfig,
|
||||
'non-issue-summary.md'
|
||||
)
|
||||
}
|
||||
|
||||
const createFullSummary = async (): Promise<void> => {
|
||||
const changes = [createTestChange()]
|
||||
const licenses: InvalidLicenseChanges = {
|
||||
forbidden: [
|
||||
createTestChange({
|
||||
name: 'underscore',
|
||||
version: '1.12.0',
|
||||
license: 'Apache 2.0'
|
||||
})
|
||||
],
|
||||
unresolved: [
|
||||
createTestChange({
|
||||
name: 'octoinvader',
|
||||
license: 'Non SPDX License'
|
||||
}),
|
||||
createTestChange({
|
||||
name: 'owner/action-1',
|
||||
license: 'XYZ-License',
|
||||
version: 'v1.2.2',
|
||||
manifest: '.github/workflows/action.yml'
|
||||
})
|
||||
],
|
||||
unlicensed: [
|
||||
createTestChange({
|
||||
name: 'my-other-dependency',
|
||||
license: null
|
||||
}),
|
||||
createTestChange({
|
||||
name: 'owner/action-2',
|
||||
version: 'main',
|
||||
license: null,
|
||||
manifest: '.github/workflows/action.yml'
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
await createSummary(changes, licenses, defaultConfig, 'full-summary.md')
|
||||
}
|
||||
|
||||
async function createSummary(
|
||||
vulnerabilities: Changes,
|
||||
licenseIssues: InvalidLicenseChanges,
|
||||
config: ConfigurationOptions,
|
||||
fileName: string
|
||||
): Promise<void> {
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, config)
|
||||
summary.addChangeVulnerabilitiesToSummary(
|
||||
vulnerabilities,
|
||||
config.fail_on_severity
|
||||
)
|
||||
summary.addLicensesToSummary(licenseIssues, defaultConfig)
|
||||
|
||||
const allChanges = [
|
||||
...vulnerabilities,
|
||||
...licenseIssues.forbidden,
|
||||
...licenseIssues.unresolved,
|
||||
...licenseIssues.unlicensed
|
||||
]
|
||||
|
||||
summary.addScannedDependencies(allChanges)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
await fs.promises.writeFile(path.resolve(tmpDir, fileName), text, {
|
||||
flag: 'w'
|
||||
})
|
||||
core.summary.emptyBuffer()
|
||||
}
|
||||
|
||||
createExampleSummaries()
|
||||
@@ -0,0 +1,84 @@
|
||||
import * as github from '@actions/github'
|
||||
import * as core from '@actions/core'
|
||||
import * as githubUtils from '@actions/github/lib/utils'
|
||||
import * as retry from '@octokit/plugin-retry'
|
||||
import {RequestError} from '@octokit/request-error'
|
||||
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry)
|
||||
const octo = new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(core.getInput('repo-token', {required: true}))
|
||||
)
|
||||
|
||||
// Comment Marker to identify an existing comment to update, so we don't spam the PR with comments
|
||||
const COMMENT_MARKER = '<!-- dependency-review-pr-comment-marker -->'
|
||||
|
||||
export async function commentPr(summary: typeof core.summary): Promise<void> {
|
||||
if (!github.context.payload.pull_request) {
|
||||
core.warning(
|
||||
'Not in the context of a pull request. Skipping comment creation.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const commentBody = `${summary.stringify()}\n\n${COMMENT_MARKER}`
|
||||
|
||||
try {
|
||||
const existingCommentId = await findCommentByMarker(COMMENT_MARKER)
|
||||
|
||||
if (existingCommentId) {
|
||||
await octo.rest.issues.updateComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
comment_id: existingCommentId,
|
||||
body: commentBody
|
||||
})
|
||||
} else {
|
||||
await octo.rest.issues.createComment({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: github.context.payload.pull_request.number,
|
||||
body: commentBody
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError && error.status === 403) {
|
||||
core.warning(
|
||||
`Unable to write summary to pull-request. Make sure you are giving this workflow the permission 'pull-requests: write'.`
|
||||
)
|
||||
} else {
|
||||
if (error instanceof Error) {
|
||||
core.warning(
|
||||
`Unable to comment summary to pull-request, received error: ${error.message}`
|
||||
)
|
||||
} else {
|
||||
core.warning(
|
||||
'Unable to comment summary to pull-request: Unexpected fatal error'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function findCommentByMarker(
|
||||
commentBodyIncludes: string
|
||||
): Promise<number | undefined> {
|
||||
const commentsIterator = octo.paginate.iterator(
|
||||
octo.rest.issues.listComments,
|
||||
{
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
// We are already checking if we are in the context of a pull request in the caller
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
issue_number: github.context.payload.pull_request!.number
|
||||
}
|
||||
)
|
||||
|
||||
for await (const {data: comments} of commentsIterator) {
|
||||
const existingComment = comments.find(comment =>
|
||||
comment.body?.includes(commentBodyIncludes)
|
||||
)
|
||||
if (existingComment) return existingComment.id
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
+3
-1
@@ -34,6 +34,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||
const base_ref = getOptionalInput('base-ref')
|
||||
const head_ref = getOptionalInput('head-ref')
|
||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
|
||||
|
||||
validateLicenses('allow-licenses', allow_licenses)
|
||||
validateLicenses('deny-licenses', deny_licenses)
|
||||
@@ -47,7 +48,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
base_ref,
|
||||
head_ref
|
||||
head_ref,
|
||||
comment_summary_in_pr
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
|
||||
+7
-2
@@ -14,19 +14,24 @@ import {isSPDXValid, octokitClient} from './utils'
|
||||
* @param { { allow?: string[], deny?: string[]}} licenses An object with `allow`/`deny` keys, each containing a list of licenses.
|
||||
* @returns {Promise<{Object.<string, Array.<Change>>}} A promise to a Record Object. The keys are strings, unlicensed, unresolved and forbidden. The values are a list of changes
|
||||
*/
|
||||
export type InvalidLicenseChangeTypes =
|
||||
| 'unlicensed'
|
||||
| 'unresolved'
|
||||
| 'forbidden'
|
||||
export type InvalidLicenseChanges = Record<InvalidLicenseChangeTypes, Changes>
|
||||
export async function getInvalidLicenseChanges(
|
||||
changes: Change[],
|
||||
licenses: {
|
||||
allow?: string[]
|
||||
deny?: string[]
|
||||
}
|
||||
): Promise<Record<string, Changes>> {
|
||||
): Promise<InvalidLicenseChanges> {
|
||||
const {allow, deny} = licenses
|
||||
|
||||
const groupedChanges = await groupChanges(changes)
|
||||
const licensedChanges: Changes = groupedChanges.licensed
|
||||
|
||||
const invalidLicenseChanges: Record<string, Changes> = {
|
||||
const invalidLicenseChanges: InvalidLicenseChanges = {
|
||||
unlicensed: groupedChanges.unlicensed,
|
||||
unresolved: [],
|
||||
forbidden: []
|
||||
|
||||
+15
-5
@@ -15,6 +15,7 @@ import * as summary from './summary'
|
||||
import {getRefs} from './git-refs'
|
||||
|
||||
import {groupDependenciesByManifest} from './utils'
|
||||
import {commentPr} from './comment-pr'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
@@ -28,6 +29,11 @@ async function run(): Promise<void> {
|
||||
headRef: refs.head
|
||||
})
|
||||
|
||||
if (!changes) {
|
||||
core.info('No Dependency Changes found. Skipping Dependency Review.')
|
||||
return
|
||||
}
|
||||
|
||||
const minSeverity = config.fail_on_severity
|
||||
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
|
||||
const filteredChanges = filterAllowedAdvisories(
|
||||
@@ -35,7 +41,7 @@ async function run(): Promise<void> {
|
||||
scopedChanges
|
||||
)
|
||||
|
||||
const addedChanges = filterChangesBySeverity(
|
||||
const vulnerableChanges = filterChangesBySeverity(
|
||||
minSeverity,
|
||||
filteredChanges
|
||||
).filter(
|
||||
@@ -54,13 +60,14 @@ async function run(): Promise<void> {
|
||||
)
|
||||
|
||||
summary.addSummaryToSummary(
|
||||
config.vulnerability_check ? addedChanges : null,
|
||||
config.license_check ? invalidLicenseChanges : null
|
||||
vulnerableChanges,
|
||||
invalidLicenseChanges,
|
||||
config
|
||||
)
|
||||
|
||||
if (config.vulnerability_check) {
|
||||
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity)
|
||||
printVulnerabilitiesBlock(addedChanges, minSeverity)
|
||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
|
||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
|
||||
}
|
||||
if (config.license_check) {
|
||||
summary.addLicensesToSummary(invalidLicenseChanges, config)
|
||||
@@ -69,6 +76,9 @@ async function run(): Promise<void> {
|
||||
|
||||
summary.addScannedDependencies(changes)
|
||||
printScannedDependencies(changes)
|
||||
if (config.comment_summary_in_pr) {
|
||||
await commentPr(core.summary)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError && error.status === 404) {
|
||||
core.setFailed(
|
||||
|
||||
+2
-1
@@ -45,7 +45,8 @@ export const ConfigurationOptionsSchema = z
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional()
|
||||
head_ref: z.string().optional(),
|
||||
comment_summary_in_pr: z.boolean().default(false)
|
||||
})
|
||||
.superRefine((config, context) => {
|
||||
if (config.allow_licenses && config.deny_licenses) {
|
||||
|
||||
+128
-71
@@ -1,50 +1,82 @@
|
||||
import * as core from '@actions/core'
|
||||
import {ConfigurationOptions, Changes} from './schemas'
|
||||
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
|
||||
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
|
||||
|
||||
export function addSummaryToSummary(
|
||||
addedPackages: Changes | null,
|
||||
invalidLicenseChanges: Record<string, Changes> | null
|
||||
): void {
|
||||
core.summary
|
||||
.addHeading('Dependency Review')
|
||||
.addRaw('We found:')
|
||||
.addList([
|
||||
...(addedPackages
|
||||
? [`${addedPackages.length} vulnerable package(s)`]
|
||||
: []),
|
||||
...(invalidLicenseChanges
|
||||
? [
|
||||
`${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`,
|
||||
`${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`,
|
||||
`${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
|
||||
]
|
||||
: [])
|
||||
])
|
||||
const icons = {
|
||||
check: '✅',
|
||||
cross: '❌',
|
||||
warning: '⚠️'
|
||||
}
|
||||
|
||||
export function addChangeVulnerabilitiesToSummary(
|
||||
addedPackages: Changes,
|
||||
severity: string
|
||||
export function addSummaryToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
invalidLicenseChanges: InvalidLicenseChanges,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
const rows: SummaryTableRow[] = []
|
||||
core.summary.addHeading('Dependency Review', 1)
|
||||
|
||||
const manifests = getManifestsSet(addedPackages)
|
||||
if (
|
||||
vulnerableChanges.length === 0 &&
|
||||
countLicenseIssues(invalidLicenseChanges) === 0
|
||||
) {
|
||||
if (!config.license_check) {
|
||||
core.summary.addRaw(`${icons.check} No vulnerabilities found.`)
|
||||
} else if (!config.vulnerability_check) {
|
||||
core.summary.addRaw(`${icons.check} No license issues found.`)
|
||||
} else {
|
||||
core.summary.addRaw(
|
||||
`${icons.check} No vulnerabilities or license issues found.`
|
||||
)
|
||||
}
|
||||
|
||||
core.summary
|
||||
.addHeading('Vulnerabilities')
|
||||
.addQuote(
|
||||
`Vulnerabilities were filtered by minimum severity <strong>${severity}</strong>.`
|
||||
)
|
||||
|
||||
if (addedPackages.length === 0) {
|
||||
core.summary.addQuote('No vulnerabilities found in added packages.')
|
||||
return
|
||||
}
|
||||
|
||||
core.summary
|
||||
.addRaw('The following issues were found:')
|
||||
.addList([
|
||||
...(config.vulnerability_check
|
||||
? [
|
||||
`${checkOrFailIcon(vulnerableChanges.length)} ${
|
||||
vulnerableChanges.length
|
||||
} vulnerable package(s)`
|
||||
]
|
||||
: []),
|
||||
...(config.license_check
|
||||
? [
|
||||
`${checkOrFailIcon(invalidLicenseChanges.forbidden.length)} ${
|
||||
invalidLicenseChanges.forbidden.length
|
||||
} package(s) with incompatible licenses`,
|
||||
`${checkOrFailIcon(invalidLicenseChanges.unresolved.length)} ${
|
||||
invalidLicenseChanges.unresolved.length
|
||||
} package(s) with invalid SPDX license definitions`,
|
||||
`${checkOrWarnIcon(invalidLicenseChanges.unlicensed.length)} ${
|
||||
invalidLicenseChanges.unlicensed.length
|
||||
} package(s) with unknown licenses.`
|
||||
]
|
||||
: [])
|
||||
])
|
||||
.addRaw('See the Details below.')
|
||||
}
|
||||
|
||||
export function addChangeVulnerabilitiesToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
severity: string
|
||||
): void {
|
||||
if (vulnerableChanges.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const rows: SummaryTableRow[] = []
|
||||
|
||||
const manifests = getManifestsSet(vulnerableChanges)
|
||||
|
||||
core.summary.addHeading('Vulnerabilities', 2)
|
||||
|
||||
for (const manifest of manifests) {
|
||||
for (const change of addedPackages.filter(
|
||||
for (const change of vulnerableChanges.filter(
|
||||
pkg => pkg.manifest === manifest
|
||||
)) {
|
||||
let previous_package = ''
|
||||
@@ -72,7 +104,7 @@ export function addChangeVulnerabilitiesToSummary(
|
||||
previous_version = change.version
|
||||
}
|
||||
}
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 3).addTable([
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4).addTable([
|
||||
[
|
||||
{data: 'Name', header: true},
|
||||
{data: 'Version', header: true},
|
||||
@@ -82,13 +114,24 @@ export function addChangeVulnerabilitiesToSummary(
|
||||
...rows
|
||||
])
|
||||
}
|
||||
|
||||
if (severity !== 'low') {
|
||||
core.summary.addQuote(
|
||||
`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function addLicensesToSummary(
|
||||
invalidLicenseChanges: Record<string, Changes>,
|
||||
invalidLicenseChanges: InvalidLicenseChanges,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.summary.addHeading('Licenses')
|
||||
if (countLicenseIssues(invalidLicenseChanges) === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
core.summary.addHeading('License Issues', 2)
|
||||
printLicenseViolations(invalidLicenseChanges)
|
||||
|
||||
if (config.allow_licenses && config.allow_licenses.length > 0) {
|
||||
core.summary.addQuote(
|
||||
@@ -101,11 +144,6 @@ export function addLicensesToSummary(
|
||||
)
|
||||
}
|
||||
|
||||
if (Object.values(invalidLicenseChanges).every(item => item.length === 0)) {
|
||||
core.summary.addQuote('No license violations detected.')
|
||||
return
|
||||
}
|
||||
|
||||
core.debug(
|
||||
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
|
||||
)
|
||||
@@ -113,39 +151,43 @@ export function addLicensesToSummary(
|
||||
core.debug(
|
||||
`${invalidLicenseChanges.unresolved.length} licenses could not be validated`
|
||||
)
|
||||
|
||||
printLicenseViolation(
|
||||
'Incompatible Licenses',
|
||||
invalidLicenseChanges.forbidden
|
||||
)
|
||||
printLicenseViolation('Unknown Licenses', invalidLicenseChanges.unlicensed)
|
||||
printLicenseViolation(
|
||||
'Invalid SPDX License Definitions',
|
||||
invalidLicenseChanges.unresolved
|
||||
)
|
||||
}
|
||||
function printLicenseViolation(heading: string, changes: Changes): void {
|
||||
core.summary.addHeading(heading, 5).addSeparator()
|
||||
|
||||
if (changes.length > 0) {
|
||||
const rows: SummaryTableRow[] = []
|
||||
const manifests = getManifestsSet(changes)
|
||||
const licenseIssueTypes: InvalidLicenseChangeTypes[] = [
|
||||
'forbidden',
|
||||
'unresolved',
|
||||
'unlicensed'
|
||||
]
|
||||
|
||||
for (const manifest of manifests) {
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4)
|
||||
const issueTypeNames: Record<InvalidLicenseChangeTypes, string> = {
|
||||
forbidden: 'Incompatible License',
|
||||
unresolved: 'Invalid SPDX License',
|
||||
unlicensed: 'Unknown License'
|
||||
}
|
||||
|
||||
for (const change of changes.filter(pkg => pkg.manifest === manifest)) {
|
||||
rows.push([
|
||||
renderUrl(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
formatLicense(change.license)
|
||||
])
|
||||
function printLicenseViolations(changes: InvalidLicenseChanges): void {
|
||||
const rowsGroupedByManifest: Record<string, SummaryTableRow[]> = {}
|
||||
|
||||
for (const issueType of licenseIssueTypes) {
|
||||
for (const change of changes[issueType]) {
|
||||
if (!rowsGroupedByManifest[change.manifest]) {
|
||||
rowsGroupedByManifest[change.manifest] = []
|
||||
}
|
||||
|
||||
core.summary.addTable([['Package', 'Version', 'License'], ...rows])
|
||||
rowsGroupedByManifest[change.manifest].push([
|
||||
renderUrl(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
formatLicense(change.license),
|
||||
issueTypeNames[issueType]
|
||||
])
|
||||
}
|
||||
} else {
|
||||
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`)
|
||||
}
|
||||
|
||||
for (const [manifest, rows] of Object.entries(rowsGroupedByManifest)) {
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4)
|
||||
core.summary.addTable([
|
||||
['Package', 'Version', 'License', 'Issue Type'],
|
||||
...rows
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +202,7 @@ export function addScannedDependencies(changes: Changes): void {
|
||||
const dependencies = groupDependenciesByManifest(changes)
|
||||
const manifests = dependencies.keys()
|
||||
|
||||
const summary = core.summary
|
||||
.addHeading('Scanned Dependencies')
|
||||
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5)
|
||||
const summary = core.summary.addHeading('Scanned Manifest Files', 2)
|
||||
|
||||
for (const manifest of manifests) {
|
||||
const deps = dependencies.get(manifest)
|
||||
@@ -174,3 +214,20 @@ export function addScannedDependencies(changes: Changes): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function countLicenseIssues(
|
||||
invalidLicenseChanges: InvalidLicenseChanges
|
||||
): number {
|
||||
return Object.values(invalidLicenseChanges).reduce(
|
||||
(acc, val) => acc + val.length,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
function checkOrFailIcon(count: number): string {
|
||||
return count === 0 ? icons.check : icons.cross
|
||||
}
|
||||
|
||||
function checkOrWarnIcon(count: number): string {
|
||||
return count === 0 ? icons.check : icons.warning
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -3,10 +3,9 @@
|
||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user