name: Build on: push: branches: - main pull_request: types: - opened - synchronize - reopened - ready_for_review merge_group: workflow_dispatch: inputs: dryRun: description: 'Dry-Run' default: 'true' required: false permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.ref }} cancel-in-progress: ${{ github.ref_name != 'main' }} env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} NODE_VERSION: 22 PDM_VERSION: 2.22.1 # renovate: datasource=pypi depName=pdm DRY_RUN: true TEST_LEGACY_DECRYPTION: true SPARSE_CHECKOUT: |- .github/actions/ data/ patches/ tools/ package.json pnpm-lock.yaml jobs: setup: runs-on: ubuntu-latest outputs: os-matrix: ${{ steps.os-matrix.outputs.os-matrix }} os-matrix-is-full: ${{ steps.os-matrix-is-full.outputs.os-matrix-is-full }} os-matrix-prefetch: ${{ steps.os-matrix-prefetch.outputs.matrix }} test-shard-matrix: ${{ steps.schedule-test-shards.outputs.test-shard-matrix }} test-matrix-empty: ${{ steps.schedule-test-shards.outputs.test-matrix-empty }} steps: - name: Calculate `os-matrix-is-full` output id: os-matrix-is-full env: IS_FULL: >- ${{ ( github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:fulltest') ) && 'true' || '' }} run: | echo 'OS_MATRIX_IS_FULL=${{ env.IS_FULL }}' >> "$GITHUB_ENV" echo 'os-matrix-is-full=${{ env.IS_FULL }}' >> "$GITHUB_OUTPUT" - name: Calculate `os-matrix` output id: os-matrix env: OS_ALL: '["ubuntu-latest", "macos-latest", "windows-latest"]' OS_LINUX_ONLY: '["ubuntu-latest"]' run: | echo 'os-matrix=${{ env.OS_MATRIX_IS_FULL && env.OS_ALL || env.OS_LINUX_ONLY }}' >> "$GITHUB_OUTPUT" - name: Detect changed files if: ${{ github.event_name == 'pull_request' }} id: changed-files env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.event.repository.full_name }} PR_URL: >- https://api.github.com/repos/{owner}/{repo}/compare/${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} JQ_FILTER: >- "changed-files=" + ([.files[].filename] | tostring) run: gh api ${{ env.PR_URL }} | jq -rc '${{ env.JQ_FILTER }}' >> "$GITHUB_OUTPUT" - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} show-progress: false - name: Calculate matrix for `node_modules` prefetch uses: ./.github/actions/calculate-prefetch-matrix id: os-matrix-prefetch with: repo: ${{ github.event.repository.full_name }} token: ${{ github.token }} node-version: ${{ env.NODE_VERSION }} - name: Prefetch test modules for `ubuntu-latest` id: setup-node uses: ./.github/actions/setup-node with: node-version: ${{ env.NODE_VERSION }} os: ${{ runner.os }} save-cache: true - name: Schedule test shards id: schedule-test-shards env: ALL_PLATFORMS: ${{ env.OS_MATRIX_IS_FULL }} FILTER_SHARDS: ${{ github.event.pull_request.draft && 'true' || '' }} CHANGED_FILES: ${{ steps.changed-files.outputs.changed-files }} run: | echo "$(pnpm -s schedule-test-shards)" >> "$GITHUB_OUTPUT" setup-build: runs-on: ubuntu-latest outputs: node-version: ${{ env.NODE_VERSION }} steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} show-progress: false - name: Prefetch build modules for `ubuntu-latest` uses: ./.github/actions/setup-node with: node-version: ${{ env.NODE_VERSION }} os: ${{ runner.os }} save-cache: true prefetch: needs: [setup] # We can't check `needs.setup.outputs.os-matrix-is-full` here, # as it will lead to further complications that aren't solvable # with current GitHub Actions feature set. # # Although this job sometimes may act as short-lived `no-op`, # it's actually the best option available. # # However, in draft mode we can skip this step. if: | !(github.event.pull_request.draft == true && needs.setup.outputs.test-matrix-empty == 'true') strategy: matrix: os: ${{ fromJSON(needs.setup.outputs.os-matrix-prefetch) }} runs-on: ${{ matrix.os }} timeout-minutes: 10 steps: - name: Checkout code if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} show-progress: false - name: Setup Node.js if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' uses: ./.github/actions/setup-node with: node-version: ${{ env.NODE_VERSION }} os: ${{ runner.os }} save-cache: true lint-eslint: needs: - setup-build runs-on: ubuntu-latest timeout-minutes: 15 permissions: actions: write steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Restore eslint cache uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/eslint key: eslint-main-cache - name: Lint run: pnpm eslint-ci - name: Remove cache if: github.event_name == 'push' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.event.repository.full_name }} run: | gh api --method DELETE /repos/{owner}/{repo}/actions/caches?key=eslint-main-cache || echo "Cache not found" - name: Save eslint cache if: github.event_name == 'push' uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/eslint key: eslint-main-cache lint-prettier: needs: - setup-build runs-on: ubuntu-latest timeout-minutes: 7 permissions: actions: write steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Restore prettier cache uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/prettier key: prettier-main-cache - name: Lint run: | pnpm prettier --write --cache-location .cache/prettier git diff --quiet || { echo "[ERROR] Please apply the changes prettier suggests:" git diff --color=always exit 1 } - name: Remove cache if: github.event_name == 'push' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.event.repository.full_name }} run: | gh api --method DELETE /repos/{owner}/{repo}/actions/caches?key=prettier-main-cache || echo "Cache not found" - name: Save prettier cache if: github.event_name == 'push' uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/prettier key: prettier-main-cache lint-docs: needs: - setup-build runs-on: ubuntu-latest timeout-minutes: 7 steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Lint markdown uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 # v18.0.0 - name: Lint fenced code blocks run: pnpm doc-fence-check - name: Lint documentation run: pnpm lint-documentation - name: Markdown lint run: pnpm markdown-lint lint-other: needs: - setup-build runs-on: ubuntu-latest timeout-minutes: 7 steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Type check run: pnpm type-check - name: Lint project file structure run: pnpm ls-lint - name: Check git version run: pnpm git-check - name: Test schema run: pnpm test-schema test: needs: [setup, prefetch] if: | !(github.event.pull_request.draft == true && needs.setup.outputs.test-matrix-empty == 'true') name: ${{ matrix.name }} runs-on: ${{ matrix.os }} timeout-minutes: ${{ matrix.runner-timeout-minutes }} strategy: matrix: include: ${{ fromJSON(needs.setup.outputs.test-shard-matrix) }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ env.NODE_VERSION }} os: ${{ runner.os }} - name: Cache jest uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: .cache/jest key: | jest-cache-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ matrix.cache-key }} - name: Unit tests shell: bash run: | for shard in ${{ matrix.shards }}; do TEST_SHARD="$shard" pnpm jest \ --ci \ --test-timeout ${{ matrix.test-timeout-milliseconds }} \ --coverage ${{ matrix.coverage }} done - name: Move coverage files if: (success() || failure()) && github.event.pull_request.draft != true && matrix.coverage run: | mkdir -p ./coverage/lcov mkdir -p ./coverage/json for shard in ${{ matrix.shards }}; do mv ./coverage/shard/$shard/lcov.info ./coverage/lcov/$shard.lcov mv ./coverage/shard/$shard/coverage-final.json ./coverage/json/$shard.json done - name: Save coverage artifacts if: (success() || failure()) && github.event.pull_request.draft != true && matrix.coverage uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ matrix.upload-artifact-name }} path: | ./coverage/lcov ./coverage/json codecov: needs: [test] runs-on: ubuntu-latest timeout-minutes: 3 if: (success() || failure()) && github.event_name != 'merge_group' && github.event.pull_request.draft != true steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: filter: blob:none # we don't need all blobs show-progress: false - name: Download coverage reports uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: coverage-* path: coverage merge-multiple: true - name: Codecov uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 with: token: ${{ secrets.CODECOV_TOKEN }} directory: coverage/lcov fail_ci_if_error: github.event_name != 'pull_request' verbose: true coverage-threshold: needs: - test - setup-build runs-on: ubuntu-latest timeout-minutes: 3 if: (success() || failure()) && github.event.pull_request.draft != true steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: filter: blob:none # we don't need all blobs sparse-checkout: ${{ env.SPARSE_CHECKOUT }} show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Download coverage reports uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: coverage-* path: coverage merge-multiple: true - name: Merge coverage reports run: pnpm nyc merge ./coverage/json ./coverage/nyc/coverage.json - name: Report coverage run: pnpm nyc report -t ./coverage/nyc --skip-full -r text -r text-summary - name: Check coverage threshold run: | pnpm nyc check-coverage -t ./coverage/nyc \ --branches 100 \ --functions 100 \ --lines 100 \ --statements 100 # Catch-all required check for test matrix and coverage test-success: needs: - setup - test - codecov - coverage-threshold runs-on: ubuntu-latest timeout-minutes: 1 if: always() steps: - name: Fail for failed or cancelled tests if: | needs.test.result == 'failure' || needs.test.result == 'cancelled' run: exit 1 - name: Fail for skipped tests when PR is ready for review if: | github.event_name == 'pull_request' && github.event.pull_request.draft != true && needs.test.result == 'skipped' run: exit 1 - name: Fail for failed or cancelled codecov if: | needs.codecov.result == 'failure' || needs.codecov.result == 'cancelled' run: exit 1 - name: Fail for skipped codecov when PR is ready for review if: | github.event_name == 'pull_request' && github.event.pull_request.draft != true && needs.codecov.result == 'skipped' run: exit 1 - name: Fail for failed or cancelled coverage-threshold if: | needs.coverage-threshold.result == 'failure' || needs.coverage-threshold.result == 'cancelled' run: exit 1 - name: Fail for skipped coverage-threshold when PR is ready for review if: | github.event_name == 'pull_request' && github.event.pull_request.draft != true && needs.coverage-threshold.result == 'skipped' run: exit 1 build: needs: - setup-build runs-on: ubuntu-latest timeout-minutes: 15 if: github.event.pull_request.draft != true steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Build run: pnpm build - name: Build docker run: pnpm build:docker build --tries=3 env: LOG_LEVEL: debug - name: Pack run: pnpm test-e2e:pack - name: Upload uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: renovate-package path: renovate-0.0.0-semantic-release.tgz build-docs: needs: - lint-docs - setup-build runs-on: ubuntu-latest timeout-minutes: 5 if: github.event.pull_request.draft != true steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - name: Setup PDM uses: pdm-project/setup-pdm@b2472ca4258a9ea3aee813980a0100a2261a42fc # v4.2 with: python-version-file: .python-version version: ${{ env.PDM_VERSION }} cache: true - name: Install pdm dependencies run: pdm install - name: Build run: pnpm build:docs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SKIP_GITHUB_ISSUES: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && 'true' || '' }} - name: Test docs run: pnpm test:docs - name: Upload uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docs path: tmp/docs/ - name: Build mkdocs run: pnpm mkdocs build --no-build test-e2e: needs: [build] runs-on: 'ubuntu-latest' timeout-minutes: 7 if: github.event.pull_request.draft != true steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: false - name: Setup pnpm uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: standalone: true - name: Setup Node.js uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Download package uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: renovate-package - name: Install dependencies run: pnpm test-e2e:install - name: E2E Test run: pnpm test-e2e:run release: needs: - setup-build - lint-eslint - lint-prettier - lint-docs - lint-other - test-e2e - test-success - build-docs - codecov - coverage-threshold if: github.repository == 'renovatebot/renovate' && github.event_name != 'pull_request' runs-on: ubuntu-latest timeout-minutes: 60 permissions: contents: write issues: write pull-requests: write id-token: write packages: write steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # zero stands for full checkout, which is required for semantic-release filter: blob:none # we don't need all blobs, only the full tree show-progress: false - name: docker-config uses: containerbase/internal-tools@b045538239fb42044408e9a3e0551a4b2755edf0 # v3.5.12 with: command: docker-config - name: Setup Node.js uses: ./.github/actions/setup-node with: node-version: ${{ needs.setup-build.outputs.node-version }} os: ${{ runner.os }} - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - name: Docker registry login run: | echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin - name: Check dry run run: | if [[ "${{github.event_name}}" == "workflow_dispatch" && "${{ github.event.inputs.dryRun }}" != "true" ]]; then echo "DRY_RUN=false" >> "$GITHUB_ENV" elif [[ "${{github.ref}}" == "refs/heads/${{env.DEFAULT_BRANCH}}" ]]; then echo "DRY_RUN=false" >> "$GITHUB_ENV" elif [[ "${{github.ref}}" =~ ^refs/heads/v[0-9]+(\.[0-9]+)?$ ]]; then echo "DRY_RUN=false" >> "$GITHUB_ENV" fi - name: semantic-release run: | pnpm semantic-release --dry-run ${{env.DRY_RUN}} env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # TODO: use action token? NPM_TOKEN: ${{ secrets.NPM_TOKEN }} LOG_LEVEL: debug