diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index fe9ef85..c6df716 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -12,8 +12,6 @@ permissions: jobs: conventional-commits: runs-on: ubuntu-latest - outputs: - status: ${{ steps.status.outputs.status }} steps: - name: Check conventional commits uses: helpers4/action/conventional-commits@main @@ -21,20 +19,8 @@ jobs: checkout: "true" pr-comment: "error" - - name: Set status - id: status - if: always() - run: | - if [ "${{ job.status }}" == "success" ]; then - echo "status=success" >> $GITHUB_OUTPUT - else - echo "status=failure" >> $GITHUB_OUTPUT - fi - version-bump-check: runs-on: ubuntu-latest - outputs: - status: ${{ steps.status.outputs.status }} steps: - name: Checkout uses: actions/checkout@v7 @@ -81,16 +67,6 @@ jobs: fi exit "$FAILED" - - name: Set status - id: status - if: always() - run: | - if [ "${{ job.status }}" == "success" ]; then - echo "status=success" >> $GITHUB_OUTPUT - else - echo "status=failure" >> $GITHUB_OUTPUT - fi - test-features: runs-on: ubuntu-latest strategy: @@ -150,9 +126,6 @@ jobs: - features: mistral-dev baseImage: ubuntu:latest - outputs: - status: ${{ steps.status.outputs.status }} - steps: - name: Checkout uses: actions/checkout@v7 @@ -189,16 +162,18 @@ jobs: shellcheck: runs-on: ubuntu-latest - outputs: - status: ${{ steps.status.outputs.status }} steps: - name: Checkout uses: actions/checkout@v7 - name: Run ShellCheck on install scripts - run: | - sudo apt-get update && sudo apt-get install -y shellcheck - find src -name "install.sh" -exec shellcheck -S warning {} + + uses: ludeeus/action-shellcheck@2.0.0 + with: + severity: warning + scandir: src + # Preserve the previous scope (install.sh only) โ€” path-keys.sh, sync-files.sh, + # and gh-auth.sh have never been linted here and may have pre-existing issues. + ignore_names: "path-keys.sh sync-files.sh gh-auth.sh" - name: Verify bootstrap copies match helpers4-common canonical run: | @@ -223,91 +198,18 @@ jobs: fi exit $FAILED - - name: Set status - id: status - if: always() - run: | - if [ "${{ job.status }}" == "success" ]; then - echo "status=success" >> $GITHUB_OUTPUT - else - echo "status=failure" >> $GITHUB_OUTPUT - fi - pr-comment: runs-on: ubuntu-latest needs: [conventional-commits, version-bump-check, test-features, shellcheck] if: always() steps: - name: Update PR comment - uses: actions/github-script@v9 + uses: helpers4/action/pr-status-comment@main with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const jobs = { - '๐Ÿงพ Conventional Commits': "${{ needs.conventional-commits.outputs.status || 'unknown' }}", - '๐Ÿ”– Version Bump': "${{ needs.version-bump-check.outputs.status || 'unknown' }}", - '๐Ÿงช Feature Tests': "${{ needs.test-features.result || 'unknown' }}", - '๐Ÿš ShellCheck': "${{ needs.shellcheck.outputs.status || 'unknown' }}", - }; - - const rows = Object.entries(jobs).map(([job, status]) => { - const icon = status === 'success' ? 'โœ…' : - status === 'failure' ? 'โŒ' : - status === 'skipped' ? 'โญ๏ธ' : 'โš ๏ธ'; - const badge = status === 'success' ? '`passing`' : - status === 'failure' ? '`failing`' : - status === 'skipped' ? '`skipped`' : '`unknown`'; - return `| ${icon} | **${job}** | ${badge} |`; - }).join('\n'); - - const passedCount = Object.values(jobs).filter(s => s === 'success').length; - const totalJobs = Object.keys(jobs).length; - const allPassed = passedCount === totalJobs; - - const statusBanner = allPassed - ? '## โœ… PR Validation Passed\n\n> All checks passed!' - : `## โŒ PR Validation Failed\n\n> ${passedCount}/${totalJobs} checks passed`; - - const comment = `${statusBanner} - - --- - - ### ๐Ÿ“‹ Pipeline Status - - | | Job | Status | - |:---:|-----|:------:| - ${rows} - - --- - - ๐Ÿค– Generated by **@helpers4** CI โ€ข ${new Date().toISOString().split('T')[0]}`; - - try { - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }); - - const botComment = comments.find(c => - c.user.type === 'Bot' && c.body.includes('PR Validation') - ); - - if (botComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: botComment.id, - body: comment - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: comment - }); - } - } catch (error) { - console.error('Error updating PR comment:', error); + jobs: | + { + "๐Ÿงพ Conventional Commits": "${{ needs.conventional-commits.result }}", + "๐Ÿ”– Version Bump": "${{ needs.version-bump-check.result }}", + "๐Ÿงช Feature Tests": "${{ needs.test-features.result }}", + "๐Ÿš ShellCheck": "${{ needs.shellcheck.result }}" } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33db30a..c34addc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -152,41 +152,33 @@ jobs: echo "โ†’ created tag ${tag}" done - - name: Get Trigganator token - id: trigganator - uses: actions/create-github-app-token@v3 - with: - client-id: ${{ vars.TRIGGANATOR_ID }} - private-key: ${{ secrets.TRIGGANATOR_KEY }} - owner: helpers4 - repositories: website - permission-contents: write - - - name: Trigger website update + trigger-website: + needs: [detect, release] + if: ${{ needs.detect.outputs.count != '0' }} + continue-on-error: true + runs-on: ubuntu-latest + # No checkout, no GITHUB_TOKEN use โ€” the composite action mints its own token via the + # GitHub App credentials passed in `with:`, so the default token needs zero permissions here. + permissions: {} + strategy: + fail-fast: false + # One dispatch per changed feature so the website workflow can react per-feature โ€” + # matrix built from detect's JSON output instead of a bash loop over `gh api`. + matrix: + feature: ${{ fromJson(needs.detect.outputs.changed) }} + steps: + - name: "Trigger website update: ${{ matrix.feature.name }} v${{ matrix.feature.version }}" continue-on-error: true - env: - CHANGED: ${{ needs.detect.outputs.changed }} - GH_TOKEN: ${{ steps.trigganator.outputs.token }} - run: | - set -euo pipefail - # One dispatch per feature so the website workflow can react per-feature. - echo "$CHANGED" | jq -c '.[]' | while read -r row; do - name=$(jq -r '.name' <<< "$row") - version=$(jq -r '.version' <<< "$row") - echo "โ†’ dispatch website: $name v$version" - # client_payload must be a JSON object, not a stringified one โ€” - # gh api's "field[nested]=value" bracket syntax builds it as such. - # (A previous version passed a pre-serialized JSON string via - # --raw-field, which the API rejects with HTTP 422.) - gh api \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - "/repos/helpers4/website/dispatches" \ - -f event_type=devcontainer-release \ - -f "client_payload[feature]=$name" \ - -f "client_payload[version]=$version" \ - -f "client_payload[ref]=${{ github.sha }}" \ - -f "client_payload[sha]=${{ github.sha }}" \ - -f "client_payload[source]=devcontainer-release" \ - || echo "โš ๏ธ dispatch failed for $name" - done + uses: helpers4/action/trigger-website-update@main + with: + event-type: devcontainer-release + payload: | + { + "feature": "${{ matrix.feature.name }}", + "version": "${{ matrix.feature.version }}", + "ref": "${{ github.sha }}", + "sha": "${{ github.sha }}", + "source": "devcontainer-release" + } + app-id: ${{ vars.TRIGGANATOR_ID }} + app-private-key: ${{ secrets.TRIGGANATOR_KEY }}