diff --git a/.github/workflows/accessibility.yml b/.github/workflows/accessibility.yml index b0cd6b2..a562ea0 100644 --- a/.github/workflows/accessibility.yml +++ b/.github/workflows/accessibility.yml @@ -16,21 +16,24 @@ jobs: uses: actions/setup-node@v4 with: node-version: '18' + cache: npm + + - name: Install dependencies + run: npm ci - name: Install LHCI - run: npm install -g @lhci/cli@0.7 + run: npm install -g @lhci/cli@0.14 - name: Run Lighthouse CI env: - # allow LHCI to include branch info LHCI_BUILD_CONTEXT__CURRENT_BRANCH: ${{ github.head_ref }} - run: | - # Run LHCI autorun; results uploaded to temporary public storage - # Tell LHCI where the static site files live (repo root) - lhci autorun --upload.target=temporary-public-storage --collect.staticDistDir=. + CHROME_PATH: /usr/bin/google-chrome-stable + run: lhci autorun + - name: Upload LHCI artifacts if: always() uses: actions/upload-artifact@v4 with: name: lhci-report path: ./.lighthouseci/**/* + if-no-files-found: ignore diff --git a/.github/workflows/axe-audit.yml b/.github/workflows/axe-audit.yml index 3115a4c..7aa6c10 100644 --- a/.github/workflows/axe-audit.yml +++ b/.github/workflows/axe-audit.yml @@ -15,29 +15,29 @@ jobs: uses: actions/setup-node@v4 with: node-version: '18' + cache: npm - name: Install dependencies - run: npm install --no-audit --no-fund + run: npm ci - name: Install Playwright browsers - run: npx playwright install --with-deps + run: npx playwright install --with-deps chromium - - name: Start static server + - name: Serve site and run axe audit run: | - npm run start & + npx http-server -p 8000 -c-1 & echo $! > server.pid - - - name: Run axe audit - run: | - node axe-audit.js > axe-results.json || true + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:8000/ > /dev/null; then break; fi + sleep 1 + done + npm run axe-audit + kill "$(cat server.pid)" 2>/dev/null || true + shell: bash - name: Upload axe results + if: always() uses: actions/upload-artifact@v4 with: name: axe-results path: axe-results.json - - - name: Stop server - if: always() - run: | - kill $(cat server.pid) || true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ced38b6..9e01d4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,10 @@ on: push: branches: [ main, master ] +permissions: + contents: read + pull-requests: write + jobs: build-and-a11y: name: Build & A11y checks @@ -17,6 +21,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: '18' + cache: npm - name: Install dependencies run: npm ci @@ -25,35 +30,55 @@ jobs: run: npm audit --audit-level=low || true - name: Serve site - run: python3 -m http.server 8000 & sleep 2 + run: | + npx http-server -p 8000 -c-1 & + echo $! > server.pid + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:8000/ > /dev/null; then exit 0; fi + sleep 1 + done + echo "Server did not become ready" >&2 + exit 1 shell: bash - name: Run pa11y - run: npx pa11y http://127.0.0.1:8000 --reporter json > pa11y.json || true + run: | + if npx pa11y http://127.0.0.1:8000 --reporter json > pa11y.json; then + : + else + echo '[]' > pa11y.json + fi shell: bash - name: Upload pa11y report + if: always() uses: actions/upload-artifact@v4 with: name: pa11y-report path: pa11y.json - name: Install Playwright browsers - run: npx playwright install --with-deps + run: npx playwright install --with-deps chromium - name: Run axe audit (Playwright) - run: npm run axe-audit || true + run: npm run axe-audit - name: Upload axe results (if any) + if: always() uses: actions/upload-artifact@v4 with: name: axe-results - path: | - axe-results.json + path: axe-results.json + + - name: Stop server + if: always() + run: kill "$(cat server.pid)" 2>/dev/null || true + shell: bash - name: Comment accessibility summary on PR - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 + if: github.event_name == 'pull_request' && !cancelled() + continue-on-error: true + uses: actions/github-script@v7 with: script: | const fs = require('fs'); @@ -62,38 +87,30 @@ jobs: let paCount = 'n/a'; let axeCount = 'n/a'; if (paExists) { - try { const pa = JSON.parse(fs.readFileSync('pa11y.json','utf8')); paCount = Array.isArray(pa) ? pa.length : (pa.issues? pa.issues.length : Object.keys(pa).length); } catch(e) { paCount = 'parse error'; } + try { + const pa = JSON.parse(fs.readFileSync('pa11y.json', 'utf8')); + paCount = Array.isArray(pa) ? pa.length : (pa.issues ? pa.issues.length : Object.keys(pa).length); + } catch (e) { + paCount = 'parse error'; + } } if (axeExists) { - try { const axe = JSON.parse(fs.readFileSync('axe-results.json','utf8')); axeCount = (axe.violations && axe.violations.length) ? axe.violations.length : 0; } catch(e) { axeCount = 'parse error'; } + try { + const axe = JSON.parse(fs.readFileSync('axe-results.json', 'utf8')); + if (axe.error) { + axeCount = 'audit error'; + } else { + axeCount = (axe.violations && axe.violations.length) ? axe.violations.length : 0; + } + } catch (e) { + axeCount = 'parse error'; + } } const artifactsUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts`; const body = `Accessibility report summary:\n- Pa11y issues: ${paCount}\n- Axe violations: ${axeCount}\n\nDownload artifacts (pa11y.json, axe-results.json) from the workflow run: ${artifactsUrl}`; - await github.rest.issues.createComment({owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body}); - - deploy: - name: Deploy to GitHub Pages - runs-on: ubuntu-latest - needs: build-and-a11y - if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install - run: npm ci - - - name: Build (if applicable) - run: npm run build || true - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./ - publish_branch: gh-pages + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); diff --git a/.github/workflows/eleventy-gh-pages.yml b/.github/workflows/eleventy-gh-pages.yml deleted file mode 100644 index 1c88391..0000000 --- a/.github/workflows/eleventy-gh-pages.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build and deploy Eleventy site - -on: - push: - branches: [ main, redesign/high-quality ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - name: Install dependencies - run: npm install - - name: Build site with Eleventy - run: npx @11ty/eleventy - - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_site diff --git a/.github/workflows/hugo-deploy.yml b/.github/workflows/hugo-deploy.yml deleted file mode 100644 index d4018fc..0000000 --- a/.github/workflows/hugo-deploy.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Deploy Hugo site to GitHub Pages - -on: - push: - branches: - - main - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment -concurrency: - group: "pages" - cancel-in-progress: false - -# Default to bash -defaults: - run: - shell: bash - -jobs: - build: - runs-on: ubuntu-latest - env: - HUGO_VERSION: 0.121.1 - steps: - - name: Install Hugo CLI - run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb - - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - fetch-depth: 0 - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v4 - - - name: Build with Hugo - env: - HUGO_ENVIRONMENT: production - HUGO_ENV: production - CLERK_PUBLISHABLE_KEY: ${{ secrets.CLERK_PUBLISHABLE_KEY }} - run: | - hugo \ - --gc \ - --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./public - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml deleted file mode 100644 index e31d81c..0000000 --- a/.github/workflows/jekyll-gh-pages.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll with GitHub Pages dependencies preinstalled - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./ - destination: ./_site - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 8ea1b8d..973da63 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ _site/ .next temitope-hospital-starter/out node_modules +axe-results.json +pa11y.json +.lighthouseci/ +server.pid diff --git a/axe-audit.js b/axe-audit.js index 69ca193..05ed3bc 100644 --- a/axe-audit.js +++ b/axe-audit.js @@ -23,12 +23,16 @@ const path = require('path'); // write results to file so the workflow can upload them fs.writeFileSync(outPath, JSON.stringify(results, null, 2)); - console.log('WROTE_AXE_RESULTS', outPath); + const violationCount = Array.isArray(results.violations) ? results.violations.length : 0; + console.error(`Wrote axe results to ${outPath} (${violationCount} violations)`); + if (violationCount > 0) { + process.exitCode = 1; + } } catch (err) { console.error('Audit failed:', err && err.stack ? err.stack : err); try { fs.writeFileSync(outPath, JSON.stringify({ error: String(err) }, null, 2)); - console.log('WROTE_AXE_RESULTS_ERROR', outPath); + console.error(`Wrote axe error to ${outPath}`); } catch (e) { console.error('Failed to write error file:', e && e.message ? e.message : e); } diff --git a/axe-results.json b/axe-results.json deleted file mode 100644 index 235954c..0000000 --- a/axe-results.json +++ /dev/null @@ -1,311 +0,0 @@ -WROTE_AXE_RESULTS /Users/adamsy/Desktop/@adabdotcom/adab-tech.github.io/axe-results.json -{ - "name": "axe" - }, - "testEnvironment": { - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/143.0.7499.4 Safari/537.36", - "windowWidth": 1280, - "windowHeight": 720, - "orientationAngle": 0, - "orientationType": "landscape-primary" - }, - "timestamp": "2026-01-12T23:08:21.136Z", - "url": "http://127.0.0.1:8000/", - "toolOptions": { - "runOnly": { - "type": "tag", - "values": [ - "wcag2aa" - ] - }, - "reporter": "v1" - }, - "inapplicable": [ - { - "id": "valid-lang", - "impact": null, - "tags": [ - "cat.language", - "wcag2aa", - "wcag312", - "TTv5", - "TT11.b", - "EN-301-549", - "EN-9.3.1.2", - "ACT", - "RGAAv4", - "RGAA-8.8.1" - ], - "description": "Ensure lang attributes have valid values", - "help": "lang attribute must have a valid value", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/valid-lang?application=axeAPI", - "nodes": [] - } - ], - "passes": [ - { - "id": "color-contrast", - "impact": "serious", - "tags": [ - "cat.color", - "wcag2aa", - "wcag143", - "TTv5", - "TT13.c", - "EN-301-549", - "EN-9.1.4.3", - "ACT", - "RGAAv4", - "RGAA-3.2.1" - ], - "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", - "help": "Elements must meet minimum color contrast ratio thresholds", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast?application=axeAPI", - "nodes": [ - { - "any": [ - { - "id": "color-contrast", - "data": { - "fgColor": "#1e293b", - "bgColor": "#ffffff", - "contrastRatio": 14.62, - "fontSize": "10.5pt (14px)", - "fontWeight": "normal", - "expectedContrastRatio": "4.5:1" - }, - "relatedNodes": [], - "impact": "serious", - "message": "Element has sufficient color contrast of 14.62" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "Home", - "target": [ - ".nav-link[href=\"/\"]" - ] - }, - { - "any": [ - { - "id": "color-contrast", - "data": { - "fgColor": "#1e293b", - "bgColor": "#ffffff", - "contrastRatio": 14.62, - "fontSize": "10.5pt (14px)", - "fontWeight": "normal", - "expectedContrastRatio": "4.5:1" - }, - "relatedNodes": [], - "impact": "serious", - "message": "Element has sufficient color contrast of 14.62" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "Blog", - "target": [ - ".nav-link[href$=\"blog/\"]" - ] - }, - { - "any": [ - { - "id": "color-contrast", - "data": { - "fgColor": "#1e293b", - "bgColor": "#ffffff", - "contrastRatio": 14.62, - "fontSize": "25.5pt (34px)", - "fontWeight": "bold", - "expectedContrastRatio": "3:1" - }, - "relatedNodes": [], - "impact": "serious", - "message": "Element has sufficient color contrast of 14.62" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "
Computational Linguist & AI Researcher specializing in African Language Technology
", - "target": [ - "p:nth-child(2)" - ] - }, - { - "any": [ - { - "id": "color-contrast", - "data": { - "fgColor": "#1e293b", - "bgColor": "#ffffff", - "contrastRatio": 14.62, - "fontSize": "12.8pt (17px)", - "fontWeight": "normal", - "expectedContrastRatio": "4.5:1" - }, - "relatedNodes": [], - "impact": "serious", - "message": "Element has sufficient color contrast of 14.62" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "Building bridges between technology and African languages through NLP and AI.
", - "target": [ - "p:nth-child(3)" - ] - }, - { - "any": [ - { - "id": "color-contrast", - "data": { - "fgColor": "#0b5cff", - "bgColor": "#ffffff", - "contrastRatio": 5.26, - "fontSize": "12.8pt (17px)", - "fontWeight": "normal", - "expectedContrastRatio": "4.5:1" - }, - "relatedNodes": [], - "impact": "serious", - "message": "Element has sufficient color contrast of 5.26" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "Visit the blog", - "target": [ - "p:nth-child(4) > a[href$=\"blog/\"]" - ] - } - ] - }, - { - "id": "meta-viewport", - "impact": null, - "tags": [ - "cat.sensory-and-visual-cues", - "wcag2aa", - "wcag144", - "EN-301-549", - "EN-9.1.4.4", - "ACT", - "RGAAv4", - "RGAA-10.4.2" - ], - "description": "Ensure does not disable text scaling and zooming", - "help": "Zooming and scaling must not be disabled", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/meta-viewport?application=axeAPI", - "nodes": [ - { - "any": [ - { - "id": "meta-viewport", - "data": null, - "relatedNodes": [], - "impact": "moderate", - "message": " tag does not disable zooming on mobile devices" - } - ], - "all": [], - "none": [], - "impact": null, - "html": "", - "target": [ - "meta[name=\"viewport\"]" - ] - } - ] - } - ], - "incomplete": [ - { - "id": "color-contrast", - "impact": "serious", - "tags": [ - "cat.color", - "wcag2aa", - "wcag143", - "TTv5", - "TT13.c", - "EN-301-549", - "EN-9.1.4.3", - "ACT", - "RGAAv4", - "RGAA-3.2.1" - ], - "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", - "help": "Elements must meet minimum color contrast ratio thresholds", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast?application=axeAPI", - "nodes": [ - { - "any": [ - { - "id": "color-contrast", - "data": { - "contrastRatio": 0, - "fontSize": "15.0pt (20px)", - "fontWeight": "bold", - "messageKey": "bgGradient", - "expectedContrastRatio": "3:1" - }, - "relatedNodes": [ - { - "html": "Adamu Abubakar", - "target": [ - "span" - ] - } - ], - "impact": "serious", - "message": "Element's background color could not be determined due to a background gradient" - } - ], - "all": [], - "none": [], - "impact": "serious", - "html": "Adamu Abubakar", - "target": [ - "span" - ], - "failureSummary": "Fix any of the following:\n Element's background color could not be determined due to a background gradient" - } - ] - } - ], - "violations": [] -} \ No newline at end of file diff --git a/index.html b/index.html index 4ce5926..e39bf55 100644 --- a/index.html +++ b/index.html @@ -3,52 +3,56 @@ - - + + - - - - + + + + - -