Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4604a3cd9 | |||
| 56339e523c | |||
| 1688b745f3 | |||
| 31c9f175b9 | |||
| eacde7836e | |||
| 81510090e4 | |||
| b472ec914b | |||
| e0cedc52dc | |||
| e3fdf0f899 | |||
| 6fad417932 | |||
| e86e9692ad | |||
| 85c8e53ab7 | |||
| c6a7eb7252 | |||
| 595b5aeba7 | |||
| fc5fd661aa | |||
| d38d1a4f40 | |||
| 8d420b827c | |||
| bde01290d3 | |||
| ab524903e8 | |||
| ef00a0afbb | |||
| 74c8179d39 | |||
| bc41886e18 | |||
| 1c73553e36 | |||
| fac3d41a58 | |||
| d8073c4b76 | |||
| 77184c6339 | |||
| 5558c35bb3 | |||
| e85d57a50e | |||
| 3eb62794c5 | |||
| 7cf33ac2f2 | |||
| 493bee0560 | |||
| 659a1e1bd0 | |||
| 6e80be31cd | |||
| 3fb5c613f0 | |||
| 7d16ba5d7e | |||
| a92a9da9c8 | |||
| c1fa9df06b | |||
| 6e2bbef080 | |||
| 9ca24b6906 | |||
| 70e1d26338 | |||
| 89c7383074 | |||
| 40f2ab01b7 | |||
| 2bedf4a221 | |||
| 87052cdc7b | |||
| 47d790678f | |||
| 1e946feb37 | |||
| 8a1ad91c0a | |||
| 8296deda21 | |||
| 733ef0ab01 |
@@ -16,6 +16,9 @@ on:
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-dist:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -10,6 +10,9 @@ on:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript-typescript' ]
|
||||
language: [ 'javascript-typescript', 'actions', 'ruby' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
config: |
|
||||
paths-ignore:
|
||||
paths-ignore:
|
||||
- dist/index.js
|
||||
- dist/sourcemap-register.js
|
||||
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
# dependency-review-action
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Installation](#installation)
|
||||
- [Configuration](#configuration)
|
||||
- [Using dependency review action to block a pull request from being merged](#using-dependency-review-action-to-block-a-pull-request-from-being-merged)
|
||||
- [Outputs](#outputs)
|
||||
- [Getting help](#getting-help)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
- [dependency-review-action](#dependency-review-action)
|
||||
- [Overview](#overview)
|
||||
- [Viewing the results](#viewing-the-results)
|
||||
- [Installation](#installation)
|
||||
- [Installation (standard)](#installation-standard)
|
||||
- [Installation (GitHub Enterprise Server)](#installation-github-enterprise-server)
|
||||
- [Configuration](#configuration)
|
||||
- [Configuration options](#configuration-options)
|
||||
- [Configuration methods](#configuration-methods)
|
||||
- [Option 1: Using inline configuration](#option-1-using-inline-configuration)
|
||||
- [Option 2: Using an external configuration file](#option-2-using-an-external-configuration-file)
|
||||
- [`OTHER` in license strings](#other-in-license-strings)
|
||||
- [Further information](#further-information)
|
||||
- [Using dependency review action to block a pull request from being merged](#using-dependency-review-action-to-block-a-pull-request-from-being-merged)
|
||||
- [Outputs](#outputs)
|
||||
- [Getting help](#getting-help)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -24,7 +34,6 @@ The action is available for:
|
||||
When the action runs, you can see the results on:
|
||||
|
||||
- The **job logs** page.
|
||||
|
||||
1. Go to the **Actions** tab for the repository and select the relevant workflow run.
|
||||
1. Then under "Jobs", click **dependency review**.
|
||||
|
||||
@@ -102,25 +111,25 @@ There are various configuration options you can use to specify settings for the
|
||||
|
||||
All configuration options are optional.
|
||||
|
||||
| Option | Usage | Possible values | Default value |
|
||||
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`\* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes` | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. | `runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | 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 the `pull-requests: write` permission. With each execution, a new comment will overwrite the existing one. | `always`, `on-failure`, `never` | `never` |
|
||||
| `deny-packages` | Any number of packages to block in a PR. This option will match on the exact version provided. If no version is provided, the option will treat the specified package as a wildcard and deny all versions. | Package(s) in [purl](https://github.com/package-url/purl-spec) format | empty |
|
||||
| `deny-groups` | Any number of groups (namespaces) to block in a PR. | Namespace(s) in [purl](https://github.com/package-url/purl-spec) format (no package name, no version number) | empty |
|
||||
| `retry-on-snapshot-warnings`\* | Enable or disable retrying the action every 10 seconds while waiting for dependency submission actions to complete. | `true`, `false` | `false` |
|
||||
| `retry-on-snapshot-warnings-timeout`\* | Maximum amount of time (in seconds) to retry the action while waiting for dependency submission actions to complete. | Any positive integer | 120 |
|
||||
| `warn-only`+ | When set to `true`, the action will log all vulnerabilities as warnings regardless of the severity, and the action will complete with a `success` status. This overrides the `fail-on-severity` option. | `true`, `false` | `false` |
|
||||
| `show-openssf-scorecard` | When set to `true`, the action will output information about all the known OpenSSF Scorecard scores for the dependencies changed in this pull request. | `true`, `false` | `true` |
|
||||
| `warn-on-openssf-scorecard-level` | When `show-openssf-scorecard-levels` is set to `true`, this option lets you configure the threshold for when a score is considered too low and gets a :warning: warning in the CI. | Any positive integer | 3 |
|
||||
| Option | Usage | Possible values | Default value |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`\* | ⚠️ This option is deprecated for possible removal in the next major release. See [Deprecate the deny-licenses option #938](https://github.com/actions/dependency-review-action/issues/938) for more information. <br> Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes` | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. | `runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | 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 the `pull-requests: write` permission. With each execution, a new comment will overwrite the existing one. | `always`, `on-failure`, `never` | `never` |
|
||||
| `deny-packages` | Any number of packages to block in a PR. This option will match on the exact version provided. If no version is provided, the option will treat the specified package as a wildcard and deny all versions. | Package(s) in [purl](https://github.com/package-url/purl-spec) format | empty |
|
||||
| `deny-groups` | Any number of groups (namespaces) to block in a PR. | Namespace(s) in [purl](https://github.com/package-url/purl-spec) format (no package name, no version number) | empty |
|
||||
| `retry-on-snapshot-warnings`\* | Enable or disable retrying the action every 10 seconds while waiting for dependency submission actions to complete. | `true`, `false` | `false` |
|
||||
| `retry-on-snapshot-warnings-timeout`\* | Maximum amount of time (in seconds) to retry the action while waiting for dependency submission actions to complete. | Any positive integer | 120 |
|
||||
| `warn-only`+ | When set to `true`, the action will log all vulnerabilities as warnings regardless of the severity, and the action will complete with a `success` status. This overrides the `fail-on-severity` option. | `true`, `false` | `false` |
|
||||
| `show-openssf-scorecard` | When set to `true`, the action will output information about all the known OpenSSF Scorecard scores for the dependencies changed in this pull request. | `true`, `false` | `true` |
|
||||
| `warn-on-openssf-scorecard-level` | When `show-openssf-scorecard-levels` is set to `true`, this option lets you configure the threshold for when a score is considered too low and gets a :warning: warning in the CI. | Any positive integer | 3 |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@@ -206,15 +215,12 @@ You can use an external configuration file to specify settings for this action.
|
||||
3. Create the configuration file in the path you specified for `config-file`.
|
||||
4. In the configuration file, specify your chosen settings.
|
||||
```yaml
|
||||
fail_on_severity: 'critical'
|
||||
allow_licenses:
|
||||
fail-on-severity: 'critical'
|
||||
allow-licenses:
|
||||
- 'GPL-3.0'
|
||||
- 'BSD-3-Clause'
|
||||
- 'MIT'
|
||||
```
|
||||
> [!NOTE]
|
||||
> For external configuration files, the option names use underscores instead of dashes.
|
||||
> Example: `fail_on_severity`
|
||||
|
||||
#### `OTHER` in license strings
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
jest,
|
||||
test
|
||||
} from '@jest/globals'
|
||||
import * as fs from 'fs'
|
||||
import * as core from '@actions/core'
|
||||
import {DefaultArtifactClient} from '@actions/artifact'
|
||||
import type {SpyInstance} from 'jest-mock'
|
||||
import {handleLargeSummary} from '../src/main'
|
||||
|
||||
jest.mock('ansi-styles', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
color: {
|
||||
red: {open: '', close: ''},
|
||||
yellow: {open: '', close: ''},
|
||||
grey: {open: '', close: ''},
|
||||
green: {open: '', close: ''}
|
||||
},
|
||||
bold: {open: '', close: ''}
|
||||
}
|
||||
}))
|
||||
jest.mock('../src/dependency-graph', () => ({}))
|
||||
jest.mock('@actions/core', () => {
|
||||
const summary = {
|
||||
addRaw: jest.fn().mockReturnThis(),
|
||||
addHeading: jest.fn().mockReturnThis(),
|
||||
addTable: jest.fn().mockReturnThis(),
|
||||
addSeparator: jest.fn().mockReturnThis(),
|
||||
addImage: jest.fn().mockReturnThis(),
|
||||
addList: jest.fn().mockReturnThis(),
|
||||
addBreak: jest.fn().mockReturnThis(),
|
||||
addLink: jest.fn().mockReturnThis(),
|
||||
addDetails: jest.fn().mockReturnThis(),
|
||||
addSection: jest.fn().mockReturnThis(),
|
||||
addCodeBlock: jest.fn().mockReturnThis(),
|
||||
addFields: jest.fn().mockReturnThis(),
|
||||
addEol: jest.fn().mockReturnThis(),
|
||||
write: jest.fn(async () => undefined),
|
||||
emptyBuffer: jest.fn(),
|
||||
stringify: jest.fn(() => '')
|
||||
}
|
||||
return {
|
||||
__esModule: true,
|
||||
getInput: jest.fn((name: string) =>
|
||||
name === 'repo-token' ? 'gh_test_token' : ''
|
||||
),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn(async (_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
summary
|
||||
}
|
||||
})
|
||||
jest.mock('@actions/artifact', () => ({
|
||||
DefaultArtifactClient: jest.fn()
|
||||
}))
|
||||
|
||||
const ORIGINAL_ENV = {...process.env}
|
||||
|
||||
type ArtifactClientInstance = {
|
||||
uploadArtifact: jest.Mock
|
||||
}
|
||||
|
||||
const DefaultArtifactClientMock = DefaultArtifactClient as unknown as jest.Mock
|
||||
|
||||
const createArtifactClient = (): ArtifactClientInstance => ({
|
||||
uploadArtifact: jest.fn(async () => undefined)
|
||||
})
|
||||
|
||||
describe('handleLargeSummary', () => {
|
||||
let writeFileSpy: SpyInstance<typeof fs.promises.writeFile>
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = {...ORIGINAL_ENV}
|
||||
writeFileSpy = jest
|
||||
.spyOn(fs.promises, 'writeFile')
|
||||
.mockImplementation(async () => undefined)
|
||||
DefaultArtifactClientMock.mockClear()
|
||||
DefaultArtifactClientMock.mockImplementation(() => createArtifactClient())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
writeFileSpy.mockRestore()
|
||||
jest.clearAllMocks()
|
||||
process.env = {...ORIGINAL_ENV}
|
||||
})
|
||||
|
||||
test('returns original summary when under size threshold', async () => {
|
||||
const summaryContent = 'short summary'
|
||||
|
||||
const result = await handleLargeSummary(summaryContent)
|
||||
|
||||
expect(result).toBe(summaryContent)
|
||||
expect(writeFileSpy).not.toHaveBeenCalled()
|
||||
expect(DefaultArtifactClientMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('uploads artifact and returns minimal summary when summary is too large', async () => {
|
||||
process.env.GITHUB_SERVER_URL = 'https://github.com'
|
||||
process.env.GITHUB_REPOSITORY = 'owner/repo'
|
||||
process.env.GITHUB_RUN_ID = '12345'
|
||||
|
||||
const largeSummary = 'a'.repeat(1024 * 1024 + 1)
|
||||
|
||||
const result = await handleLargeSummary(largeSummary)
|
||||
|
||||
expect(writeFileSpy).toHaveBeenCalledTimes(1)
|
||||
expect(writeFileSpy).toHaveBeenCalledWith('summary.md', largeSummary)
|
||||
expect(DefaultArtifactClientMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
const artifactInstance = DefaultArtifactClientMock.mock.results[0]
|
||||
?.value as ArtifactClientInstance
|
||||
|
||||
expect(artifactInstance.uploadArtifact).toHaveBeenCalledWith(
|
||||
'dependency-review-summary',
|
||||
['summary.md'],
|
||||
'.',
|
||||
{retentionDays: 1}
|
||||
)
|
||||
|
||||
expect(result).toContain('# Dependency Review Summary')
|
||||
expect(result).toContain('dependency-review-summary')
|
||||
expect(result).toContain('actions/runs/12345')
|
||||
})
|
||||
|
||||
test('returns original summary and logs a warning when artifact handling fails', async () => {
|
||||
const warningMock = core.warning as jest.Mock
|
||||
warningMock.mockClear()
|
||||
const largeSummary = 'b'.repeat(1024 * 1024 + 1)
|
||||
|
||||
DefaultArtifactClientMock.mockImplementation(() => ({
|
||||
uploadArtifact: jest.fn(async () => {
|
||||
throw new Error('upload failed')
|
||||
})
|
||||
}))
|
||||
|
||||
const result = await handleLargeSummary(largeSummary)
|
||||
|
||||
expect(result).toBe(largeSummary)
|
||||
expect(warningMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to handle large summary')
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {Change, Changes, ConfigurationOptions, Scorecard} from '../src/schemas'
|
||||
import {Changes, ConfigurationOptions, Scorecard} from '../src/schemas'
|
||||
import * as summary from '../src/summary'
|
||||
import * as core from '@actions/core'
|
||||
import {createTestChange} from './fixtures/create-test-change'
|
||||
@@ -109,10 +109,38 @@ test('prints headline as h1', () => {
|
||||
expect(text).toContain('<h1>Dependency Review</h1>')
|
||||
})
|
||||
|
||||
test('does not add deprecation warning for deny-licenses option if not set', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
scorecard,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).not.toContain('deny-licenses')
|
||||
})
|
||||
|
||||
test('adds deprecation warning for deny-licenses option if set', () => {
|
||||
const config = {...defaultConfig, deny_licenses: ['MIT']}
|
||||
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
scorecard,
|
||||
config
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('deny-licenses')
|
||||
})
|
||||
|
||||
test('returns minimal summary formatted for posting as a PR comment', () => {
|
||||
const OLD_ENV = process.env
|
||||
|
||||
let changes: Changes = [
|
||||
const changes: Changes = [
|
||||
createTestChange({name: 'lodash', version: '1.2.3'}),
|
||||
createTestChange({name: 'colors', version: '2.3.4'}),
|
||||
createTestChange({name: '@foo/bar', version: '*'})
|
||||
@@ -122,7 +150,7 @@ test('returns minimal summary formatted for posting as a PR comment', () => {
|
||||
process.env.GITHUB_REPOSITORY = 'owner/repo'
|
||||
process.env.GITHUB_RUN_ID = 'abc-123-xyz'
|
||||
|
||||
let minSummary: string = summary.addSummaryToSummary(
|
||||
const minSummary: string = summary.addSummaryToSummary(
|
||||
changes,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
@@ -436,7 +464,9 @@ test('addLicensesToSummary() - includes list of configured allowed licenses', ()
|
||||
summary.addLicensesToSummary(licenseIssues, config)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<strong>Allowed Licenses</strong>: MIT, Apache-2.0')
|
||||
expect(text).toContain(
|
||||
'<details><summary><strong>Allowed Licenses</strong>:</summary> MIT, Apache-2.0</details>'
|
||||
)
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - includes configured denied license', () => {
|
||||
@@ -448,11 +478,33 @@ test('addLicensesToSummary() - includes configured denied license', () => {
|
||||
|
||||
const config: ConfigurationOptions = {
|
||||
...defaultConfig,
|
||||
deny_licenses: ['MIT']
|
||||
deny_licenses: ['MIT', 'Apache-2.0']
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, config)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('<strong>Denied Licenses</strong>: MIT')
|
||||
expect(text).toContain(
|
||||
'<details><summary><strong>Denied Licenses</strong>:</summary> MIT, Apache-2.0</details>'
|
||||
)
|
||||
})
|
||||
|
||||
test('addLicensesToSummary() - includes allowed dependency licences', () => {
|
||||
const licenseIssues = {
|
||||
forbidden: [createTestChange()],
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
const config: ConfigurationOptions = {
|
||||
...defaultConfig,
|
||||
allow_dependencies_licenses: ['MIT', 'Apache-2.0']
|
||||
}
|
||||
|
||||
summary.addLicensesToSummary(licenseIssues, config)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain(
|
||||
'<details><summary><strong>Excluded from license check</strong>:</summary> MIT, Apache-2.0</details>'
|
||||
)
|
||||
})
|
||||
|
||||
+102552
-2371
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+2927
-5
File diff suppressed because it is too large
Load Diff
Generated
+253
-649
File diff suppressed because it is too large
Load Diff
+12
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dependency-review-action",
|
||||
"version": "4.7.1",
|
||||
"version": "4.8.0",
|
||||
"private": true,
|
||||
"description": "A GitHub Action for Dependency Review",
|
||||
"main": "lib/main.js",
|
||||
@@ -25,20 +25,21 @@
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/artifact": "^1.1.2",
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@octokit/plugin-retry": "^6.1.0",
|
||||
"@octokit/request-error": "^5.1.1",
|
||||
"@octokit/types": "12.5.0",
|
||||
"@onebeyond/spdx-license-satisfies": "^1.0.1",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"got": "^14.4.5",
|
||||
"got": "^14.4.7",
|
||||
"jest": "^29.7.0",
|
||||
"octokit": "^3.1.2",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^6.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"yaml": "^2.3.4",
|
||||
"ts-jest": "^29.4.1",
|
||||
"yaml": "^2.8.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,14 +53,14 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-jest": "^28.8.3",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"prettier": "3.2.5",
|
||||
"typescript": "^5.4.5"
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"overrides": {
|
||||
"cross-spawn": ">=7.0.5",
|
||||
"@octokit/request-error@5.0.1": "5.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-84
@@ -1,87 +1,8 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'json'
|
||||
require 'tempfile'
|
||||
require 'open3'
|
||||
require 'bundler/inline'
|
||||
require 'optparse'
|
||||
|
||||
gemfile do
|
||||
source 'https://rubygems.org'
|
||||
gem 'octokit'
|
||||
end
|
||||
# Load the scan_pr library
|
||||
require_relative 'scan_pr_lib'
|
||||
|
||||
config_file = nil
|
||||
github_token = ENV["GITHUB_TOKEN"]
|
||||
|
||||
if !github_token || github_token.empty?
|
||||
puts "Please set the GITHUB_TOKEN environment variable"
|
||||
exit -1
|
||||
end
|
||||
|
||||
op = OptionParser.new do |opts|
|
||||
usage = <<EOF
|
||||
Run Dependency Review on a repository.
|
||||
|
||||
\e[1mUsage:\e[22m
|
||||
scripts/scan_pr [options] <pr_url>
|
||||
|
||||
\e[1mExample:\e[22m
|
||||
scripts/scan_pr https://github.com/actions/dependency-review-action/pull/294
|
||||
|
||||
EOF
|
||||
|
||||
opts.banner = usage
|
||||
|
||||
opts.on('-c', '--config-file <FILE>', 'Use an external configuration file') do |cf|
|
||||
config_file = cf
|
||||
end
|
||||
|
||||
opts.on("-h", "--help", "Prints this help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
op.parse!
|
||||
|
||||
# make sure we have a NWO somewhere in the parameters
|
||||
arg = /(?<repo_nwo>[\w\-]+\/[\w\-]+)\/pull\/(?<pr_number>\d+)/.match(ARGV.join(" "))
|
||||
|
||||
if arg.nil?
|
||||
puts op
|
||||
exit -1
|
||||
end
|
||||
|
||||
repo_nwo = arg[:repo_nwo]
|
||||
pr_number = arg[:pr_number]
|
||||
|
||||
octo = Octokit::Client.new(access_token: github_token)
|
||||
pr = octo.pull_request(repo_nwo, pr_number)
|
||||
|
||||
event_file = Tempfile.new
|
||||
event_file.write("{ \"pull_request\": #{pr.to_h.to_json}}")
|
||||
event_file.close
|
||||
|
||||
action_inputs = {
|
||||
"repo-token": github_token,
|
||||
"config-file": config_file
|
||||
}
|
||||
|
||||
dev_cmd_env = {
|
||||
"GITHUB_REPOSITORY" => repo_nwo,
|
||||
"GITHUB_EVENT_NAME" => "pull_request",
|
||||
"GITHUB_EVENT_PATH" => event_file.path,
|
||||
"GITHUB_STEP_SUMMARY" => "/dev/null"
|
||||
}
|
||||
|
||||
# bash does not like variable names with dashes like the ones Actions
|
||||
# uses (e.g. INPUT_REPO-TOKEN). Passing them through `env` instead of
|
||||
# manually setting them does the job.
|
||||
action_inputs_env_str = action_inputs.map { |name, value| "\"INPUT_#{name.upcase}=#{value}\"" }.join(" ")
|
||||
dev_cmd = "./node_modules/.bin/nodemon --exec \"env #{action_inputs_env_str} node -r esbuild-register\" src/main.ts"
|
||||
|
||||
Open3.popen2e(dev_cmd_env, dev_cmd) do |stdin, out|
|
||||
while line = out.gets
|
||||
puts line.gsub(github_token, "<REDACTED>")
|
||||
end
|
||||
end
|
||||
# Create and run the scanner
|
||||
scanner = ScanPr.new
|
||||
scanner.run(ARGV)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
require 'json'
|
||||
require 'tempfile'
|
||||
require 'open3'
|
||||
require 'bundler/inline'
|
||||
require 'optparse'
|
||||
|
||||
gemfile do
|
||||
source 'https://rubygems.org'
|
||||
gem 'octokit'
|
||||
end
|
||||
|
||||
class ScanPr
|
||||
def initialize
|
||||
@config_file = nil
|
||||
@github_token = ENV["GITHUB_TOKEN"]
|
||||
|
||||
validate_token
|
||||
end
|
||||
|
||||
def run(args)
|
||||
parse_options(args)
|
||||
repo_nwo, pr_number = extract_repo_and_pr(args)
|
||||
|
||||
pr = fetch_pull_request(repo_nwo, pr_number)
|
||||
event_file = create_event_file(pr)
|
||||
|
||||
execute_dependency_review(repo_nwo, event_file)
|
||||
ensure
|
||||
event_file&.unlink
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_token
|
||||
if !@github_token || @github_token.empty?
|
||||
puts "Please set the GITHUB_TOKEN environment variable"
|
||||
exit -1
|
||||
end
|
||||
end
|
||||
|
||||
def parse_options(args)
|
||||
op = OptionParser.new do |opts|
|
||||
usage = <<EOF
|
||||
Run Dependency Review on a repository.
|
||||
|
||||
\e[1mUsage:\e[22m
|
||||
scripts/scan_pr [options] <pr_url>
|
||||
|
||||
\e[1mExample:\e[22m
|
||||
scripts/scan_pr https://github.com/actions/dependency-review-action/pull/294
|
||||
|
||||
EOF
|
||||
|
||||
opts.banner = usage
|
||||
|
||||
opts.on('-c', '--config-file <FILE>', 'Use an external configuration file') do |cf|
|
||||
@config_file = cf
|
||||
end
|
||||
|
||||
opts.on("-h", "--help", "Prints this help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
op.parse!(args)
|
||||
@option_parser = op
|
||||
end
|
||||
|
||||
def extract_repo_and_pr(args)
|
||||
# make sure we have a NWO somewhere in the parameters
|
||||
arg = /(?<repo_nwo>[\w\-]+\/[\w\-]+)\/pull\/(?<pr_number>\d+)/.match(args.join(" "))
|
||||
|
||||
if arg.nil?
|
||||
puts @option_parser
|
||||
exit -1
|
||||
end
|
||||
|
||||
[arg[:repo_nwo], arg[:pr_number]]
|
||||
end
|
||||
|
||||
def fetch_pull_request(repo_nwo, pr_number)
|
||||
octo = Octokit::Client.new(access_token: @github_token)
|
||||
octo.pull_request(repo_nwo, pr_number)
|
||||
end
|
||||
|
||||
def create_event_file(pr)
|
||||
event_file = Tempfile.new
|
||||
event_file.write("{ \"pull_request\": #{pr.to_h.to_json}}")
|
||||
event_file.close
|
||||
event_file
|
||||
end
|
||||
|
||||
def execute_dependency_review(repo_nwo, event_file)
|
||||
action_inputs = {
|
||||
"repo-token": @github_token,
|
||||
"config-file": @config_file
|
||||
}
|
||||
|
||||
dev_cmd_env = {
|
||||
"GITHUB_REPOSITORY" => repo_nwo,
|
||||
"GITHUB_EVENT_NAME" => "pull_request",
|
||||
"GITHUB_EVENT_PATH" => event_file.path,
|
||||
"GITHUB_STEP_SUMMARY" => "/dev/null"
|
||||
}
|
||||
|
||||
# Merge action inputs into environment, formatting keys as INPUT_...
|
||||
action_inputs_env = action_inputs.each_with_object({}) do |(name, value), h|
|
||||
h["INPUT_#{name.to_s.upcase}"] = value unless value.nil?
|
||||
end
|
||||
env = dev_cmd_env.merge(action_inputs_env)
|
||||
|
||||
dev_cmd = [
|
||||
"./node_modules/.bin/nodemon",
|
||||
"--exec",
|
||||
"node",
|
||||
"-r",
|
||||
"esbuild-register",
|
||||
"src/main.ts"
|
||||
]
|
||||
|
||||
Open3.popen2e(env, *dev_cmd) do |stdin, out|
|
||||
while line = out.gets
|
||||
puts line.gsub(@github_token, "<REDACTED>")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+40
@@ -24,6 +24,8 @@ import {getRefs} from './git-refs'
|
||||
import {groupDependenciesByManifest} from './utils'
|
||||
import {commentPr, MAX_COMMENT_LENGTH} from './comment-pr'
|
||||
import {getDeniedChanges} from './deny'
|
||||
import * as artifact from '@actions/artifact'
|
||||
import * as fs from 'fs'
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
@@ -61,6 +63,41 @@ async function getComparison(
|
||||
return comparison
|
||||
}
|
||||
|
||||
export async function handleLargeSummary(
|
||||
summaryContent: string
|
||||
): Promise<string> {
|
||||
const MAX_SUMMARY_SIZE = 1024 * 1024 // 1024k in bytes
|
||||
if (Buffer.byteLength(summaryContent, 'utf8') <= MAX_SUMMARY_SIZE) {
|
||||
return summaryContent
|
||||
}
|
||||
|
||||
const artifactClient = new artifact.DefaultArtifactClient()
|
||||
const artifactName = 'dependency-review-summary'
|
||||
const files = ['summary.md']
|
||||
|
||||
try {
|
||||
// Write the summary to a file
|
||||
await fs.promises.writeFile('summary.md', summaryContent)
|
||||
|
||||
// Upload the artifact
|
||||
await artifactClient.uploadArtifact(artifactName, files, '.', {
|
||||
retentionDays: 1
|
||||
})
|
||||
|
||||
// Return a minimal summary with a link to the artifact
|
||||
return `# Dependency Review Summary
|
||||
|
||||
The full dependency review summary is too large to display here. Please download the artifact named "${artifactName}" to view the complete report.
|
||||
|
||||
[View full job summary](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})`
|
||||
} catch (error) {
|
||||
core.warning(
|
||||
`Failed to handle large summary: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
return summaryContent
|
||||
}
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const config = await readConfig()
|
||||
@@ -179,6 +216,9 @@ async function run(): Promise<void> {
|
||||
let rendered = core.summary.stringify()
|
||||
core.setOutput('comment-content', rendered)
|
||||
|
||||
// Handle large summaries by uploading as artifact
|
||||
rendered = await handleLargeSummary(rendered)
|
||||
|
||||
// if the summary is oversized, replace with minimal version
|
||||
if (rendered.length >= MAX_COMMENT_LENGTH) {
|
||||
core.debug(
|
||||
|
||||
+14
-5
@@ -22,6 +22,10 @@ export function addSummaryToSummary(
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): string {
|
||||
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||
addDenyListsDeprecationWarningToSummary()
|
||||
}
|
||||
|
||||
const out: string[] = []
|
||||
|
||||
const scorecardWarnings = countScorecardWarnings(scorecard, config)
|
||||
@@ -106,6 +110,13 @@ export function addSummaryToSummary(
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function addDenyListsDeprecationWarningToSummary(): void {
|
||||
core.summary.addRaw(
|
||||
`${icons.warning} <strong>Deprecation Warning</strong>: The <em>deny-licenses</em> option is deprecated for possible removal in the next major release. For more information, see actions/dependency-review-action/issues/938.`,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
function countScorecardWarnings(
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
@@ -195,19 +206,17 @@ export function addLicensesToSummary(
|
||||
|
||||
if (config.allow_licenses && config.allow_licenses.length > 0) {
|
||||
core.summary.addQuote(
|
||||
`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`
|
||||
`<details><summary><strong>Allowed Licenses</strong>:</summary> ${config.allow_licenses.join(', ')}</details>`
|
||||
)
|
||||
}
|
||||
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||
core.summary.addQuote(
|
||||
`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`
|
||||
`<details><summary><strong>Denied Licenses</strong>:</summary> ${config.deny_licenses.join(', ')}</details>`
|
||||
)
|
||||
}
|
||||
if (config.allow_dependencies_licenses) {
|
||||
core.summary.addQuote(
|
||||
`<strong>Excluded from license check</strong>: ${config.allow_dependencies_licenses.join(
|
||||
', '
|
||||
)}`
|
||||
`<details><summary><strong>Excluded from license check</strong>:</summary> ${config.allow_dependencies_licenses.join(', ')}</details>`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user