Compare commits

...
Author SHA1 Message Date
Federico Builes 5951e7db04 Merge branch 'main' into adjust_summary_format 2023-03-06 09:08:35 +01:00
Federico Builes 4f537bf170 Merge pull request #417 from actions/dependabot/npm_and_yarn/zod-3.21.0
Bump zod from 3.20.6 to 3.21.0
2023-03-06 08:25:25 +01:00
Federico Builes 25f22ad0c7 dist files 2023-03-06 08:25:10 +01:00
Federico Builes 2878425083 Merge pull request #419 from actions/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-5.54.0
Bump @typescript-eslint/eslint-plugin from 5.53.0 to 5.54.0
2023-03-06 05:56:03 +01:00
Federico Builes 862b667fee Merge pull request #421 from actions/dependabot/npm_and_yarn/nodemon-2.0.21
Bump nodemon from 2.0.20 to 2.0.21
2023-03-06 05:55:16 +01:00
dependabot[bot] f7c42c00ca Bump @typescript-eslint/eslint-plugin from 5.53.0 to 5.54.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.53.0 to 5.54.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.54.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-03-06 04:48:15 +00:00
dependabot[bot] 00dbe9df8d Bump nodemon from 2.0.20 to 2.0.21
Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.20 to 2.0.21.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v2.0.20...v2.0.21)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-03-06 04:47:54 +00:00
Federico Builes f89a053f16 Merge pull request #420 from actions/dependabot/npm_and_yarn/types/node-16.18.14
Bump @types/node from 16.18.13 to 16.18.14
2023-03-06 05:47:25 +01:00
Federico Builes 700f66ed8f Merge pull request #418 from actions/dependabot/npm_and_yarn/got-12.6.0
Bump got from 12.5.3 to 12.6.0
2023-03-06 05:47:11 +01:00
dependabot[bot] fc5eaef91a Bump @types/node from 16.18.13 to 16.18.14
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.18.13 to 16.18.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-03-06 02:15:36 +00:00
dependabot[bot] 087d445ca8 Bump got from 12.5.3 to 12.6.0
Bumps [got](https://github.com/sindresorhus/got) from 12.5.3 to 12.6.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v12.5.3...v12.6.0)

---
updated-dependencies:
- dependency-name: got
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-03-06 02:14:13 +00:00
dependabot[bot] 95fa321e74 Bump zod from 3.20.6 to 3.21.0
Bumps [zod](https://github.com/colinhacks/zod) from 3.20.6 to 3.21.0.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Changelog](https://github.com/colinhacks/zod/blob/master/CHANGELOG.md)
- [Commits](https://github.com/colinhacks/zod/compare/v3.20.6...v3.21.0)

---
updated-dependencies:
- dependency-name: zod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-03-06 02:13:53 +00:00
David Losert 429d317ccc Rebuilt dist files 2023-03-02 07:52:41 +00:00
David Losert 6b34d93738 Skips dependency review if no changes detected 2023-03-02 07:47:09 +00:00
David Losert b7a25f4e9b Makes License Issues a single table per manifest 2023-03-02 07:43:23 +00:00
David Losert 9f0792541a Rebuilt dist files 2023-03-02 06:57:41 +00:00
David Losert 5e6910e937 Built the library in it's current state 2023-03-01 07:43:17 +00:00
David Losert 715956774a Adds some explanation on how to use the script 2023-03-01 07:43:08 +00:00
David Losert 94e6fb6deb Fixes build to only include src folder 2023-03-01 07:43:00 +00:00
David Losert 1090cda9d5 Adjusts headlines and formatting for license issues 2023-02-28 12:28:20 +00:00
David Losert 6315b3822f Renames variable to be more speaking 2023-02-28 12:27:55 +00:00
David Losert c5dab80dd4 Adds script to generate test-markdown files 2023-02-28 11:08:48 +00:00
David Losert b089c5b002 Adds conditional license summary 2023-02-28 11:08:39 +00:00
David Losert 6e66d136ec Reformats vulnerability section 2023-02-27 16:05:59 +00:00
David Losert 1b9faef957 Fixes ESLint to also incldue tests and fixes eslint errors in tests 2023-02-27 16:05:03 +00:00
Federico Builes 748b8a5c33 Merge pull request #414 from actions/dependabot/npm_and_yarn/typescript-eslint/parser-5.53.0
Bump @typescript-eslint/parser from 5.52.0 to 5.53.0
2023-02-27 08:13:40 +01:00
dependabot[bot] 1639aef23d Bump @typescript-eslint/parser from 5.52.0 to 5.53.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.52.0 to 5.53.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.53.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-27 07:11:29 +00:00
Federico Builes f5f33b0c33 Merge pull request #415 from actions/dependabot/npm_and_yarn/types/node-16.18.13
Bump @types/node from 16.18.12 to 16.18.13
2023-02-27 08:08:52 +01:00
Federico Builes 2d8dd98fa8 Merge pull request #413 from actions/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-5.53.0
Bump @typescript-eslint/eslint-plugin from 5.52.0 to 5.53.0
2023-02-27 08:08:23 +01:00
Federico Builes 182833caa3 Merge pull request #412 from actions/dependabot/npm_and_yarn/eslint-8.35.0
Bump eslint from 8.34.0 to 8.35.0
2023-02-27 08:08:09 +01:00
dependabot[bot] b0bc193e06 Bump @types/node from 16.18.12 to 16.18.13
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.18.12 to 16.18.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-27 02:53:14 +00:00
dependabot[bot] f3146217d6 Bump @typescript-eslint/eslint-plugin from 5.52.0 to 5.53.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.52.0 to 5.53.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.53.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-27 02:51:25 +00:00
dependabot[bot] c5b1778acb Bump eslint from 8.34.0 to 8.35.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.34.0 to 8.35.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.34.0...v8.35.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-27 02:50:27 +00:00
David Losert 19ee172e7e feat: Adjusts the formatting and content for the status header 2023-02-22 14:05:52 +00:00
Federico Builes 23c92ea3fe Merge pull request #407 from actions/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-5.52.0
Bump @typescript-eslint/eslint-plugin from 5.51.0 to 5.52.0
2023-02-20 09:58:01 +01:00
dependabot[bot] 1af3349db2 Bump @typescript-eslint/eslint-plugin from 5.51.0 to 5.52.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.51.0 to 5.52.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.52.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-20 06:08:25 +00:00
Federico Builes 2238302e66 Merge pull request #408 from actions/dependabot/npm_and_yarn/eslint-plugin-github-4.6.1
Bump eslint-plugin-github from 4.6.0 to 4.6.1
2023-02-20 07:03:23 +01:00
Federico Builes e4158c9844 Merge pull request #405 from actions/dependabot/npm_and_yarn/typescript-eslint/parser-5.52.0
Bump @typescript-eslint/parser from 5.51.0 to 5.52.0
2023-02-20 07:02:50 +01:00
dependabot[bot] 7f874fd2fb Bump eslint-plugin-github from 4.6.0 to 4.6.1
Bumps [eslint-plugin-github](https://github.com/github/eslint-plugin-github) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-github
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-20 02:27:53 +00:00
dependabot[bot] 9928099802 Bump @typescript-eslint/parser from 5.51.0 to 5.52.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.51.0 to 5.52.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.52.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-02-20 02:26:07 +00:00
Federico Builes d9d1c4ba24 adding dist 2023-02-16 14:44:46 +01:00
Federico Builes a3ee6a76df Merge pull request #393 from davelosert/write-summary-to-pr
Add Feature: Write Summary as comment to the pull request
2023-02-16 14:44:03 +01:00
David Losert f69167c9be Build files for current version 2023-02-16 10:04:56 +00:00
David Losert 1c85e9db8d Adds option to write summary into a pr comment 2023-02-16 10:03:16 +00:00
23 changed files with 2340 additions and 505 deletions
+2
View File
@@ -100,3 +100,5 @@ Thumbs.db
# Ignore built ts files # Ignore built ts files
__tests__/runner/* __tests__/runner/*
lib/**/* lib/**/*
tmp
+18 -18
View File
@@ -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. 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 | | 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` | | `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 | | `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 | | `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` | | `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 | | `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` | | `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` | | `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 | | `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 *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 ### 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 #### 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. 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 | | 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` | | `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. | | `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 #### Example
@@ -128,7 +128,7 @@ Start by specifying that you will be using an external configuration file:
config-file: './.github/dependency-review-config.yml' config-file: './.github/dependency-review-config.yml'
``` ```
And then create the file in the path you just specified: And then create the file in the path you just specified:
```yaml ```yaml
fail-on-severity: 'critical' fail-on-severity: 'critical'
+6 -4
View File
@@ -5,13 +5,13 @@ import * as Utils from '../src/utils'
// GitHub Action inputs come in the form of environment variables // GitHub Action inputs come in the form of environment variables
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY) // 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 process.env[`INPUT_${input.toUpperCase()}`] = value
} }
// We want a clean ENV before each test. We use `delete` // We want a clean ENV before each test. We use `delete`
// since we want `undefined` values and not empty strings. // since we want `undefined` values and not empty strings.
function clearInputs() { function clearInputs(): void {
const allowedOptions = [ const allowedOptions = [
'FAIL-ON-SEVERITY', 'FAIL-ON-SEVERITY',
'FAIL-ON-SCOPES', 'FAIL-ON-SCOPES',
@@ -22,9 +22,11 @@ function clearInputs() {
'VULNERABILITY-CHECK', 'VULNERABILITY-CHECK',
'CONFIG-FILE', 'CONFIG-FILE',
'BASE-REF', 'BASE-REF',
'HEAD-REF' 'HEAD-REF',
'COMMENT-SUMMARY-IN-PR'
] ]
// eslint-disable-next-line github/array-foreach
allowedOptions.forEach(option => { allowedOptions.forEach(option => {
delete process.env[`INPUT_${option.toUpperCase()}`] delete process.env[`INPUT_${option.toUpperCase()}`]
}) })
@@ -237,7 +239,7 @@ test('it supports comma-separated lists', async () => {
'config-file', 'config-file',
'./__tests__/fixtures/inline-license-config-sample.yml' './__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']) expect(config.allow_licenses).toEqual(['MIT', 'GPL-2.0-only'])
}) })
+5 -5
View File
@@ -1,12 +1,12 @@
import {expect, test} from '@jest/globals' import {expect, test} from '@jest/globals'
import {Change, Changes} from '../src/schemas' import {Change} from '../src/schemas'
import { import {
filterChangesBySeverity, filterChangesBySeverity,
filterChangesByScopes, filterChangesByScopes,
filterAllowedAdvisories filterAllowedAdvisories
} from '../src/filter' } from '../src/filter'
let npmChange: Change = { const npmChange: Change = {
manifest: 'package.json', manifest: 'package.json',
change_type: 'added', change_type: 'added',
ecosystem: 'npm', ecosystem: 'npm',
@@ -26,7 +26,7 @@ let npmChange: Change = {
] ]
} }
let rubyChange: Change = { const rubyChange: Change = {
change_type: 'added', change_type: 'added',
manifest: 'Gemfile.lock', manifest: 'Gemfile.lock',
ecosystem: 'rubygems', ecosystem: 'rubygems',
@@ -52,7 +52,7 @@ let rubyChange: Change = {
] ]
} }
let noVulnNpmChange: Change = { const noVulnNpmChange: Change = {
manifest: 'package.json', manifest: 'package.json',
change_type: 'added', change_type: 'added',
ecosystem: 'npm', ecosystem: 'npm',
@@ -92,7 +92,7 @@ test('it properly filters changes by scope', async () => {
test('it properly handles undefined advisory IDs', async () => { test('it properly handles undefined advisory IDs', async () => {
const changes = [npmChange, rubyChange, noVulnNpmChange] const changes = [npmChange, rubyChange, noVulnNpmChange]
let result = filterAllowedAdvisories(undefined, changes) const result = filterAllowedAdvisories(undefined, changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange]) expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
}) })
+36
View File
@@ -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/[email protected]',
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}
+5 -2
View File
@@ -3,7 +3,7 @@ import {Change, Changes} from '../src/schemas'
let getInvalidLicenseChanges: Function let getInvalidLicenseChanges: Function
let npmChange: Change = { const npmChange: Change = {
manifest: 'package.json', manifest: 'package.json',
change_type: 'added', change_type: 'added',
ecosystem: 'npm', ecosystem: 'npm',
@@ -23,7 +23,7 @@ let npmChange: Change = {
] ]
} }
let rubyChange: Change = { const rubyChange: Change = {
change_type: 'added', change_type: 'added',
manifest: 'Gemfile.lock', manifest: 'Gemfile.lock',
ecosystem: 'rubygems', ecosystem: 'rubygems',
@@ -63,6 +63,7 @@ const mockOctokit = {
jest.mock('octokit', () => { jest.mock('octokit', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
Octokit: class { Octokit: class {
constructor() { constructor() {
return mockOctokit return mockOctokit
@@ -78,6 +79,7 @@ beforeEach(async () => {
// true for BSD, false for all others // true for BSD, false for all others
return jest.fn((license: string, _: string): boolean => license === 'BSD') return jest.fn((license: string, _: string): boolean => license === 'BSD')
}) })
// eslint-disable-next-line @typescript-eslint/no-require-imports
;({getInvalidLicenseChanges} = require('../src/licenses')) ;({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') throw new Error('Some Error')
}) })
}) })
// eslint-disable-next-line @typescript-eslint/no-require-imports
;({getInvalidLicenseChanges} = require('../src/licenses')) ;({getInvalidLicenseChanges} = require('../src/licenses'))
const changes: Changes = [npmChange, rubyChange] const changes: Changes = [npmChange, rubyChange]
const invalidLicenses = await getInvalidLicenseChanges(changes, { const invalidLicenses = await getInvalidLicenseChanges(changes, {
+305
View File
@@ -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')
})
+3
View File
@@ -41,6 +41,9 @@ inputs:
vulnerability-check: vulnerability-check:
description: A boolean to determine if vulnerability checks should be performed description: A boolean to determine if vulnerability checks should be performed
required: false 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: runs:
using: 'node16' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'
Generated Vendored
+516 -99
View File
@@ -1,6 +1,142 @@
require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({ /******/ 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: /***/ 4966:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
@@ -123,18 +259,6 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getInvalidLicenseChanges = void 0; exports.getInvalidLicenseChanges = void 0;
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424)); const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
const utils_1 = __nccwpck_require__(918); 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) { function getInvalidLicenseChanges(changes, licenses) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const { allow, deny } = licenses; const { allow, deny } = licenses;
@@ -322,6 +446,7 @@ const licenses_1 = __nccwpck_require__(3247);
const summary = __importStar(__nccwpck_require__(8608)); const summary = __importStar(__nccwpck_require__(8608));
const git_refs_1 = __nccwpck_require__(1086); const git_refs_1 = __nccwpck_require__(1086);
const utils_1 = __nccwpck_require__(918); const utils_1 = __nccwpck_require__(918);
const comment_pr_1 = __nccwpck_require__(5842);
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
@@ -333,20 +458,24 @@ function run() {
baseRef: refs.base, baseRef: refs.base,
headRef: refs.head headRef: refs.head
}); });
if (!changes) {
core.info('No Dependency Changes found. Skipping Dependency Review.');
return;
}
const minSeverity = config.fail_on_severity; const minSeverity = config.fail_on_severity;
const scopedChanges = (0, filter_1.filterChangesByScopes)(config.fail_on_scopes, changes); const scopedChanges = (0, filter_1.filterChangesByScopes)(config.fail_on_scopes, changes);
const filteredChanges = (0, filter_1.filterAllowedAdvisories)(config.allow_ghsas, scopedChanges); 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 !== undefined &&
change.vulnerabilities.length > 0); change.vulnerabilities.length > 0);
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, { const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
allow: config.allow_licenses, allow: config.allow_licenses,
deny: config.deny_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) { if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity); summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
printVulnerabilitiesBlock(addedChanges, minSeverity); printVulnerabilitiesBlock(vulnerableChanges, minSeverity);
} }
if (config.license_check) { if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config); summary.addLicensesToSummary(invalidLicenseChanges, config);
@@ -354,6 +483,9 @@ function run() {
} }
summary.addScannedDependencies(changes); summary.addScannedDependencies(changes);
printScannedDependencies(changes); printScannedDependencies(changes);
if (config.comment_summary_in_pr) {
yield (0, comment_pr_1.commentPr)(core.summary);
}
} }
catch (error) { catch (error) {
if (error instanceof request_error_1.RequestError && error.status === 404) { if (error instanceof request_error_1.RequestError && error.status === 404) {
@@ -539,7 +671,8 @@ exports.ConfigurationOptionsSchema = z
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
base_ref: 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) => { .superRefine((config, context) => {
if (config.allow_licenses && config.deny_licenses) { 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; exports.addScannedDependencies = exports.addLicensesToSummary = exports.addChangeVulnerabilitiesToSummary = exports.addSummaryToSummary = void 0;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const utils_1 = __nccwpck_require__(918); const utils_1 = __nccwpck_require__(918);
function addSummaryToSummary(addedPackages, invalidLicenseChanges) { const icons = {
core.summary check: '✅',
.addHeading('Dependency Review') cross: '❌',
.addRaw('We found:') warning: '⚠️'
.addList([ };
...(addedPackages function addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config) {
? [`${addedPackages.length} vulnerable package(s)`] core.summary.addHeading('Dependency Review', 1);
: []), if (vulnerableChanges.length === 0 &&
...(invalidLicenseChanges countLicenseIssues(invalidLicenseChanges) === 0) {
? [ if (!config.license_check) {
`${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`, core.summary.addRaw(`${icons.check} No vulnerabilities found.`);
`${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`, }
`${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.` 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.`);
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.');
return; 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 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_package = '';
let previous_version = ''; let previous_version = '';
for (const vuln of change.vulnerabilities) { for (const vuln of change.vulnerabilities) {
@@ -653,7 +804,7 @@ function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
previous_version = change.version; 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: 'Name', header: true },
{ data: 'Version', header: true }, { data: 'Version', header: true },
@@ -663,46 +814,58 @@ function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
...rows ...rows
]); ]);
} }
if (severity !== 'low') {
core.summary.addQuote(`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`);
}
} }
exports.addChangeVulnerabilitiesToSummary = addChangeVulnerabilitiesToSummary; exports.addChangeVulnerabilitiesToSummary = addChangeVulnerabilitiesToSummary;
function addLicensesToSummary(invalidLicenseChanges, config) { 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) { if (config.allow_licenses && config.allow_licenses.length > 0) {
core.summary.addQuote(`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`); core.summary.addQuote(`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`);
} }
if (config.deny_licenses && config.deny_licenses.length > 0) { if (config.deny_licenses && config.deny_licenses.length > 0) {
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`); 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(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`); 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; exports.addLicensesToSummary = addLicensesToSummary;
function printLicenseViolation(heading, changes) { const licenseIssueTypes = [
core.summary.addHeading(heading, 5).addSeparator(); 'forbidden',
if (changes.length > 0) { 'unresolved',
const rows = []; 'unlicensed'
const manifests = (0, utils_1.getManifestsSet)(changes); ];
for (const manifest of manifests) { const issueTypeNames = {
core.summary.addHeading(`<em>${manifest}</em>`, 4); forbidden: 'Incompatible License',
for (const change of changes.filter(pkg => pkg.manifest === manifest)) { unresolved: 'Invalid SPDX License',
rows.push([ unlicensed: 'Unknown License'
(0, utils_1.renderUrl)(change.source_repository_url, change.name), };
change.version, function printLicenseViolations(changes) {
formatLicense(change.license) 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 { for (const [manifest, rows] of Object.entries(rowsGroupedByManifest)) {
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`); core.summary.addHeading(`<em>${manifest}</em>`, 4);
core.summary.addTable([
['Package', 'Version', 'License', 'Issue Type'],
...rows
]);
} }
} }
function formatLicense(license) { function formatLicense(license) {
@@ -714,9 +877,7 @@ function formatLicense(license) {
function addScannedDependencies(changes) { function addScannedDependencies(changes) {
const dependencies = (0, utils_1.groupDependenciesByManifest)(changes); const dependencies = (0, utils_1.groupDependenciesByManifest)(changes);
const manifests = dependencies.keys(); const manifests = dependencies.keys();
const summary = core.summary const summary = core.summary.addHeading('Scanned Manifest Files', 2);
.addHeading('Scanned Dependencies')
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5);
for (const manifest of manifests) { for (const manifest of manifests) {
const deps = dependencies.get(manifest); const deps = dependencies.get(manifest);
if (deps) { if (deps) {
@@ -726,6 +887,15 @@ function addScannedDependencies(changes) {
} }
} }
exports.addScannedDependencies = addScannedDependencies; 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 })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.z = void 0; exports.z = void 0;
const mod = __importStar(__nccwpck_require__(9906)); const z = __importStar(__nccwpck_require__(9906));
exports.z = mod; exports.z = z;
__exportStar(__nccwpck_require__(9906), exports); __exportStar(__nccwpck_require__(9906), exports);
exports["default"] = mod; exports["default"] = z;
/***/ }), /***/ }),
@@ -41285,7 +41455,7 @@ const errorMap = (issue, _ctx) => {
? `exactly equal to ` ? `exactly equal to `
: issue.inclusive : issue.inclusive
? `greater than or equal to ` ? `greater than or equal to `
: `greater than `}${new Date(issue.minimum)}`; : `greater than `}${new Date(Number(issue.minimum))}`;
else else
message = "Invalid input"; message = "Invalid input";
break; break;
@@ -41300,12 +41470,18 @@ const errorMap = (issue, _ctx) => {
: issue.inclusive : issue.inclusive
? `less than or equal to` ? `less than or equal to`
: `less than`} ${issue.maximum}`; : `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") else if (issue.type === "date")
message = `Date must be ${issue.exact message = `Date must be ${issue.exact
? `exactly` ? `exactly`
: issue.inclusive : issue.inclusive
? `smaller than or equal to` ? `smaller than or equal to`
: `smaller than`} ${new Date(issue.maximum)}`; : `smaller than`} ${new Date(Number(issue.maximum))}`;
else else
message = "Invalid input"; message = "Invalid input";
break; 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 //old email regex
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i; // const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
// eslint-disable-next-line // 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,}))$/; 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,})+))$/;
// interface IsDateStringOptions extends StringDateOptions { // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
/** const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;
* Match any configuration 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})))$/;
// any?: boolean;
// }
// Adapted from https://stackoverflow.com/a/3143231 // Adapted from https://stackoverflow.com/a/3143231
const datetimeRegex = (args) => { const datetimeRegex = (args) => {
if (args.precision) { 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 { class ZodString extends ZodType {
constructor() { constructor() {
super(...arguments); super(...arguments);
@@ -41699,6 +41882,14 @@ class ZodString extends ZodType {
...this._def, ...this._def,
checks: [...this._def.checks, { kind: "trim" }], 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) { _parse(input) {
if (this._def.coerce) { if (this._def.coerce) {
@@ -41786,6 +41977,17 @@ class ZodString extends ZodType {
status.dirty(); 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") { else if (check.kind === "uuid") {
if (!uuidRegex.test(input.data)) { if (!uuidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx); ctx = this._getOrReturnCtx(input, ctx);
@@ -41849,6 +42051,12 @@ class ZodString extends ZodType {
else if (check.kind === "trim") { else if (check.kind === "trim") {
input.data = input.data.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") { else if (check.kind === "startsWith") {
if (!input.data.startsWith(check.value)) { if (!input.data.startsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx); ctx = this._getOrReturnCtx(input, ctx);
@@ -41883,6 +42091,17 @@ class ZodString extends ZodType {
status.dirty(); 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 { else {
util_1.util.assertNever(check); util_1.util.assertNever(check);
} }
@@ -41901,6 +42120,9 @@ class ZodString extends ZodType {
url(message) { url(message) {
return this._addCheck({ kind: "url", ...errorUtil_1.errorUtil.errToObj(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) { uuid(message) {
return this._addCheck({ kind: "uuid", ...errorUtil_1.errorUtil.errToObj(message) }); return this._addCheck({ kind: "uuid", ...errorUtil_1.errorUtil.errToObj(message) });
} }
@@ -41910,6 +42132,9 @@ class ZodString extends ZodType {
cuid2(message) { cuid2(message) {
return this._addCheck({ kind: "cuid2", ...errorUtil_1.errorUtil.errToObj(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) { datetime(options) {
var _a; var _a;
if (typeof options === "string") { if (typeof options === "string") {
@@ -41978,6 +42203,9 @@ class ZodString extends ZodType {
get isURL() { get isURL() {
return !!this._def.checks.find((ch) => ch.kind === "url"); return !!this._def.checks.find((ch) => ch.kind === "url");
} }
get isEmoji() {
return !!this._def.checks.find((ch) => ch.kind === "emoji");
}
get isUUID() { get isUUID() {
return !!this._def.checks.find((ch) => ch.kind === "uuid"); return !!this._def.checks.find((ch) => ch.kind === "uuid");
} }
@@ -41987,6 +42215,9 @@ class ZodString extends ZodType {
get isCUID2() { get isCUID2() {
return !!this._def.checks.find((ch) => ch.kind === "cuid2"); return !!this._def.checks.find((ch) => ch.kind === "cuid2");
} }
get isIP() {
return !!this._def.checks.find((ch) => ch.kind === "ip");
}
get minLength() { get minLength() {
let min = null; let min = null;
for (const ch of this._def.checks) { for (const ch of this._def.checks) {
@@ -42207,6 +42438,19 @@ class ZodNumber extends ZodType {
message: errorUtil_1.errorUtil.toString(message), 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() { get minValue() {
let min = null; let min = null;
for (const ch of this._def.checks) { for (const ch of this._def.checks) {
@@ -42261,6 +42505,11 @@ ZodNumber.create = (params) => {
}); });
}; };
class ZodBigInt extends ZodType { class ZodBigInt extends ZodType {
constructor() {
super(...arguments);
this.min = this.gte;
this.max = this.lte;
}
_parse(input) { _parse(input) {
if (this._def.coerce) { if (this._def.coerce) {
input.data = BigInt(input.data); input.data = BigInt(input.data);
@@ -42275,13 +42524,155 @@ class ZodBigInt extends ZodType {
}); });
return parseUtil_1.INVALID; 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; exports.ZodBigInt = ZodBigInt;
ZodBigInt.create = (params) => { ZodBigInt.create = (params) => {
var _a; var _a;
return new ZodBigInt({ return new ZodBigInt({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodBigInt, typeName: ZodFirstPartyTypeKind.ZodBigInt,
coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false, coerce: (_a = params === null || params === void 0 ? void 0 : params.coerce) !== null && _a !== void 0 ? _a : false,
...processCreateParams(params), ...processCreateParams(params),
@@ -43023,6 +43414,9 @@ class ZodObject extends ZodType {
shape: () => shape, shape: () => shape,
}); });
} }
/**
* @deprecated
*/
deepPartial() { deepPartial() {
return deepPartialify(this); return deepPartialify(this);
} }
@@ -44194,29 +44588,45 @@ ZodDefault.create = (type, params) => {
class ZodCatch extends ZodType { class ZodCatch extends ZodType {
_parse(input) { _parse(input) {
const { ctx } = this._processInputParams(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({ const result = this._def.innerType._parse({
data: ctx.data, data: newCtx.data,
path: ctx.path, path: newCtx.path,
parent: { parent: {
...ctx, ...newCtx,
common: {
...ctx.common,
issues: [], // don't collect issues from inner type
},
}, },
}); });
if ((0, parseUtil_1.isAsync)(result)) { if ((0, parseUtil_1.isAsync)(result)) {
return result.then((result) => { return result.then((result) => {
return { return {
status: "valid", 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 { else {
return { return {
status: "valid", 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; exports.ZodPipeline = ZodPipeline;
const custom = (check, params = {}, fatal) => { const custom = (check, params = {},
/* @deprecated */
fatal) => {
if (check) if (check)
return ZodAny.create().superRefine((data, ctx) => { return ZodAny.create().superRefine((data, ctx) => {
var _a, _b;
if (!check(data)) { if (!check(data)) {
const p = typeof params === "function" ? params(data) : params; 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; const p2 = typeof p === "string" ? { message: p } : p;
ctx.addIssue({ code: "custom", ...p2, fatal }); ctx.addIssue({ code: "custom", ...p2, fatal: _fatal });
} }
}); });
return ZodAny.create(); return ZodAny.create();
@@ -44392,7 +44806,7 @@ const instanceOfType = (
// const instanceOfType = <T extends new (...args: any[]) => any>( // const instanceOfType = <T extends new (...args: any[]) => any>(
cls, params = { cls, params = {
message: `Input not instance of ${cls.name}`, 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; exports["instanceof"] = instanceOfType;
const stringType = ZodString.create; const stringType = ZodString.create;
exports.string = stringType; exports.string = stringType;
@@ -44555,6 +44969,7 @@ function readInlineConfig() {
const vulnerability_check = getOptionalBoolean('vulnerability-check'); const vulnerability_check = getOptionalBoolean('vulnerability-check');
const base_ref = getOptionalInput('base-ref'); const base_ref = getOptionalInput('base-ref');
const head_ref = getOptionalInput('head-ref'); const head_ref = getOptionalInput('head-ref');
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr');
validateLicenses('allow-licenses', allow_licenses); validateLicenses('allow-licenses', allow_licenses);
validateLicenses('deny-licenses', deny_licenses); validateLicenses('deny-licenses', deny_licenses);
const keys = { const keys = {
@@ -44566,7 +44981,8 @@ function readInlineConfig() {
license_check, license_check,
vulnerability_check, vulnerability_check,
base_ref, base_ref,
head_ref head_ref,
comment_summary_in_pr
}; };
return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined)); return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined));
} }
@@ -44831,7 +45247,8 @@ exports.ConfigurationOptionsSchema = z
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
base_ref: 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) => { .superRefine((config, context) => {
if (config.allow_licenses && config.deny_licenses) { if (config.allow_licenses && config.deny_licenses) {
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+1052 -284
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -5,7 +5,7 @@
"description": "A GitHub Action for Dependency Review", "description": "A GitHub Action for Dependency Review",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc -p tsconfig.build.json",
"format": "prettier --write '**/*.ts'", "format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'", "format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
@@ -30,31 +30,31 @@
"@octokit/plugin-retry": "^4.1.1", "@octokit/plugin-retry": "^4.1.1",
"@octokit/request-error": "^2.1.0", "@octokit/request-error": "^2.1.0",
"ansi-styles": "^6.2.1", "ansi-styles": "^6.2.1",
"got": "^12.5.3", "got": "^12.6.0",
"nodemon": "^2.0.20", "nodemon": "^2.0.21",
"octokit": "^2.0.14", "octokit": "^2.0.14",
"spdx-expression-parse": "^3.0.1", "spdx-expression-parse": "^3.0.1",
"spdx-satisfies": "^5.0.1", "spdx-satisfies": "^5.0.1",
"yaml": "^2.2.1", "yaml": "^2.2.1",
"zod": "^3.20.6" "zod": "^3.21.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/node": "^16.18.12", "@types/node": "^16.18.14",
"@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.0", "@typescript-eslint/parser": "^5.48.0",
"@types/spdx-expression-parse": "^3.0.2", "@types/spdx-expression-parse": "^3.0.2",
"@types/spdx-satisfies": "^0.1.0", "@types/spdx-satisfies": "^0.1.0",
"@typescript-eslint/eslint-plugin": "^5.51.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.51.0", "@typescript-eslint/parser": "^5.53.0",
"@vercel/ncc": "^0.36.1", "@vercel/ncc": "^0.36.1",
"esbuild-register": "^3.4.2", "esbuild-register": "^3.4.2",
"eslint": "^8.34.0", "eslint": "^8.35.0",
"eslint-plugin-github": "^4.6.0", "eslint-plugin-github": "^4.6.1",
"eslint-plugin-jest": "^27.2.1", "eslint-plugin-jest": "^27.2.1",
"jest": "^27.5.1", "jest": "^27.5.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"nodemon": "^2.0.20", "nodemon": "^2.0.21",
"prettier": "2.8.4", "prettier": "2.8.4",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"typescript": "^4.9.5" "typescript": "^4.9.5"
+114
View File
@@ -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()
+84
View File
@@ -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
View File
@@ -34,6 +34,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
const vulnerability_check = getOptionalBoolean('vulnerability-check') const vulnerability_check = getOptionalBoolean('vulnerability-check')
const base_ref = getOptionalInput('base-ref') const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref') const head_ref = getOptionalInput('head-ref')
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
validateLicenses('allow-licenses', allow_licenses) validateLicenses('allow-licenses', allow_licenses)
validateLicenses('deny-licenses', deny_licenses) validateLicenses('deny-licenses', deny_licenses)
@@ -47,7 +48,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
license_check, license_check,
vulnerability_check, vulnerability_check,
base_ref, base_ref,
head_ref head_ref,
comment_summary_in_pr
} }
return Object.fromEntries( return Object.fromEntries(
+7 -2
View File
@@ -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. * @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 * @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( export async function getInvalidLicenseChanges(
changes: Change[], changes: Change[],
licenses: { licenses: {
allow?: string[] allow?: string[]
deny?: string[] deny?: string[]
} }
): Promise<Record<string, Changes>> { ): Promise<InvalidLicenseChanges> {
const {allow, deny} = licenses const {allow, deny} = licenses
const groupedChanges = await groupChanges(changes) const groupedChanges = await groupChanges(changes)
const licensedChanges: Changes = groupedChanges.licensed const licensedChanges: Changes = groupedChanges.licensed
const invalidLicenseChanges: Record<string, Changes> = { const invalidLicenseChanges: InvalidLicenseChanges = {
unlicensed: groupedChanges.unlicensed, unlicensed: groupedChanges.unlicensed,
unresolved: [], unresolved: [],
forbidden: [] forbidden: []
+15 -5
View File
@@ -15,6 +15,7 @@ import * as summary from './summary'
import {getRefs} from './git-refs' import {getRefs} from './git-refs'
import {groupDependenciesByManifest} from './utils' import {groupDependenciesByManifest} from './utils'
import {commentPr} from './comment-pr'
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
@@ -28,6 +29,11 @@ async function run(): Promise<void> {
headRef: refs.head headRef: refs.head
}) })
if (!changes) {
core.info('No Dependency Changes found. Skipping Dependency Review.')
return
}
const minSeverity = config.fail_on_severity const minSeverity = config.fail_on_severity
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes) const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
const filteredChanges = filterAllowedAdvisories( const filteredChanges = filterAllowedAdvisories(
@@ -35,7 +41,7 @@ async function run(): Promise<void> {
scopedChanges scopedChanges
) )
const addedChanges = filterChangesBySeverity( const vulnerableChanges = filterChangesBySeverity(
minSeverity, minSeverity,
filteredChanges filteredChanges
).filter( ).filter(
@@ -54,13 +60,14 @@ async function run(): Promise<void> {
) )
summary.addSummaryToSummary( summary.addSummaryToSummary(
config.vulnerability_check ? addedChanges : null, vulnerableChanges,
config.license_check ? invalidLicenseChanges : null invalidLicenseChanges,
config
) )
if (config.vulnerability_check) { if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity) summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
printVulnerabilitiesBlock(addedChanges, minSeverity) printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
} }
if (config.license_check) { if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config) summary.addLicensesToSummary(invalidLicenseChanges, config)
@@ -69,6 +76,9 @@ async function run(): Promise<void> {
summary.addScannedDependencies(changes) summary.addScannedDependencies(changes)
printScannedDependencies(changes) printScannedDependencies(changes)
if (config.comment_summary_in_pr) {
await commentPr(core.summary)
}
} catch (error) { } catch (error) {
if (error instanceof RequestError && error.status === 404) { if (error instanceof RequestError && error.status === 404) {
core.setFailed( core.setFailed(
+2 -1
View File
@@ -45,7 +45,8 @@ export const ConfigurationOptionsSchema = z
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
base_ref: 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) => { .superRefine((config, context) => {
if (config.allow_licenses && config.deny_licenses) { if (config.allow_licenses && config.deny_licenses) {
+128 -71
View File
@@ -1,50 +1,82 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {ConfigurationOptions, Changes} from './schemas' import {ConfigurationOptions, Changes} from './schemas'
import {SummaryTableRow} from '@actions/core/lib/summary' import {SummaryTableRow} from '@actions/core/lib/summary'
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils' import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
export function addSummaryToSummary( const icons = {
addedPackages: Changes | null, check: '✅',
invalidLicenseChanges: Record<string, Changes> | null cross: '❌',
): void { warning: '⚠️'
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.`
]
: [])
])
} }
export function addChangeVulnerabilitiesToSummary( export function addSummaryToSummary(
addedPackages: Changes, vulnerableChanges: Changes,
severity: string invalidLicenseChanges: InvalidLicenseChanges,
config: ConfigurationOptions
): void { ): 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 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 manifest of manifests) {
for (const change of addedPackages.filter( for (const change of vulnerableChanges.filter(
pkg => pkg.manifest === manifest pkg => pkg.manifest === manifest
)) { )) {
let previous_package = '' let previous_package = ''
@@ -72,7 +104,7 @@ export function addChangeVulnerabilitiesToSummary(
previous_version = change.version 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: 'Name', header: true},
{data: 'Version', header: true}, {data: 'Version', header: true},
@@ -82,13 +114,24 @@ export function addChangeVulnerabilitiesToSummary(
...rows ...rows
]) ])
} }
if (severity !== 'low') {
core.summary.addQuote(
`Only included vulnerabilities with severity <strong>${severity}</strong> or higher.`
)
}
} }
export function addLicensesToSummary( export function addLicensesToSummary(
invalidLicenseChanges: Record<string, Changes>, invalidLicenseChanges: InvalidLicenseChanges,
config: ConfigurationOptions config: ConfigurationOptions
): void { ): 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) { if (config.allow_licenses && config.allow_licenses.length > 0) {
core.summary.addQuote( 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( core.debug(
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses` `found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
) )
@@ -113,39 +151,43 @@ export function addLicensesToSummary(
core.debug( core.debug(
`${invalidLicenseChanges.unresolved.length} licenses could not be validated` `${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 licenseIssueTypes: InvalidLicenseChangeTypes[] = [
const rows: SummaryTableRow[] = [] 'forbidden',
const manifests = getManifestsSet(changes) 'unresolved',
'unlicensed'
]
for (const manifest of manifests) { const issueTypeNames: Record<InvalidLicenseChangeTypes, string> = {
core.summary.addHeading(`<em>${manifest}</em>`, 4) forbidden: 'Incompatible License',
unresolved: 'Invalid SPDX License',
unlicensed: 'Unknown License'
}
for (const change of changes.filter(pkg => pkg.manifest === manifest)) { function printLicenseViolations(changes: InvalidLicenseChanges): void {
rows.push([ const rowsGroupedByManifest: Record<string, SummaryTableRow[]> = {}
renderUrl(change.source_repository_url, change.name),
change.version, for (const issueType of licenseIssueTypes) {
formatLicense(change.license) for (const change of changes[issueType]) {
]) if (!rowsGroupedByManifest[change.manifest]) {
rowsGroupedByManifest[change.manifest] = []
} }
rowsGroupedByManifest[change.manifest].push([
core.summary.addTable([['Package', 'Version', 'License'], ...rows]) 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 dependencies = groupDependenciesByManifest(changes)
const manifests = dependencies.keys() const manifests = dependencies.keys()
const summary = core.summary const summary = core.summary.addHeading('Scanned Manifest Files', 2)
.addHeading('Scanned Dependencies')
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5)
for (const manifest of manifests) { for (const manifest of manifests) {
const deps = dependencies.get(manifest) 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
}
+8
View File
@@ -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
View File
@@ -3,10 +3,9 @@
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, "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'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"outDir": "./lib" /* Redirect output structure to the directory. */, "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. */, "strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, "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'. */ "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"]
} }