From ee49ac2e3b7dfd4615c82f9d974fd41589bab71a Mon Sep 17 00:00:00 2001 From: per1234 Date: Mon, 7 Oct 2024 22:35:59 -0700 Subject: [PATCH] Add infrastructure to check for unapproved npm dependency licenses A task and GitHub Actions workflow are provided here for checking the license types of npm-managed project dependencies. On every push and pull request that affects relevant files, the CI workflow will check: - If the dependency licenses cache is up to date - If any of the project's dependencies have an unapproved license type. Approval can be based on: - Universally allowed license type - Individual dependency --- .github/CONTRIBUTING.md | 14 ++ .../workflows/check-npm-dependencies-task.yml | 156 ++++++++++++++++++ .licensed.yml | 63 +++++++ README.md | 1 + Taskfile.yml | 33 ++++ 5 files changed, 267 insertions(+) create mode 100644 .github/workflows/check-npm-dependencies-task.yml create mode 100644 .licensed.yml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d4412ac..fca6a11 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -74,6 +74,20 @@ Thanks! ## Common Development Operations +### Dependency License Metadata + +Metadata about the license types of all dependencies is cached in the repository. To update this cache, run the following command from the repository root folder: + +```text +task general:cache-dep-licenses +``` + +The necessary **Licensed** tool can be installed by following [these instructions](https://github.com/github/licensed#as-an-executable). + +Unfortunately, **Licensed** does not have Windows support. + +An updated cache is also generated whenever the cache is found to be outdated by the "**Check Go Dependencies**" CI workflow and made available for download via the `dep-licenses-cache` [workflow artifact](https://docs.github.com/actions/managing-workflow-runs/downloading-workflow-artifacts). + ### Running Checks Checks and tests are set up to ensure the project content is functional and compliant with the established standards. diff --git a/.github/workflows/check-npm-dependencies-task.yml b/.github/workflows/check-npm-dependencies-task.yml new file mode 100644 index 0000000..f06d609 --- /dev/null +++ b/.github/workflows/check-npm-dependencies-task.yml @@ -0,0 +1,156 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-npm-dependencies-task.md +name: Check npm Dependencies + +# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-npm-dependencies-task.ya?ml" + - ".licenses/**" + - ".licensed.json" + - ".licensed.ya?ml" + - "Taskfile.ya?ml" + - "**/.gitmodules" + - "**/.npmrc" + - "**/package.json" + - "**/package-lock.json" + pull_request: + paths: + - ".github/workflows/check-npm-dependencies-task.ya?ml" + - ".licenses/**" + - ".licensed.json" + - ".licensed.ya?ml" + - "Taskfile.ya?ml" + - "**/.gitmodules" + - "**/.npmrc" + - "**/package.json" + - "**/package-lock.json" + schedule: + # Run periodically to catch breakage caused by external changes. + - cron: "0 8 * * WED" + workflow_dispatch: + repository_dispatch: + +jobs: + run-determination: + runs-on: ubuntu-latest + permissions: {} + outputs: + result: ${{ steps.determination.outputs.result }} + steps: + - name: Determine if the rest of the workflow should run + id: determination + run: | + RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x" + # The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead. + if [[ + "${{ github.event_name }}" != "create" || + "${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX + ]]; then + # Run the other jobs. + RESULT="true" + else + # There is no need to run the other jobs. + RESULT="false" + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + + check-cache: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # This is required to allow jonabc/setup-licensed to install licensed via Ruby gem. + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby # Install latest version + + - name: Install licensed + uses: jonabc/setup-licensed@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: package.json + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Update dependencies license metadata cache + run: task --silent general:cache-dep-licenses + + - name: Check for outdated cache + id: diff + run: | + git add . + if ! git diff --cached --color --exit-code; then + echo + echo "::error::Dependency license metadata out of sync. See: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-go-dependencies-task.md#metadata-cache" + exit 1 + fi + + # Some might find it convenient to have CI generate the cache rather than setting up for it locally + - name: Upload cache to workflow artifact + if: failure() && steps.diff.outcome == 'failure' + uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + include-hidden-files: true + name: dep-licenses-cache + path: .licenses/ + + check-deps: + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # This is required to allow jonabc/setup-licensed to install licensed via Ruby gem. + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby # Install latest version + + - name: Install licensed + uses: jonabc/setup-licensed@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: package.json + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Check for dependencies with unapproved licenses + run: task --silent general:check-dep-licenses diff --git a/.licensed.yml b/.licensed.yml new file mode 100644 index 0000000..9d59351 --- /dev/null +++ b/.licensed.yml @@ -0,0 +1,63 @@ +# See: https://github.com/github/licensed/blob/master/docs/configuration.md + +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies/Apache-2.0/.licensed.yml +allowed: + # The following are based on https://www.apache.org/legal/resolved.html#category-a + - apache-2.0 + - apache-1.1 + - php-3.01 + # "MX4J License" - no SPDX ID + - bsd-2-clause + - bsd-2-clause-netbsd # Deprecated ID for `bsd-2-clause` + - bsd-2-clause-views + - bsd-2-clause-freebsd # Deprecated ID for `bsd-2-clause-views` + - bsd-3-clause + - bsd-3-clause-clear + # "DOM4J License" - no SPDX ID + - postgresql + # "Eclipse Distribution License 1.0" - no SPDX ID + - mit + - x11 + - isc + - smlnj + - standardml-nj # Deprecated ID for `smlnj` + # "Cup Parser Generator" - no SPDX ID + - icu + - ncsa + - w3c + # "W3C Community Contributor License Agreement" - no SPDX ID + - xnet + - zlib + # "FSF autoconf license" - no SPDX ID + - afl-3.0 + # "Service+Component+Architecture+Specifications" - no SPDX ID + # "OOXML XSD ECMA License" + - ms-pl + - cc-pddc + - psf-2.0 + # "Python Imaging Library Software License" + - apafml + - bsl-1.0 + - ogl-uk-3.0 + - wtfpl + - unicode-tou + - zpl-2.0 + # "ACE license" - no SPDX ID + - upl-1.0 + # "Open Grid Forum License" - no SPDX ID + # 'Google "Additional IP Rights Grant (Patents)" file' - no SPDX ID + - unlicense + - hpnd + - mulanpsl-2.0 + - cc0-1.0 + # The following are based on individual license text + - lgpl-2.0-or-later + - lgpl-2.0+ # Deprecated ID for `lgpl-2.0-or-later` + - lgpl-2.1-only + - lgpl-2.1 # Deprecated ID for `lgpl-2.1-only` + - lgpl-2.1-or-later + - lgpl-2.1+ # Deprecated ID for `lgpl-2.1-or-later` + - lgpl-3.0-only + - lgpl-3.0 # Deprecated ID for `lgpl-3.0-only` + - lgpl-3.0-or-later + - lgpl-3.0+ # Deprecated ID for `lgpl-3.0-or-later` diff --git a/README.md b/README.md index 6c67ec7..092fee3 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Tests Status](https://github.com/arduino/arduino-lint-action/actions/workflows/test-javascript-jest-task.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/test-javascript-jest-task.yml) [![Integration Tests Status](https://github.com/arduino/arduino-lint-action/workflows/Integration%20Tests/badge.svg)](https://github.com/arduino/arduino-lint-action/actions?workflow=Integration+Tests) [![Check License status](https://github.com/arduino/arduino-lint-action/actions/workflows/check-license.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/check-license.yml) +[![Check npm Dependencies status](https://github.com/arduino/arduino-lint-action/actions/workflows/check-npm-dependencies-task.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/check-npm-dependencies-task.yml) [![Check Packaging status](https://github.com/arduino/arduino-lint-action/actions/workflows/check-packaging-ncc-typescript-npm.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/check-packaging-ncc-typescript-npm.yml) [![Check Prettier Formatting status](https://github.com/arduino/arduino-lint-action/actions/workflows/check-prettier-formatting-task.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/check-prettier-formatting-task.yml) [![Check TypeScript Configuration status](https://github.com/arduino/arduino-lint-action/actions/workflows/check-tsconfig-task.yml/badge.svg)](https://github.com/arduino/arduino-lint-action/actions/workflows/check-tsconfig-task.yml) diff --git a/Taskfile.yml b/Taskfile.yml index 15258ea..be63117 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -36,6 +36,33 @@ tasks: - task: poetry:sync - task: ts:build + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies-task/Taskfile.yml + general:cache-dep-licenses: + desc: Cache dependency license metadata + deps: + - task: general:prepare-deps + cmds: + - | + if ! which licensed &>/dev/null; then + if [[ {{OS}} == "windows" ]]; then + echo "Licensed does not have Windows support." + echo "Please use Linux/macOS or download the dependencies cache from the GitHub Actions workflow artifact." + else + echo "licensed not found or not in PATH." + echo "Please install: https://github.com/github/licensed#as-an-executable" + fi + exit 1 + fi + - licensed cache + + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-dependencies-task/Taskfile.yml + general:check-dep-licenses: + desc: Check for unapproved dependency licenses + deps: + - task: general:cache-dep-licenses + cmds: + - licensed status + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check-task/Taskfile.yml general:check-spelling: desc: Check for commonly misspelled words @@ -64,6 +91,12 @@ tasks: cmds: - npx prettier --write . + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-npm-dependencies-task/Taskfile.yml + general:prepare-deps: + desc: Prepare project dependencies for license check + deps: + - task: npm:install-deps + js:test: desc: | Test the project's JavaScript/TypeScript code.