From aa7676a0858773c12819f67c65bab431e4008f5d Mon Sep 17 00:00:00 2001 From: per1234 Date: Tue, 15 Oct 2024 21:33:43 -0700 Subject: [PATCH] Add infrastructure to check for problems with npm configuration files A task and GitHub Actions workflow are provided here for checking the project's npm package manager configuration. On every push and pull request that affects relevant files, and periodically: - Validate package.json against its JSON schema. - Check for forgotten package-lock.json syncs. --- .github/workflows/check-npm-task.yml | 120 +++++++++++++++++++++++++++ README.md | 1 + Taskfile.yml | 116 ++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 .github/workflows/check-npm-task.yml diff --git a/.github/workflows/check-npm-task.yml b/.github/workflows/check-npm-task.yml new file mode 100644 index 00000000..2fe90aef --- /dev/null +++ b/.github/workflows/check-npm-task.yml @@ -0,0 +1,120 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-npm-task.md +name: Check npm + +env: + # See: https://github.com/actions/setup-node/#readme + NODE_VERSION: 20.x + +# See: https://docs.github.com/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows +on: + create: + push: + paths: + - ".github/workflows/check-npm-task.ya?ml" + - "**/package.json" + - "**/package-lock.json" + - "Taskfile.ya?ml" + pull_request: + paths: + - ".github/workflows/check-npm-task.ya?ml" + - "**/package.json" + - "**/package-lock.json" + - "Taskfile.ya?ml" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema. + - cron: "0 8 * * TUE" + 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 + + validate: + name: validate (${{ matrix.project.path }}) + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + project: + - path: . + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Validate package.json + run: task --silent npm:validate PROJECT_PATH="${{ matrix.project.path }}" + + check-sync: + name: check-sync (${{ matrix.project.path }}) + needs: run-determination + if: needs.run-determination.outputs.result == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + project: + # TODO: add paths of all npm-managed projects in the repository here. + - path: . + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Install npm dependencies + run: task npm:install-deps PROJECT_PATH="${{ matrix.project.path }}" + + - name: Check package-lock.json + run: git diff --color --exit-code "${{ matrix.project.path }}/package-lock.json" diff --git a/README.md b/README.md index 782750aa..95a96224 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Check Go Dependencies status](https://github.com/arduino/arduino-lint/actions/workflows/check-go-dependencies-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/check-go-dependencies-task.yml) [![Publish Tester Build status](https://github.com/arduino/arduino-lint/actions/workflows/publish-go-tester-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/publish-go-tester-task.yml) [![Publish Nightly Build status](https://github.com/arduino/arduino-lint/actions/workflows/publish-go-nightly-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/publish-go-nightly-task.yml) +[![Check npm status](https://github.com/arduino/arduino-lint/actions/workflows/check-npm-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/check-npm-task.yml) [![Check Python status](https://github.com/arduino/arduino-lint/actions/workflows/check-python-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/check-python-task.yml) [![Check Markdown status](https://github.com/arduino/arduino-lint/actions/workflows/check-markdown-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/check-markdown-task.yml) [![Spell Check status](https://github.com/arduino/arduino-lint/actions/workflows/spell-check-task.yml/badge.svg)](https://github.com/arduino/arduino-lint/actions/workflows/spell-check-task.yml) diff --git a/Taskfile.yml b/Taskfile.yml index 5c439e9e..28cded44 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -18,6 +18,8 @@ vars: || \ echo '"ERROR: Unable to discover Go packages"' \ ) + # Last version of ajv-cli with support for the JSON schema "Draft 4" specification + SCHEMA_DRAFT_4_AJV_CLI_VERSION: 3.3.0 # build vars COMMIT: sh: echo "$(git log --no-show-signature -n 1 --format=%h)" @@ -380,6 +382,85 @@ tasks: cmds: - npm install + # Parameter variables: + # - PROJECT_PATH: path of the npm-managed project. Default value: "./" + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-npm-task/Taskfile.yml + npm:validate: + desc: Validate npm configuration files against their JSON schema + vars: + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/package.json + SCHEMA_URL: https://json.schemastore.org/package.json + SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="package-json-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/ava.json + AVA_SCHEMA_URL: https://json.schemastore.org/ava.json + AVA_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="ava-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/base.json + BASE_SCHEMA_URL: https://json.schemastore.org/base.json + BASE_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="base-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/eslintrc.json + ESLINTRC_SCHEMA_URL: https://json.schemastore.org/eslintrc.json + ESLINTRC_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="eslintrc-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/jscpd.json + JSCPD_SCHEMA_URL: https://json.schemastore.org/jscpd.json + JSCPD_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="jscpd-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/npm-badges.json + NPM_BADGES_SCHEMA_URL: https://json.schemastore.org/npm-badges.json + NPM_BADGES_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="npm-badges-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/partial-eslint-plugins.json + PARTIAL_ESLINT_PLUGINS_SCHEMA_URL: https://json.schemastore.org/partial-eslint-plugins.json + PARTIAL_ESLINT_PLUGINS_PATH: + sh: task utility:mktemp-file TEMPLATE="partial-eslint-plugins-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/prettierrc.json + PRETTIERRC_SCHEMA_URL: https://json.schemastore.org/prettierrc.json + PRETTIERRC_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="prettierrc-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/semantic-release.json + SEMANTIC_RELEASE_SCHEMA_URL: https://json.schemastore.org/semantic-release.json + SEMANTIC_RELEASE_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="semantic-release-schema-XXXXXXXXXX.json" + # Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/stylelintrc.json + STYLELINTRC_SCHEMA_URL: https://json.schemastore.org/stylelintrc.json + STYLELINTRC_SCHEMA_PATH: + sh: task utility:mktemp-file TEMPLATE="stylelintrc-schema-XXXXXXXXXX.json" + INSTANCE_PATH: >- + {{default "." .PROJECT_PATH}}/package.json + PROJECT_FOLDER: + sh: pwd + WORKING_FOLDER: + sh: task utility:mktemp-folder TEMPLATE="dependabot-validate-XXXXXXXXXX" + cmds: + - wget --quiet --output-document="{{.SCHEMA_PATH}}" {{.SCHEMA_URL}} + - wget --quiet --output-document="{{.AVA_SCHEMA_PATH}}" {{.AVA_SCHEMA_URL}} + - wget --quiet --output-document="{{.BASE_SCHEMA_PATH}}" {{.BASE_SCHEMA_URL}} + - wget --quiet --output-document="{{.ESLINTRC_SCHEMA_PATH}}" {{.ESLINTRC_SCHEMA_URL}} + - wget --quiet --output-document="{{.JSCPD_SCHEMA_PATH}}" {{.JSCPD_SCHEMA_URL}} + - wget --quiet --output-document="{{.NPM_BADGES_SCHEMA_PATH}}" {{.NPM_BADGES_SCHEMA_URL}} + - wget --quiet --output-document="{{.PARTIAL_ESLINT_PLUGINS_PATH}}" {{.PARTIAL_ESLINT_PLUGINS_SCHEMA_URL}} + - wget --quiet --output-document="{{.PRETTIERRC_SCHEMA_PATH}}" {{.PRETTIERRC_SCHEMA_URL}} + - wget --quiet --output-document="{{.SEMANTIC_RELEASE_SCHEMA_PATH}}" {{.SEMANTIC_RELEASE_SCHEMA_URL}} + - wget --quiet --output-document="{{.STYLELINTRC_SCHEMA_PATH}}" {{.STYLELINTRC_SCHEMA_URL}} + - | + cd "{{.WORKING_FOLDER}}" # Workaround for https://github.com/npm/cli/issues/3210 + npx ajv-cli@{{.SCHEMA_DRAFT_4_AJV_CLI_VERSION}} validate \ + --all-errors \ + -s "{{.SCHEMA_PATH}}" \ + -r "{{.AVA_SCHEMA_PATH}}" \ + -r "{{.BASE_SCHEMA_PATH}}" \ + -r "{{.ESLINTRC_SCHEMA_PATH}}" \ + -r "{{.JSCPD_SCHEMA_PATH}}" \ + -r "{{.NPM_BADGES_SCHEMA_PATH}}" \ + -r "{{.PARTIAL_ESLINT_PLUGINS_PATH}}" \ + -r "{{.PRETTIERRC_SCHEMA_PATH}}" \ + -r "{{.SEMANTIC_RELEASE_SCHEMA_PATH}}" \ + -r "{{.STYLELINTRC_SCHEMA_PATH}}" \ + -d "{{.PROJECT_FOLDER}}/{{.INSTANCE_PATH}}" + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/poetry-task/Taskfile.yml poetry:install-deps: desc: Install dependencies managed by Poetry @@ -465,6 +546,41 @@ tasks: fi - shfmt -w "{{.SCRIPT_PATH}}" + # Make a temporary file named according to the passed TEMPLATE variable and print the path passed to stdout + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/windows-task/Taskfile.yml + utility:mktemp-file: + vars: + RAW_PATH: + sh: mktemp --tmpdir "{{.TEMPLATE}}" + cmds: + - task: utility:normalize-path + vars: + RAW_PATH: "{{.RAW_PATH}}" + + # Make a temporary folder named according to the passed TEMPLATE variable and print the path passed to stdout + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/windows-task/Taskfile.yml + utility:mktemp-folder: + vars: + RAW_PATH: + sh: mktemp --directory --tmpdir "{{.TEMPLATE}}" + cmds: + - task: utility:normalize-path + vars: + RAW_PATH: "{{.RAW_PATH}}" + + # Print a normalized version of the path passed via the RAW_PATH variable to stdout + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/windows-task/Taskfile.yml + utility:normalize-path: + cmds: + - | + if [[ "{{.OS}}" == "Windows_NT" ]] && which cygpath &>/dev/null; then + # Even though the shell handles POSIX format absolute paths as expected, external applications do not. + # So paths passed to such applications must first be converted to Windows format. + cygpath -w "{{.RAW_PATH}}" + else + echo "{{.RAW_PATH}}" + fi + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-mkdocs-task/Taskfile.yml website:check: desc: Check whether the MkDocs-based website will build