diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6fe40e2..7ab6e4f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -28,6 +28,9 @@ jobs: pull-requests: write with: TAG_MAJOR_AND_MINOR: true + secrets: + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} npm: uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6234c81..8406146 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,40 @@ jobs: id: versions run: echo "BUN_VERSION=$(sed -n 's/^bun = "\(.*\)"/\1/p' mise.toml)" >> "$GITHUB_OUTPUT" + # Docs-only changes skip everything but lint, which also checks markdown. + # Release PRs count as docs-only too: they only touch the changelog, the + # manifest and the version in package.json. Outside a pull request every job runs. + path-filter: + uses: this-is-tobi/github-workflows/.github/workflows/classify-changes.yml@v0 + if: ${{ !github.event.pull_request.draft }} + permissions: + pull-requests: read + with: + PROSE_PATTERNS: >- + ["docs/*", "*.md", "LICENSE", "NOTICE", ".release-please-manifest.json" + ${{ startsWith(github.head_ref, 'release-please--') && ', "package.json"' || '' }}] + + # Pull requests are rebase-merged, so every commit lands on main as is and + # release-please builds the version and changelog from it. + lint-commits: + uses: this-is-tobi/github-workflows/.github/workflows/lint-commits.yml@v0 + if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }} + permissions: + contents: read + with: + CONFIG_FILE: .commitlintrc.json + + scan-secrets: + uses: this-is-tobi/github-workflows/.github/workflows/scan-gitleaks.yml@v0 + if: ${{ !github.event.pull_request.draft }} + permissions: + contents: read + security-events: write + pull-requests: write + with: + GITHUB_SECURITY_TAB: true + PR_NUMBER: ${{ format('{0}', github.event.pull_request.number || '') }} + lint: uses: this-is-tobi/github-workflows/.github/workflows/lint-js.yml@v0 needs: @@ -36,8 +70,10 @@ jobs: tests-unit: uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0 + if: ${{ needs.path-filter.outputs.code == 'true' }} needs: - expose-vars + - path-filter with: RUNTIME: bun RUNTIME_VERSION: ${{ needs.expose-vars.outputs.BUN_VERSION }} @@ -49,13 +85,18 @@ jobs: tests-cli: uses: ./.github/workflows/tests-cli.yml + if: ${{ needs.path-filter.outputs.code == 'true' }} needs: - - expose-vars + - path-filter + secrets: + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} build-docker: uses: this-is-tobi/github-workflows/.github/workflows/build-docker.yml@v0 + if: ${{ needs.path-filter.outputs.code == 'true' }} needs: - - expose-vars + - path-filter permissions: contents: read packages: write @@ -72,14 +113,16 @@ jobs: tests-docker: uses: ./.github/workflows/tests-docker.yml needs: - - expose-vars - build-docker with: DOCKER_IMAGE: ${{ (github.event.pull_request.number || github.event.number) && format('ghcr.io/{0}/docpress:pr-{1}', github.repository_owner, (github.event.pull_request.number || github.event.number)) || '' }} + secrets: + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} scan-vuln: uses: this-is-tobi/github-workflows/.github/workflows/scan-trivy.yml@v0 - if: ${{ !github.event.pull_request.draft && (github.base_ref == 'main' || github.base_ref == 'develop') }} + if: ${{ github.base_ref == 'main' || github.base_ref == 'develop' }} needs: - build-docker permissions: @@ -93,6 +136,9 @@ jobs: FORMAT: sarif GITHUB_SECURITY_TAB: true PR_NUMBER: ${{ format('{0}', github.event.pull_request.number || github.event.number) }} + secrets: + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} # Sonarqube scan - requires vars.SONAR_URL repository variable and SONAR_TOKEN + SONAR_PROJECT_KEY secrets code-scan: @@ -116,13 +162,17 @@ jobs: -Dsonar.cpd.exclusions=**/*.spec.js,**/*.spec.ts secrets: inherit - # Workaround for required status check in protection branches (see. https://github.com/orgs/community/discussions/13690) + # The single check the ruleset requires, standing for the path-filtered jobs + # above: list every job so a failure never reaches it as a skip. all-jobs-passed: name: Check jobs status - runs-on: ubuntu-latest + uses: this-is-tobi/github-workflows/.github/workflows/check-jobs.yml@v0 if: ${{ always() }} needs: - expose-vars + - path-filter + - lint-commits + - scan-secrets - lint - tests-unit - tests-cli @@ -130,19 +180,5 @@ jobs: - tests-docker - scan-vuln - code-scan - steps: - - name: Check status of all required jobs - env: - NEEDS_CONTEXT: ${{ toJson(needs) }} - run: |- - JOB_IDS=$(echo "$NEEDS_CONTEXT" | jq -r 'keys[]') - for JOB_ID in $JOB_IDS; do - RESULT=$(echo "$NEEDS_CONTEXT" | jq -r ".[\"$JOB_ID\"].result") - echo "$JOB_ID job result: $RESULT" - if [[ $RESULT != "success" && $RESULT != "skipped" ]]; then - echo "***" - echo "Error: The $JOB_ID job did not pass." - exit 1 - fi - done - echo "All jobs passed or were skipped." + with: + NEEDS: ${{ toJson(needs) }} diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml index 1b39989..d3a3fa2 100644 --- a/.github/workflows/tests-cli.yml +++ b/.github/workflows/tests-cli.yml @@ -2,16 +2,39 @@ name: Tests - cli on: workflow_call: + secrets: + APP_CLIENT_ID: + description: GitHub App Client ID, used to mint a read-only token for the GitHub API calls docpress makes + required: false + APP_PRIVATE_KEY: + description: GitHub App private key (PEM). Required alongside APP_CLIENT_ID. + required: false workflow_dispatch: jobs: cli-tests: name: CLI tests runs-on: ubuntu-latest + env: + # `secrets` is not available in a step-level `if`, but it is here. + HAS_APP_AUTH: ${{ secrets.APP_CLIENT_ID != '' && secrets.APP_PRIVATE_KEY != '' }} steps: - name: Checks-out repository uses: actions/checkout@v6 + - name: Mint GitHub App token + # Read-only, and only for the 5,000 requests/hour an App gets against the + # job token's 1,000. Without the App secrets (e.g. fork pull requests) the + # tests fall back to GITHUB_TOKEN. + id: app-token + if: ${{ env.HAS_APP_AUTH == 'true' }} + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: read + permission-metadata: read + - name: Install toolchain # bun, node and pnpm as pinned in mise.toml. node and pnpm are only needed # for the npm and pnpm consumer-install scenarios below. @@ -36,5 +59,5 @@ jobs: - name: Run cli tests env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} run: ./ci/scripts/run-tests.sh -c 'npm,pnpm,bun' diff --git a/.github/workflows/tests-docker.yml b/.github/workflows/tests-docker.yml index 512414d..b09cd34 100644 --- a/.github/workflows/tests-docker.yml +++ b/.github/workflows/tests-docker.yml @@ -6,6 +6,13 @@ on: DOCKER_IMAGE: required: false type: string + secrets: + APP_CLIENT_ID: + description: GitHub App Client ID, used to mint a read-only token for the GitHub API calls docpress makes + required: false + APP_PRIVATE_KEY: + description: GitHub App private key (PEM). Required alongside APP_CLIENT_ID. + required: false workflow_dispatch: inputs: DOCKER_IMAGE: @@ -17,10 +24,26 @@ jobs: docker-tests: name: Docker tests runs-on: ubuntu-latest + env: + # `secrets` is not available in a step-level `if`, but it is here. + HAS_APP_AUTH: ${{ secrets.APP_CLIENT_ID != '' && secrets.APP_PRIVATE_KEY != '' }} steps: - name: Checks-out repository uses: actions/checkout@v6 + - name: Mint GitHub App token + # Read-only, and only for the 5,000 requests/hour an App gets against the + # job token's 1,000. Without the App secrets (e.g. fork pull requests) the + # tests fall back to GITHUB_TOKEN. + id: app-token + if: ${{ env.HAS_APP_AUTH == 'true' }} + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + permission-contents: read + permission-metadata: read + - name: Install bun uses: oven-sh/setup-bun@v2 with: @@ -28,6 +51,6 @@ jobs: - name: Run docker tests env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} DOCKER_IMAGE: ${{ inputs.DOCKER_IMAGE }} run: ./ci/scripts/run-tests.sh -d "$DOCKER_IMAGE"