Bump lodash from 4.17.23 to 4.18.1 #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Copyright Validation | |
| on: | |
| # Direct trigger for org-level repository rulesets — works for PRs from forks | |
| # since pull_request_target runs with base repo context. The GITHUB_TOKEN is | |
| # explicitly scoped to least privilege in the job permissions block below. | |
| # Fork code is only read as text by the trusted copyrightcheck.py script; | |
| # no fork code is executed. Worst case from a malicious .copyrightconfig | |
| # is the check passes (policy bypass), not code execution. | |
| pull_request_target: | |
| types: [opened, edited, synchronize, reopened] | |
| # Also support being called as a reusable workflow from individual repos | |
| workflow_call: | |
| jobs: | |
| copyright-check: | |
| name: Validate Copyright Headers | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| # issues: write is needed for the PR comment step (workflow_call path only). | |
| # pull_request_target skips that step, but GitHub Actions has no per-event | |
| # conditional permissions within a single job — splitting into two jobs would | |
| # require artifact sharing and add significant complexity for minimal gain. | |
| issues: write | |
| steps: | |
| - name: Checkout PR head | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| ref: refs/pull/${{ github.event.pull_request.number }}/head | |
| path: target-repo | |
| persist-credentials: false | |
| - name: Checkout pr-workflows repo | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| repository: ${{ github.repository_owner }}/pr-workflows | |
| ref: main | |
| path: pr-workflows | |
| persist-credentials: false | |
| - name: Setup config | |
| id: setup-config | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| cfg="target-repo/.copyrightconfig" | |
| if [ ! -f "$cfg" ]; then | |
| git clone --depth 1 --branch "$BASE_REF" \ | |
| https://x-access-token:${GH_TOKEN}@github.com/${GH_REPO}.git base-repo | |
| [ -f base-repo/.copyrightconfig ] && cfg="base-repo/.copyrightconfig" | |
| fi | |
| [ -f "$cfg" ] || { echo "missing config"; exit 1; } | |
| [ -s "$cfg" ] || { echo "empty config"; exit 1; } | |
| grep -q '^startyear:' "$cfg" || { echo "startyear missing"; exit 1; } | |
| echo "config-file=$cfg" >> $GITHUB_OUTPUT | |
| - name: Set up Python | |
| uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4 | |
| with: | |
| python-version: '3.11' | |
| - name: Get changed files | |
| id: changed-files | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}/files" --paginate \ | |
| --jq '.[].filename' | while IFS= read -r f; do | |
| if [ -f "target-repo/$f" ]; then echo "$f"; fi | |
| done > files_to_check.txt | |
| count=$(wc -l < files_to_check.txt | tr -d ' ') | |
| if [ "$count" -eq 0 ]; then echo "skip-validation=true" >> $GITHUB_OUTPUT; else echo "skip-validation=false" >> $GITHUB_OUTPUT; fi | |
| echo "files-count=$count" >> $GITHUB_OUTPUT | |
| - name: Validate | |
| id: validate | |
| if: steps.changed-files.outputs.skip-validation != 'true' | |
| continue-on-error: true | |
| env: | |
| CONFIG_FILE: ${{ steps.setup-config.outputs.config-file }} | |
| run: | | |
| script="pr-workflows/scripts/copyrightcheck.py" | |
| cfg="$CONFIG_FILE" | |
| [ -f "$script" ] || { echo "script missing"; exit 1; } | |
| chmod +x "$script" | |
| export COPYRIGHT_CHECK_COMMIT_SHA=$(git -C target-repo rev-parse HEAD) | |
| python3 "$script" --config "$cfg" --working-dir target-repo \ | |
| --files-from-stdin < files_to_check.txt > validation_output.txt 2>&1 \ | |
| && echo "status=success" >> "$GITHUB_OUTPUT" \ | |
| || { echo "status=failed" >> "$GITHUB_OUTPUT"; exit 1; } | |
| - name: Extract Markdown summary | |
| if: always() && steps.changed-files.outputs.skip-validation != 'true' | |
| run: | | |
| awk '/^<<<COPYRIGHT-CHECK:MARKDOWN>>>/{f=1;next}/^<<<END COPYRIGHT-CHECK:MARKDOWN>>>/{f=0}f' validation_output.txt > summary.md | |
| echo '----- BEGIN COPYRIGHT SUMMARY (also in Job Summary) -----' | |
| cat summary.md | |
| echo '----- END COPYRIGHT SUMMARY -----' | |
| cat summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Post / Update PR comment with summary | |
| id: pr-comment | |
| # workflow_call: token is scoped to the calling repo — write access works. | |
| # pull_request_target (org ruleset): token is read-only for the triggering repo — | |
| # createComment/updateComment will 403. Skip and rely on Job Summary instead. | |
| if: always() && steps.changed-files.outputs.skip-validation != 'true' && github.event_name == 'workflow_call' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| VALIDATION_STATUS: ${{ steps.validate.outputs.status }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| try { | |
| if (!context.payload.pull_request) { core.warning('No pull_request context; skipping comment.'); core.setOutput('comment_action','unavailable'); return; } | |
| let mdRaw=''; | |
| try { mdRaw = fs.readFileSync('summary.md','utf8'); } catch(e){ core.warning('summary.md not found'); core.setOutput('comment_action','unavailable'); return; } | |
| if (!mdRaw.endsWith('\n')) mdRaw += '\n'; | |
| if (!mdRaw.includes('<!-- COPYRIGHT-CHECK-COMMENT:')) mdRaw += '<!-- COPYRIGHT-CHECK-COMMENT: v1 -->\n'; | |
| const isValid = process.env.VALIDATION_STATUS === 'success'; | |
| const number = context.payload.pull_request.number; | |
| const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: number, per_page: 100 }); | |
| const existing = comments.find(c => (c.body||'').includes('<!-- COPYRIGHT-CHECK-COMMENT:')); | |
| if (existing) { | |
| // Updating an existing comment does not send an email notification. | |
| await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body: mdRaw }); | |
| core.setOutput('comment_action','updated'); | |
| } else if (!isValid) { | |
| await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: number, body: mdRaw }); | |
| core.setOutput('comment_action','created'); | |
| } else { | |
| core.info('Copyright headers are valid and no prior comment exists; skipping comment to avoid email notification.'); | |
| core.setOutput('comment_action','skipped'); | |
| } | |
| } catch(e) { | |
| core.error(`Failed to create/update PR comment: ${e.status || ''} ${e.message}`); | |
| if (e.response) core.error(`Response: ${JSON.stringify(e.response.data).slice(0,400)}`); | |
| core.setOutput('comment_action','unavailable'); | |
| } | |
| - name: Check for validation errors | |
| if: always() && steps.changed-files.outputs.skip-validation != 'true' | |
| env: | |
| COMMENT_ACTION: ${{ steps.pr-comment.outputs.comment_action }} | |
| VALIDATION_STATUS: ${{ steps.validate.outputs.status }} | |
| run: | | |
| if [ "$VALIDATION_STATUS" != "success" ]; then | |
| if [ "$COMMENT_ACTION" = "updated" ] || [ "$COMMENT_ACTION" = "created" ]; then | |
| echo "::error title=Copyright Validation Failed::See the $COMMENT_ACTION PR comment for detailed results."; | |
| else | |
| echo "::error title=Copyright Validation Failed::Copyright headers are missing or invalid — see the Job Summary for details."; | |
| fi | |
| exit 1 | |
| fi | |
| - name: No-op summary | |
| if: steps.changed-files.outputs.skip-validation == 'true' | |
| run: echo "::notice title=Copyright Check::No files to validate" |