From 2b5ff8f015795ac5fecb1b960d7818096cb16d2f Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Sun, 26 Apr 2026 17:14:45 +0300 Subject: [PATCH 01/13] feat: add Lighthouse CI workflow for quality scoring Audits 4 pages on every PR: performance, accessibility, best practices, SEO. Accessibility < 90 blocks merge; others warn at < 90. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lighthouse.yml | 37 ++++++++++++++++++++++++++++++++ lighthouserc.json | 24 +++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 .github/workflows/lighthouse.yml create mode 100644 lighthouserc.json diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000..a61865a --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,37 @@ +name: Lighthouse CI + +on: + pull_request: + branches: + - main + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.12.0" + cache: npm + cache-dependency-path: astro-site/package-lock.json + + - name: Install dependencies + working-directory: astro-site + run: npm ci + + - name: Create assets symlink + run: mkdir -p astro-site/src/content && ln -s ../../../assets astro-site/src/content/assets + + - name: Build site + working-directory: astro-site + run: npm run build + + - name: Lighthouse CI + uses: treosh/lighthouse-ci-action@v12 + with: + configPath: ./lighthouserc.json + uploadArtifacts: true diff --git a/lighthouserc.json b/lighthouserc.json new file mode 100644 index 0000000..ccbf725 --- /dev/null +++ b/lighthouserc.json @@ -0,0 +1,24 @@ +{ + "ci": { + "collect": { + "staticDistDir": "./astro-site/dist", + "url": [ + "/tutorial-git/", + "/tutorial-git/introduction/", + "/tutorial-git/building-blocks/", + "/tutorial-git/branching-and-merging/" + ] + }, + "assert": { + "assertions": { + "categories:performance": ["warn", { "minScore": 0.9 }], + "categories:accessibility": ["error", { "minScore": 0.9 }], + "categories:best-practices": ["warn", { "minScore": 0.9 }], + "categories:seo": ["warn", { "minScore": 0.9 }] + } + }, + "upload": { + "target": "temporary-public-storage" + } + } +} From 6c3e50e6a21879b2cf63cdb0cfb48a0efda14fc8 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Sun, 26 Apr 2026 17:17:33 +0300 Subject: [PATCH 02/13] fix: use relative URLs for Lighthouse CI static server The static server serves from dist/ directly, so URLs should not include the base path prefix. Co-Authored-By: Claude Opus 4.6 (1M context) --- lighthouserc.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lighthouserc.json b/lighthouserc.json index ccbf725..a3d9ded 100644 --- a/lighthouserc.json +++ b/lighthouserc.json @@ -3,10 +3,10 @@ "collect": { "staticDistDir": "./astro-site/dist", "url": [ - "/tutorial-git/", - "/tutorial-git/introduction/", - "/tutorial-git/building-blocks/", - "/tutorial-git/branching-and-merging/" + "/", + "/introduction/", + "/building-blocks/", + "/branching-and-merging/" ] }, "assert": { From 1073fb4a296e4d4f34a3c1d1fcbe20d48b7bf8b7 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Sun, 26 Apr 2026 17:21:53 +0300 Subject: [PATCH 03/13] fix: remove public upload from Lighthouse config SonarCloud flagged temporary-public-storage as a security hotspot. Reports are still available as GitHub Actions artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) --- lighthouserc.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/lighthouserc.json b/lighthouserc.json index a3d9ded..700ad87 100644 --- a/lighthouserc.json +++ b/lighthouserc.json @@ -16,9 +16,6 @@ "categories:best-practices": ["warn", { "minScore": 0.9 }], "categories:seo": ["warn", { "minScore": 0.9 }] } - }, - "upload": { - "target": "temporary-public-storage" } } } From 5c5229e70a4dd1d30e338ea6b3b2392cf766efa3 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Sun, 26 Apr 2026 17:48:43 +0300 Subject: [PATCH 04/13] feat: add SonarCloud CI workflow Replaces the GitHub App integration with a CI-based scan. Scans astro-site/src on every PR and push to main. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/sonarcloud.yml | 23 +++++++++++++++++++++++ sonar-project.properties | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 .github/workflows/sonarcloud.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 0000000..0151055 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,23 @@ +name: SonarCloud + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + sonarcloud: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..3f4949f --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectKey=braboj_tutorial-git +sonar.organization=braboj +sonar.sources=astro-site/src +sonar.exclusions=**/node_modules/**,**/dist/**,**/.astro/** From d6d6e92c817501c510c571ec4218b529b2288fff Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:33:54 +0300 Subject: [PATCH 05/13] fix: update SonarCloud action to v6 v5 has a known security vulnerability per SonarSource advisory. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 0151055..bd79166 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -18,6 +18,6 @@ jobs: fetch-depth: 0 - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@v5 + uses: SonarSource/sonarqube-scan-action@v6 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From fbc7992fde9cede622ad970323db245f1aee0455 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:35:27 +0300 Subject: [PATCH 06/13] fix: add npm install for SonarCloud TypeScript analysis SonarCloud needs node_modules to resolve tsconfig references. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/sonarcloud.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index bd79166..407954c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -17,6 +17,17 @@ jobs: with: fetch-depth: 0 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.12.0" + cache: npm + cache-dependency-path: astro-site/package-lock.json + + - name: Install dependencies + working-directory: astro-site + run: npm ci + - name: SonarCloud Scan uses: SonarSource/sonarqube-scan-action@v6 env: From 721437f9a53a0113959eeb31c2df24691b80bf63 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:42:55 +0300 Subject: [PATCH 07/13] feat: post Lighthouse scores as PR comment Reads JSON reports from .lighthouseci/ and posts a table with per-page scores for performance, accessibility, best practices, and SEO. Updates existing comment on re-runs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lighthouse.yml | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index a61865a..cbd3313 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -8,6 +8,8 @@ on: jobs: lighthouse: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 @@ -31,7 +33,59 @@ jobs: run: npm run build - name: Lighthouse CI + id: lhci uses: treosh/lighthouse-ci-action@v12 with: configPath: ./lighthouserc.json uploadArtifacts: true + + - name: Post Lighthouse scores to PR + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + const dir = '.lighthouseci'; + const files = fs.readdirSync(dir).filter(f => f.endsWith('.json') && !f.startsWith('manifest')); + const rows = files.map(f => { + const r = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); + const url = new URL(r.finalUrl || r.requestedUrl); + const scores = r.categories; + const fmt = (cat) => { + if (!scores[cat]) return 'N/A'; + const s = Math.round(scores[cat].score * 100); + const icon = s >= 90 ? '๐ŸŸข' : s >= 50 ? '๐ŸŸ ' : '๐Ÿ”ด'; + return `${icon} ${s}`; + }; + return `| ${url.pathname} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`; + }); + const body = [ + '## Lighthouse Results', + '', + '| Page | Performance | Accessibility | Best Practices | SEO |', + '|------|------------|---------------|----------------|-----|', + ...rows, + '', + '_Scores: ๐ŸŸข 90+ ยท ๐ŸŸ  50-89 ยท ๐Ÿ”ด 0-49_' + ].join('\n'); + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => c.body.includes('## Lighthouse Results')); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } From d6f6f933834179f16ad3befb7de399479e9cf6e1 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:44:57 +0300 Subject: [PATCH 08/13] fix: handle missing URL fields in Lighthouse report parsing Use finalDisplayedUrl as primary, fall back to finalUrl, requestedUrl, then filename. Guard against non-http values. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lighthouse.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index cbd3313..42ff793 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -49,7 +49,8 @@ jobs: const files = fs.readdirSync(dir).filter(f => f.endsWith('.json') && !f.startsWith('manifest')); const rows = files.map(f => { const r = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); - const url = new URL(r.finalUrl || r.requestedUrl); + const rawUrl = r.finalDisplayedUrl || r.finalUrl || r.requestedUrl || ''; + const pagePath = rawUrl.startsWith('http') ? new URL(rawUrl).pathname : rawUrl || f; const scores = r.categories; const fmt = (cat) => { if (!scores[cat]) return 'N/A'; @@ -57,7 +58,7 @@ jobs: const icon = s >= 90 ? '๐ŸŸข' : s >= 50 ? '๐ŸŸ ' : '๐Ÿ”ด'; return `${icon} ${s}`; }; - return `| ${url.pathname} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`; + return `| ${pagePath} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`; }); const body = [ '## Lighthouse Results', From 61e016df26f38e447359651b3dcd0e2a51b4f06b Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:47:11 +0300 Subject: [PATCH 09/13] fix: handle nested lhr structure and skip non-report JSON files Lighthouse CI may wrap reports under .lhr key. Skip files without categories to avoid errors on manifest or other metadata files. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lighthouse.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 42ff793..6216e54 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -47,8 +47,11 @@ jobs: const path = require('path'); const dir = '.lighthouseci'; const files = fs.readdirSync(dir).filter(f => f.endsWith('.json') && !f.startsWith('manifest')); - const rows = files.map(f => { - const r = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); + const rows = []; + for (const f of files) { + const raw = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); + const r = raw.lhr || raw; + if (!r.categories) continue; const rawUrl = r.finalDisplayedUrl || r.finalUrl || r.requestedUrl || ''; const pagePath = rawUrl.startsWith('http') ? new URL(rawUrl).pathname : rawUrl || f; const scores = r.categories; @@ -58,8 +61,8 @@ jobs: const icon = s >= 90 ? '๐ŸŸข' : s >= 50 ? '๐ŸŸ ' : '๐Ÿ”ด'; return `${icon} ${s}`; }; - return `| ${pagePath} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`; - }); + rows.push(`| ${pagePath} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`); + } const body = [ '## Lighthouse Results', '', From 5be2825f3e0ccbf833c8418cabb88dcbb0924cd7 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 07:53:51 +0300 Subject: [PATCH 10/13] revert: remove Lighthouse PR comment feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep simple workflow โ€” scores available via artifacts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lighthouse.yml | 58 -------------------------------- 1 file changed, 58 deletions(-) diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 6216e54..a61865a 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -8,8 +8,6 @@ on: jobs: lighthouse: runs-on: ubuntu-latest - permissions: - pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 @@ -33,63 +31,7 @@ jobs: run: npm run build - name: Lighthouse CI - id: lhci uses: treosh/lighthouse-ci-action@v12 with: configPath: ./lighthouserc.json uploadArtifacts: true - - - name: Post Lighthouse scores to PR - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const dir = '.lighthouseci'; - const files = fs.readdirSync(dir).filter(f => f.endsWith('.json') && !f.startsWith('manifest')); - const rows = []; - for (const f of files) { - const raw = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); - const r = raw.lhr || raw; - if (!r.categories) continue; - const rawUrl = r.finalDisplayedUrl || r.finalUrl || r.requestedUrl || ''; - const pagePath = rawUrl.startsWith('http') ? new URL(rawUrl).pathname : rawUrl || f; - const scores = r.categories; - const fmt = (cat) => { - if (!scores[cat]) return 'N/A'; - const s = Math.round(scores[cat].score * 100); - const icon = s >= 90 ? '๐ŸŸข' : s >= 50 ? '๐ŸŸ ' : '๐Ÿ”ด'; - return `${icon} ${s}`; - }; - rows.push(`| ${pagePath} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`); - } - const body = [ - '## Lighthouse Results', - '', - '| Page | Performance | Accessibility | Best Practices | SEO |', - '|------|------------|---------------|----------------|-----|', - ...rows, - '', - '_Scores: ๐ŸŸข 90+ ยท ๐ŸŸ  50-89 ยท ๐Ÿ”ด 0-49_' - ].join('\n'); - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }); - const existing = comments.find(c => c.body.includes('## Lighthouse Results')); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body, - }); - } From e5a1675d060bf72e5d88d786bf24751d176a3bee Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 11:25:36 +0300 Subject: [PATCH 11/13] feat: replace SonarCloud with CodeQL CodeQL is GitHub-native, free for all repos (public and private), and aligns with the quality gates template in solid-ai-templates. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/codeql.yml | 29 +++++++++++++++++++++++++++ .github/workflows/sonarcloud.yml | 34 -------------------------------- sonar-project.properties | 4 ---- 3 files changed, 29 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/sonarcloud.yml delete mode 100644 sonar-project.properties diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..a507a23 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,29 @@ +name: CodeQL + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + analyze: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 407954c..0000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: SonarCloud - -on: - pull_request: - branches: - - main - push: - branches: - - main - -jobs: - sonarcloud: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "22.12.0" - cache: npm - cache-dependency-path: astro-site/package-lock.json - - - name: Install dependencies - working-directory: astro-site - run: npm ci - - - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@v6 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 3f4949f..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,4 +0,0 @@ -sonar.projectKey=braboj_tutorial-git -sonar.organization=braboj -sonar.sources=astro-site/src -sonar.exclusions=**/node_modules/**,**/dist/**,**/.astro/** From f2348edd8e20ce7253878766b63bc5a003a8b938 Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 11:49:02 +0300 Subject: [PATCH 12/13] fix: add explicit permissions to workflows Resolves CodeQL alert: workflows without explicit permissions run with broad defaults. Restrict to contents:read. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/build.yml | 3 +++ .github/workflows/lighthouse.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85f420..b6d0b0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,9 @@ on: branches: - main +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index a61865a..579ee8f 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -5,6 +5,9 @@ on: branches: - main +permissions: + contents: read + jobs: lighthouse: runs-on: ubuntu-latest From 4ab39cc6ad2a07f4524e5a5687cc97bc723882af Mon Sep 17 00:00:00 2001 From: Branimir Georgiev Date: Mon, 27 Apr 2026 11:51:20 +0300 Subject: [PATCH 13/13] chore: remove custom CodeQL workflow GitHub's default CodeQL setup is already enabled and covers JavaScript/TypeScript and Actions. Custom workflow conflicts with the default setup. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/codeql.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index a507a23..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: CodeQL - -on: - pull_request: - branches: - - main - push: - branches: - - main - -jobs: - analyze: - runs-on: ubuntu-latest - permissions: - security-events: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: javascript-typescript - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3