breaking: 2026 full-stack modernization #85
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: CI | ||
| on: | ||
| push: | ||
| branches: [develop, main] | ||
| pull_request: | ||
| branches: [develop, main] | ||
| workflow_dispatch: | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| checks: write | ||
| jobs: | ||
| test: | ||
| name: Test & Lint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Set up JDK 8 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '8' | ||
| - name: Set up Clojure (Leiningen) | ||
| uses: DeLaGuardo/setup-clojure@12.5 | ||
| with: | ||
| lein: 2.11.2 | ||
| - name: Cache Maven dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.m2/repository | ||
| key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-maven- | ||
| - name: Load local pdfbox libs | ||
| run: | | ||
| mkdir -p ~/.m2/repository/org/ | ||
| cp -rv ./lib/* ~/.m2/repository/ | ||
| - name: Install dependencies | ||
| run: lein deps | ||
| - name: Run linter | ||
| id: lint | ||
| run: | | ||
| echo "## π Lint Results" >> $GITHUB_STEP_SUMMARY | ||
| if lein lint 2>&1 | tee lint-output.txt; then | ||
| echo "β **Lint passed** - no errors" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "β **Lint failed**" >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| cat lint-output.txt >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| exit 1 | ||
| fi | ||
| - name: Run tests | ||
| id: test | ||
| run: | | ||
| echo "## π§ͺ Test Results" >> $GITHUB_STEP_SUMMARY | ||
| if lein test 2>&1 | tee test-output.txt; then | ||
| # Extract test summary | ||
| SUMMARY=$(grep -E "^Ran [0-9]+ tests" test-output.txt || echo "Tests completed") | ||
| echo "β **Tests passed**" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "β **Tests failed**" >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| tail -50 test-output.txt >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| exit 1 | ||
| fi | ||
| - name: Build ClojureScript | ||
| id: cljs | ||
| run: | | ||
| echo "## π¦ ClojureScript Build" >> $GITHUB_STEP_SUMMARY | ||
| if lein cljsbuild once dev 2>&1 | tee cljs-output.txt; then | ||
| echo "β **CLJS build succeeded**" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "β **CLJS build failed**" >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| tail -50 cljs-output.txt >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| exit 1 | ||
| fi | ||
| - name: Post PR comment with results | ||
| if: github.event_name == 'pull_request' && always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| // Read outputs | ||
| const lintOutput = fs.existsSync('lint-output.txt') ? fs.readFileSync('lint-output.txt', 'utf8') : 'No output'; | ||
| const testOutput = fs.existsSync('test-output.txt') ? fs.readFileSync('test-output.txt', 'utf8') : 'No output'; | ||
| const cljsOutput = fs.existsSync('cljs-output.txt') ? fs.readFileSync('cljs-output.txt', 'utf8') : 'No output'; | ||
| // Determine status | ||
| const lintOk = '${{ steps.lint.outcome }}' === 'success'; | ||
| const testOk = '${{ steps.test.outcome }}' === 'success'; | ||
| const cljsOk = '${{ steps.cljs.outcome }}' === 'success'; | ||
| const allOk = lintOk && testOk && cljsOk; | ||
| // Extract test summary | ||
| const testMatch = testOutput.match(/Ran (\d+) tests containing (\d+) assertions/); | ||
| const testSummary = testMatch ? `${testMatch[1]} tests, ${testMatch[2]} assertions` : 'Unknown'; | ||
| // Build comment | ||
| const status = allOk ? 'β All checks passed' : 'β Some checks failed'; | ||
| const body = `## ${status} | ||
| | Check | Status | Details | | ||
| |-------|--------|---------| | ||
| | π Lint | ${lintOk ? 'β Pass' : 'β Fail'} | ${lintOk ? 'No errors' : 'See workflow logs'} | | ||
| | π§ͺ Tests | ${testOk ? 'β Pass' : 'β Fail'} | ${testOk ? testSummary : 'See workflow logs'} | | ||
| | π¦ CLJS Build | ${cljsOk ? 'β Pass' : 'β Fail'} | ${cljsOk ? 'Compiled successfully' : 'See workflow logs'} | | ||
| <details> | ||
| <summary>π Full test output</summary> | ||
| \`\`\` | ||
| ${testOutput.slice(-2000)} | ||
| \`\`\` | ||
| </details> | ||
| --- | ||
| *Workflow run: [${context.runId}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})*`; | ||
| // Find existing comment | ||
| 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('All checks passed') || c.body.includes('Some checks failed') | ||
| ); | ||
| if (botComment) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: botComment.id, | ||
| body: body | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: body | ||
| }); | ||
| } | ||
| - name: Upload artifacts on failure | ||
| if: failure() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: ci-failure-logs | ||
| path: | | ||
| lint-output.txt | ||
| test-output.txt | ||
| cljs-output.txt | ||
| retention-days: 7 | ||