diff --git a/.github/workflows/pr-check-lint_content.yml b/.github/workflows/pr-check-lint_content.yml index 6011a722bc1b5df..7cff1362c4ab1a0 100644 --- a/.github/workflows/pr-check-lint_content.yml +++ b/.github/workflows/pr-check-lint_content.yml @@ -1,20 +1,11 @@ name: Lint content on: - pull_request: - paths: - - .github/workflows/pr-check-lint_content.yml - - .nvmrc - - "*.md" - - "files/**/*.md" + workflow_call: permissions: contents: read -concurrency: - group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000000000..6dbbf820e536911 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,145 @@ +name: PR checks + +on: + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + cspell: ${{ steps.filter.outputs.cspell }} + javascript: ${{ steps.filter.outputs.javascript }} + json: ${{ steps.filter.outputs.json }} + content: ${{ steps.filter.outputs.content }} + redirects: ${{ steps.filter.outputs.redirects }} + scripts: ${{ steps.filter.outputs.scripts }} + urls: ${{ steps.filter.outputs.urls }} + yaml: ${{ steps.filter.outputs.yaml }} + steps: + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 + id: filter + with: + filters: | + shared: &shared + - .github/workflows/pr-check.yml + - .nvmrc + cspell: + - *shared + - .github/workflows/pr-check_cspell_lists.yml + - .vscode/dictionaries/* + - scripts/sort_and_unique_file_lines.js + javascript: + - *shared + - .github/workflows/pr-check_javascript.yml + - package.json + - package-lock.json + - "*.js" + - "*.mjs" + - "**/*.js" + - "**/*.mjs" + json: + - *shared + - .github/workflows/pr-check_json.yml + - "*.json" + - "*.jsonc" + - "**/*.json" + - "**/*.jsonc" + content: + - *shared + - .github/workflows/pr-check-lint_content.yml + - "*.md" + - files/**/*.md + redirects: + - *shared + - .github/workflows/pr-check_redirects.yml + - files/** + scripts: + - *shared + - .github/workflows/pr-check_scripts.yml + - package.json + - package-lock.json + urls: + - *shared + - .github/workflows/pr-check_url-issues.yml + - package.json + - package-lock.json + - files/**/*.md + - scripts/log-url-issues.js + yaml: + - *shared + - .github/workflows/pr-check_yml.yml + - package.json + - package-lock.json + - "*.yml" + - "*.yaml" + - "**/*.yml" + - "**/*.yaml" + + cspell: + needs: changes + if: needs.changes.outputs.cspell == 'true' + uses: ./.github/workflows/pr-check_cspell_lists.yml + + javascript: + needs: changes + if: needs.changes.outputs.javascript == 'true' + uses: ./.github/workflows/pr-check_javascript.yml + + json: + needs: changes + if: needs.changes.outputs.json == 'true' + uses: ./.github/workflows/pr-check_json.yml + + # `pr-reviewdog.yml` looks up this job by its name. + content: + needs: changes + if: needs.changes.outputs.content == 'true' + uses: ./.github/workflows/pr-check-lint_content.yml + + redirects: + needs: changes + if: needs.changes.outputs.redirects == 'true' + uses: ./.github/workflows/pr-check_redirects.yml + + scripts: + needs: changes + if: needs.changes.outputs.scripts == 'true' + uses: ./.github/workflows/pr-check_scripts.yml + + urls: + needs: changes + if: needs.changes.outputs.urls == 'true' + uses: ./.github/workflows/pr-check_url-issues.yml + + yaml: + needs: changes + if: needs.changes.outputs.yaml == 'true' + uses: ./.github/workflows/pr-check_yml.yml + + # Required aggregate check: skipped checks pass, failures and cancellations do not. + all-checks-passed-or-skipped: + if: ${{ !cancelled() }} + needs: + - changes + - cspell + - javascript + - json + - content + - redirects + - scripts + - urls + - yaml + runs-on: ubuntu-latest + steps: + - name: Fail if any check did not pass + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/pr-check_cspell_lists.yml b/.github/workflows/pr-check_cspell_lists.yml index 230a152fe2c051f..370e243715ac337 100644 --- a/.github/workflows/pr-check_cspell_lists.yml +++ b/.github/workflows/pr-check_cspell_lists.yml @@ -1,18 +1,13 @@ name: Check cSpell lists on: - pull_request: - paths: - - .github/workflows/pr-check_cspell_lists.yml - - .nvmrc - - .vscode/dictionaries/* - - scripts/sort_and_unique_file_lines.js + workflow_call: # No GITHUB_TOKEN permissions, as we don't use it. permissions: {} jobs: - docs: + check: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/pr-check_javascript.yml b/.github/workflows/pr-check_javascript.yml index f3c706c3209eeeb..6b8f8be6259f5f1 100644 --- a/.github/workflows/pr-check_javascript.yml +++ b/.github/workflows/pr-check_javascript.yml @@ -1,22 +1,13 @@ name: JavaScript lint on: - pull_request: - paths: - - .github/workflows/pr-check_javascript.yml - - .nvmrc - - package.json - - package-lock.json - - "*.js" - - "*.mjs" - - "**/*.js" - - "**/*.mjs" + workflow_call: # No GITHUB_TOKEN permissions, as we only use it to increase API limit. permissions: {} jobs: - lint-js: + lint: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr-check_json.yml b/.github/workflows/pr-check_json.yml index f24d4fb6c6e90c1..be7c4643a0db7f7 100644 --- a/.github/workflows/pr-check_json.yml +++ b/.github/workflows/pr-check_json.yml @@ -1,20 +1,13 @@ name: JSON lint on: - pull_request: - paths: - - .github/workflows/pr-check_json.yml - - .nvmrc - - "*.json" - - "*.jsonc" - - "**/*.json" - - "**/*.jsonc" + workflow_call: # No GITHUB_TOKEN permissions, as we only use it to increase API limit. permissions: {} jobs: - lint-json: + lint: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr-check_redirects.yml b/.github/workflows/pr-check_redirects.yml index 011ae0916e4df69..f5a0fec799f1d34 100644 --- a/.github/workflows/pr-check_redirects.yml +++ b/.github/workflows/pr-check_redirects.yml @@ -1,13 +1,13 @@ name: Check Redirects on: - pull_request: + workflow_call: # No GITHUB_TOKEN permissions, as we only use it to increase API limit. permissions: {} jobs: - check-redirects: + check: runs-on: ubuntu-latest steps: @@ -21,27 +21,12 @@ jobs: node-version-file: ".nvmrc" cache: npm - # This is a "required" workflow so path filtering can not be used: - # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks - # We have to rely on a custom filtering mechanism to run the checks only if required files are modified. - - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 - name: See if any file needs checking - id: filter - with: - filters: | - required_files: - - ".nvmrc" - - "files/**" - - ".github/workflows/pr-check_redirects.yml" - - name: Install - if: steps.filter.outputs.required_files == 'true' run: npm ci env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check redirects file(s) - if: steps.filter.outputs.required_files == 'true' run: npm run content validate-redirects en-US env: # Used by the `rari` cli to avoid rate limiting issues diff --git a/.github/workflows/pr-check_scripts.yml b/.github/workflows/pr-check_scripts.yml index 44f24a03a42bfe4..9fce9a378aebeec 100644 --- a/.github/workflows/pr-check_scripts.yml +++ b/.github/workflows/pr-check_scripts.yml @@ -1,12 +1,7 @@ name: Check scripts on: - pull_request: - paths: - - .github/workflows/pr-check_scripts.yml - - .nvmrc - - package.json - - package-lock.json + workflow_call: # No GITHUB_TOKEN permissions, as we only use it to increase API limit. permissions: {} @@ -76,7 +71,7 @@ jobs: echo "STDERR..................................................." cat /tmp/stderr.log - filecheck: + filecheck-help: runs-on: ubuntu-latest steps: @@ -122,7 +117,7 @@ jobs: - run: npm run content validate-redirects en-US - build: + build-help: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr-check_url-issues.yml b/.github/workflows/pr-check_url-issues.yml index ab98a00c078bae6..5ac855ff06060c1 100644 --- a/.github/workflows/pr-check_url-issues.yml +++ b/.github/workflows/pr-check_url-issues.yml @@ -1,20 +1,13 @@ name: Check URL issues on: - pull_request: - paths: - - .github/workflows/pr-check_url-issues.yml - - .nvmrc - - package.json - - package-lock.json - - "files/**/*.md" - - scripts/log-url-issues.js + workflow_call: # No GITHUB_TOKEN permissions, as we don't use it. permissions: {} jobs: - check_url_issues: + check: #if: github.repository == 'mdn/content' runs-on: ubuntu-latest diff --git a/.github/workflows/pr-check_yml.yml b/.github/workflows/pr-check_yml.yml index 822365f03a2e22a..2a3b450e7f56b83 100644 --- a/.github/workflows/pr-check_yml.yml +++ b/.github/workflows/pr-check_yml.yml @@ -1,22 +1,13 @@ name: Lint YAML on: - pull_request: - paths: - - .github/workflows/pr-check_yml.yml - - .nvmrc - - package.json - - package-lock.json - - "*.yml" - - "*.yaml" - - "**/*.yml" - - "**/*.yaml" + workflow_call: # No GITHUB_TOKEN permissions, as we only use it to increase API limit. permissions: {} jobs: - lint-yml: + lint: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr-reviewdog.yml b/.github/workflows/pr-reviewdog.yml index 597b59e80a7485e..6d3420a042f9ff2 100644 --- a/.github/workflows/pr-reviewdog.yml +++ b/.github/workflows/pr-reviewdog.yml @@ -3,9 +3,13 @@ name: PR Reviewdog on: workflow_run: workflows: - - "Lint content" + - "PR checks" types: - - completed + - in_progress + +# `in_progress` can fire more than once per run. +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} permissions: # Download artifact from lint workflow. @@ -46,23 +50,76 @@ jobs: --jq "[.[] | select(.base.repo.full_name == \"$BASE_REPO\" and .head.sha == \"$HEAD_SHA\") | .number] | first // empty") echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT - - name: Download artifact + - name: Wait for lint results + id: wait if: steps.identify-pr.outputs.number - continue-on-error: true + shell: bash + timeout-minutes: 11 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_PATH: repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }} + RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} + run: | + deadline=$((SECONDS + 600)) + # The lint job usually finishes within 30 seconds of the run starting. + sleep 30 + while ((SECONDS < deadline)); do + workflow_run=$(gh api "$RUN_PATH") + if jq -e --argjson attempt "$RUN_ATTEMPT" \ + '.run_attempt != $attempt or .conclusion == "cancelled"' <<< "$workflow_run" > /dev/null; then + echo "Workflow run was cancelled or re-run." + exit 0 + fi + + # A skipped reusable workflow is listed under the caller job id alone. + lint_job=$(gh api "$RUN_PATH/attempts/$RUN_ATTEMPT/jobs?per_page=100" --paginate | \ + jq -scr '[.[].jobs[] | select(.name == "content / lint" or .name == "content")] | first // empty') + if [[ -n "$lint_job" ]]; then + lint_started_at=$(jq -r '.started_at // empty' <<< "$lint_job") + if [[ -n "$lint_started_at" ]] && ! jq -e '.conclusion == "skipped"' <<< "$lint_job" > /dev/null; then + # Earlier attempts can leave artifacts under the same run ID. + artifact_id=$(gh api "$RUN_PATH/artifacts?per_page=100" --paginate | \ + jq -sr --arg started_at "$lint_started_at" \ + '[.[].artifacts[] | select(.name == "lint-results" and .expired == false and .created_at >= $started_at)] | first.id // empty') + if [[ -n "$artifact_id" ]]; then + echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + + if jq -e '.status == "completed"' <<< "$lint_job" > /dev/null; then + echo "Lint job finished without an artifact." + exit 0 + fi + fi + + if jq -e '.status == "completed"' <<< "$workflow_run" > /dev/null; then + echo "Workflow run finished without lint results." + exit 0 + fi + sleep 15 + done + echo "::warning::Timed out waiting for lint results." + exit 0 + + - name: Download artifact + if: steps.wait.outputs.artifact_id uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: lint-results + artifact-ids: ${{ steps.wait.outputs.artifact_id }} + # Without this, `artifact-ids` nests the files under `lint-results/lint-results/`. + merge-multiple: true path: lint-results github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - name: Check for artifact id: check - if: steps.identify-pr.outputs.number && hashFiles('lint-results/') != '' + if: steps.wait.outputs.artifact_id && hashFiles('lint-results/') != '' run: echo "HAS_ARTIFACT=true" >> "$GITHUB_OUTPUT" - name: Checkout - if: steps.identify-pr.outputs.number + if: steps.check.outputs.HAS_ARTIFACT == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: mdn-content