feat(cli): fix issue with content hash handling #74
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: PR | |
| on: | |
| pull_request: | |
| types: [opened, edited, synchronize, reopened] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| title: | |
| name: Title | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check format | |
| id: title | |
| uses: amannn/action-semantic-pull-request@v5 | |
| continue-on-error: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| types: | | |
| feat | |
| fix | |
| docs | |
| style | |
| refactor | |
| test | |
| chore | |
| ci | |
| perf | |
| revert | |
| requireScope: false | |
| subjectPattern: ^[a-z].+$ | |
| - name: Process results | |
| id: result | |
| run: | | |
| if [ "${{ steps.title.outcome }}" == "success" ]; then | |
| echo "status=Passed" >> $GITHUB_OUTPUT | |
| echo "details=" >> $GITHUB_OUTPUT | |
| else | |
| echo "status=Failed" >> $GITHUB_OUTPUT | |
| echo "details=Use: type(scope): description" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update PR comment | |
| uses: actions/github-script@v7 | |
| env: | |
| SECTION: PR Title | |
| STATUS: ${{ steps.result.outputs.status }} | |
| DETAILS: ${{ steps.result.outputs.details }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const marker = "<!-- ci-summary -->"; | |
| const detailsMarker = "<!-- details-section -->"; | |
| const section = process.env.SECTION; | |
| const status = process.env.STATUS; | |
| const details = process.env.DETAILS; | |
| const runUrl = process.env.RUN_URL; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.payload.pull_request.number; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, repo, issue_number, per_page: 100, | |
| }); | |
| const existing = comments.find(c => | |
| c.user?.login === "github-actions[bot]" && c.body?.includes(marker) | |
| ); | |
| let rows = {}; | |
| let existingDetails = {}; | |
| if (existing?.body) { | |
| const parts = existing.body.split(detailsMarker); | |
| const tableSection = parts[0] || ""; | |
| const lines = tableSection.split("\n"); | |
| for (const line of lines) { | |
| const match = line.match(/^\| ([^|]+) \| ([^|]+) \|$/); | |
| if (match) { | |
| const name = match[1].trim(); | |
| if (name && name !== "Check" && !name.startsWith(":")) { | |
| rows[name] = match[2].trim(); | |
| } | |
| } | |
| } | |
| const detailsRegex = /<details>\s*<summary><strong>([^<]+)<\/strong>.*?<\/summary>([\s\S]*?)<\/details>/g; | |
| let detailMatch; | |
| while ((detailMatch = detailsRegex.exec(existing.body)) !== null) { | |
| existingDetails[detailMatch[1].trim()] = detailMatch[0]; | |
| } | |
| } | |
| rows[section] = status; | |
| if (status === "Failed" && details) { | |
| existingDetails[section] = `<details>\n<summary><strong>${section}</strong></summary>\n\n${details}\n\nExample: \`feat(cli): add bulletin upload command\`\n\n[View run](${runUrl})\n\n</details>`; | |
| } else { | |
| delete existingDetails[section]; | |
| } | |
| const order = ["Lint", "Format", "Typecheck", "Build", "Release", "PR Title", "Labels"]; | |
| const sortedKeys = Object.keys(rows).sort((a, b) => { | |
| const ai = order.indexOf(a), bi = order.indexOf(b); | |
| return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi); | |
| }); | |
| let table = `| Check | Result |\n|:------|:-------|\n`; | |
| for (const key of sortedKeys) { | |
| table += `| ${key} | ${rows[key]} |\n`; | |
| } | |
| const detailsOrder = ["Lint", "Format", "Typecheck", "Build", "Release", "PR Title", "Labels"]; | |
| const sortedDetails = Object.keys(existingDetails).sort((a, b) => { | |
| const ai = detailsOrder.indexOf(a), bi = detailsOrder.indexOf(b); | |
| return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi); | |
| }); | |
| let body = `${marker}\n## CI Summary\n\n${table}`; | |
| if (sortedDetails.length > 0) { | |
| body += `\n${detailsMarker}\n\n---\n\n${sortedDetails.map(k => existingDetails[k]).join("\n\n")}`; | |
| } | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } | |
| - name: Fail if title invalid | |
| if: steps.title.outcome == 'failure' | |
| run: exit 1 | |
| labels: | |
| name: Labels | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Apply labels | |
| id: labels | |
| uses: actions/labeler@v5 | |
| continue-on-error: true | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get applied labels | |
| id: result | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.payload.pull_request.number; | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner, repo, pull_number: issue_number | |
| }); | |
| const labels = pr.labels.map(l => l.name).join(', '); | |
| if ('${{ steps.labels.outcome }}' === 'success') { | |
| core.setOutput('status', 'Passed'); | |
| core.setOutput('details', labels || 'No labels matched'); | |
| } else { | |
| core.setOutput('status', 'Failed'); | |
| core.setOutput('details', 'Labeler error'); | |
| } | |
| - name: Update PR comment | |
| uses: actions/github-script@v7 | |
| env: | |
| SECTION: Labels | |
| STATUS: ${{ steps.result.outputs.status }} | |
| DETAILS: ${{ steps.result.outputs.details }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const marker = "<!-- ci-summary -->"; | |
| const detailsMarker = "<!-- details-section -->"; | |
| const section = process.env.SECTION; | |
| const status = process.env.STATUS; | |
| const details = process.env.DETAILS; | |
| const runUrl = process.env.RUN_URL; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.payload.pull_request.number; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, repo, issue_number, per_page: 100, | |
| }); | |
| const existing = comments.find(c => | |
| c.user?.login === "github-actions[bot]" && c.body?.includes(marker) | |
| ); | |
| let rows = {}; | |
| let existingDetails = {}; | |
| if (existing?.body) { | |
| const parts = existing.body.split(detailsMarker); | |
| const tableSection = parts[0] || ""; | |
| const lines = tableSection.split("\n"); | |
| for (const line of lines) { | |
| const match = line.match(/^\| ([^|]+) \| ([^|]+) \|$/); | |
| if (match) { | |
| const name = match[1].trim(); | |
| if (name && name !== "Check" && !name.startsWith(":")) { | |
| rows[name] = match[2].trim(); | |
| } | |
| } | |
| } | |
| const detailsRegex = /<details>\s*<summary><strong>([^<]+)<\/strong>.*?<\/summary>([\s\S]*?)<\/details>/g; | |
| let detailMatch; | |
| while ((detailMatch = detailsRegex.exec(existing.body)) !== null) { | |
| existingDetails[detailMatch[1].trim()] = detailMatch[0]; | |
| } | |
| } | |
| rows[section] = status; | |
| if (status === "Failed") { | |
| existingDetails[section] = `<details>\n<summary><strong>${section}</strong></summary>\n\n${details}\n\n[View run](${runUrl})\n\n</details>`; | |
| } else if (details && details !== 'No labels matched') { | |
| existingDetails[section] = `<details>\n<summary><strong>${section}</strong></summary>\n\n${details}\n\n</details>`; | |
| } else { | |
| delete existingDetails[section]; | |
| } | |
| const order = ["Lint", "Format", "Typecheck", "Build", "Release", "PR Title", "Labels"]; | |
| const sortedKeys = Object.keys(rows).sort((a, b) => { | |
| const ai = order.indexOf(a), bi = order.indexOf(b); | |
| return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi); | |
| }); | |
| let table = `| Check | Result |\n|:------|:-------|\n`; | |
| for (const key of sortedKeys) { | |
| table += `| ${key} | ${rows[key]} |\n`; | |
| } | |
| const detailsOrder = ["Lint", "Format", "Typecheck", "Build", "Release", "PR Title", "Labels"]; | |
| const sortedDetails = Object.keys(existingDetails).sort((a, b) => { | |
| const ai = detailsOrder.indexOf(a), bi = detailsOrder.indexOf(b); | |
| return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi); | |
| }); | |
| let body = `${marker}\n## CI Summary\n\n${table}`; | |
| if (sortedDetails.length > 0) { | |
| body += `\n${detailsMarker}\n\n---\n\n${sortedDetails.map(k => existingDetails[k]).join("\n\n")}`; | |
| } | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } |