Compare commits

...

5 Commits

Author SHA1 Message Date
829c074c7f fix(ci): use bash and explicit if conditions to fix skip logic
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 20s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 1m34s
Build & Deploy KLZ Cables / 🏗️ Build & Push (push) Successful in 7m23s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 40s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 1m10s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 2s
2026-02-01 17:58:18 +01:00
9189e813b2 fix(ci): skip build and deploy jobs when target is skip (chore commits) 2026-02-01 17:53:11 +01:00
249313cc37 chore: Configure Husky, Commitlint, and Lint-staged to enforce Git commit message conventions and run pre-commit checks.
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Has been skipped
Build & Deploy KLZ Cables / 🏗️ Build & Push (push) Failing after 6s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Has been skipped
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Has been skipped
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s
2026-02-01 17:41:00 +01:00
8232971419 ci: Update Gitea deploy workflow.
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 1m33s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Has been cancelled
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Has been cancelled
Build & Deploy KLZ Cables / 🔔 Notifications (push) Has been cancelled
Build & Deploy KLZ Cables / 🏗️ Build & Push (push) Has been cancelled
2026-02-01 17:37:38 +01:00
f41260e1db feat: implement automated Lighthouse CI testing for sitemap URLs with dedicated configuration and scripts. 2026-02-01 17:35:31 +01:00
11 changed files with 4361 additions and 23 deletions

View File

@@ -1,12 +1,16 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"extends": ["next/core-web-vitals", "next/typescript", "prettier"],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-require-imports": "off",
"prefer-const": "warn",
"react/no-unescaped-entities": "off",
"@next/next/no-img-element": "warn"
}
}

View File

@@ -46,24 +46,31 @@ jobs:
- name: 🔍 Environment & Version ermitteln
id: determine
shell: bash
run: |
TAG="${{ github.ref_name }}"
SHORT_SHA="${{ github.sha }}"
SHORT_SHA="${SHORT_SHA:0:9}"
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-9)
IMAGE_TAG="sha-${SHORT_SHA}"
COMMIT_MSG=$(git log -1 --pretty=%s || echo "No commit message available")
if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then
TARGET="testing"
IMAGE_TAG="main-${SHORT_SHA}"
ENV_FILE=".env.testing"
TRAEFIK_HOST='`testing.klz-cables.com`'
NEXT_PUBLIC_BASE_URL="https://testing.klz-cables.com"
DIRECTUS_URL="https://cms.testing.klz-cables.com"
DIRECTUS_HOST='`cms.testing.klz-cables.com`'
PROJECT_NAME="klz-cables-testing"
IS_PROD="false"
GOTIFY_TITLE="🧪 Testing-Deploy"
GOTIFY_PRIORITY=4
if [[ "$COMMIT_MSG" =~ ^chore: ]]; then
TARGET="skip"
GOTIFY_TITLE=" Skip Deploy (Chore)"
GOTIFY_PRIORITY=2
else
TARGET="testing"
IMAGE_TAG="main-${SHORT_SHA}"
ENV_FILE=".env.testing"
TRAEFIK_HOST='`testing.klz-cables.com`'
NEXT_PUBLIC_BASE_URL="https://testing.klz-cables.com"
DIRECTUS_URL="https://cms.testing.klz-cables.com"
DIRECTUS_HOST='`cms.testing.klz-cables.com`'
PROJECT_NAME="klz-cables-testing"
IS_PROD="false"
GOTIFY_TITLE="🧪 Testing-Deploy"
GOTIFY_PRIORITY=4
fi
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TARGET="production"
@@ -162,6 +169,7 @@ jobs:
build:
name: 🏗️ Build & Push
needs: prepare
if: ${{ needs.prepare.outputs.target != 'skip' }}
runs-on: docker
steps:
- name: Checkout repository
@@ -210,6 +218,7 @@ jobs:
deploy:
name: 🚀 Deploy
needs: [prepare, build, qa]
if: ${{ needs.prepare.outputs.target != 'skip' }}
runs-on: docker
env:
TARGET: ${{ needs.prepare.outputs.target }}
@@ -309,11 +318,63 @@ jobs:
fi
# ──────────────────────────────────────────────────────────────────────────────
# JOB 5: Notifications
# JOB 5: PageSpeed Test
# ──────────────────────────────────────────────────────────────────────────────
pagespeed:
name: ⚡ PageSpeed
needs: [prepare, deploy]
if: |
always() &&
needs.prepare.outputs.target != 'skip' &&
needs.deploy.result == 'success' &&
github.event.inputs.skip_long_checks != 'true'
runs-on: docker
outputs:
report_url: ${{ steps.save.outputs.report_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: 🔍 Install Chrome for Lighthouse
run: |
apt-get update && apt-get install -y wget gnupg
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
apt-get update && apt-get install -y google-chrome-stable --no-install-recommends
continue-on-error: true # Might fail if runner is not debian-based or restricted
- name: 🧪 Run PageSpeed (Lighthouse)
env:
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
PAGESPEED_LIMIT: 8
run: npm run pagespeed:test
- name: 💾 Save Report URL
id: save
if: always()
run: |
if [ -f pagespeed-report-url.txt ]; then
URL=$(cat pagespeed-report-url.txt)
echo "report_url=$URL" >> $GITHUB_OUTPUT
echo "✅ Report URL found: $URL"
fi
# ──────────────────────────────────────────────────────────────────────────────
# JOB 6: Notifications
# ──────────────────────────────────────────────────────────────────────────────
notifications:
name: 🔔 Notifications
needs: [prepare, qa, build, deploy]
needs: [prepare, qa, build, deploy, pagespeed]
if: always()
runs-on: docker
steps:
@@ -332,9 +393,14 @@ jobs:
- name: 🔔 Gotify - Success
if: needs.deploy.result == 'success'
run: |
REPORT_MSG=""
if [ -n "${{ needs.pagespeed.outputs.report_url }}" ]; then
REPORT_MSG="\n\n⚡ **PageSpeed Report:**\n${{ needs.pagespeed.outputs.report_url }}"
fi
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
-F "title=${{ needs.prepare.outputs.gotify_title }}" \
-F "message=Erfolgreich deployt auf **${{ needs.prepare.outputs.target }}**\n\nVersion: **${{ needs.prepare.outputs.image_tag }}**\nCommit: ${{ needs.prepare.outputs.short_sha }} (${{ needs.prepare.outputs.commit_msg }})\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}" \
-F "message=Erfolgreich deployt auf **${{ needs.prepare.outputs.target }}**\n\nVersion: **${{ needs.prepare.outputs.image_tag }}**\nCommit: ${{ needs.prepare.outputs.short_sha }} (${{ needs.prepare.outputs.commit_msg }})\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}${REPORT_MSG}" \
-F "priority=4" || true
- name: 🔔 Gotify - Failure

1
.husky/commit-msg Executable file
View File

@@ -0,0 +1 @@
npx --no -- commitlint --edit "$1"

1
.husky/pre-commit Executable file
View File

@@ -0,0 +1 @@
npx lint-staged

11
.lintstagedrc.js Normal file
View File

@@ -0,0 +1,11 @@
const path = require('path');
const buildEslintCommand = (filenames) =>
`next lint --fix --file ${filenames
.map((f) => path.relative(process.cwd(), f))
.join(' --file ')}`;
module.exports = {
'*.{js,jsx,ts,tsx}': [buildEslintCommand, 'prettier --write'],
'*.{json,md,css,scss}': ['prettier --write'],
};

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100
}

8
commitlint.config.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'header-max-length': [2, 'always', 150],
'subject-case': [0],
'subject-full-stop': [0],
},
};

22
lighthouserc.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = {
ci: {
collect: {
numberOfRuns: 1,
settings: {
preset: 'desktop',
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
},
},
assert: {
assertions: {
'categories:performance': ['warn', { minScore: 0.9 }],
'categories:accessibility': ['warn', { minScore: 0.9 }],
'categories:best-practices': ['warn', { minScore: 0.9 }],
'categories:seo': ['warn', { minScore: 0.9 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};

4097
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,9 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@commitlint/cli": "^20.4.0",
"@commitlint/config-conventional": "^20.4.0",
"@lhci/cli": "^0.15.1",
"@tailwindcss/cli": "^4.1.18",
"@tailwindcss/postcss": "^4.1.18",
"@types/node": "^22.19.3",
@@ -48,7 +51,11 @@
"autoprefixer": "^10.4.23",
"eslint": "^8.57.1",
"eslint-config-next": "14.2.35",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"postcss": "^8.5.6",
"prettier": "^3.8.1",
"sass": "^1.97.1",
"tailwindcss": "^4.1.18",
"tsx": "^4.21.0",
@@ -74,7 +81,10 @@
"directus:push:testing": "./scripts/sync-directus.sh push testing",
"directus:pull:testing": "./scripts/sync-directus.sh pull testing",
"directus:push:prod": "./scripts/sync-directus.sh push production",
"directus:pull:prod": "./scripts/sync-directus.sh pull production"
"directus:pull:prod": "./scripts/sync-directus.sh pull production",
"pagespeed:test": "tsx ./scripts/pagespeed-sitemap.ts",
"pagespeed:urls": "tsx -e \"import sitemap from './app/sitemap'; sitemap().then(urls => console.log(urls.map(u => u.url).join('\\n')))\"",
"prepare": "husky"
},
"version": "1.0.0"
}
}

View File

@@ -0,0 +1,115 @@
import axios from 'axios';
import * as cheerio from 'cheerio';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
/**
* PageSpeed Test Script
*
* 1. Fetches sitemap.xml from the target URL
* 2. Extracts all URLs
* 3. Runs Lighthouse CI on those URLs
*/
const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'https://testing.klz-cables.com';
const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20; // Default limit to avoid infinite runs
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
async function main() {
console.log(`\n🚀 Starting PageSpeed test for: ${targetUrl}`);
console.log(`📊 Limit: ${limit} pages\n`);
try {
// 1. Fetch Sitemap
const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`;
console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
// We might need to bypass gatekeeper for the sitemap fetch too
const response = await axios.get(sitemapUrl, {
headers: {
'Cookie': `klz_gatekeeper_session=${gatekeeperPassword}`
},
validateStatus: (status) => status < 400
});
const $ = cheerio.load(response.data, { xmlMode: true });
let urls = $('url loc').map((i, el) => $(el).text()).get();
// Cleanup and filter
urls = [...new Set(urls)].filter(u => u.startsWith('http')).sort();
console.log(`✅ Found ${urls.length} URLs in sitemap.`);
if (urls.length === 0) {
console.error('❌ No URLs found in sitemap. Is the site up?');
process.exit(1);
}
if (urls.length > limit) {
console.log(`⚠️ Too many pages (${urls.length}). Limiting to ${limit} representative pages.`);
// Try to pick a variety: home, some products, some blog posts
const home = urls.filter(u => u.endsWith('/de') || u.endsWith('/en') || u === targetUrl);
const others = urls.filter(u => !home.includes(u));
urls = [...home, ...others.slice(0, limit - home.length)];
}
console.log(`🧪 Pages to be tested:`);
urls.forEach(u => console.log(` - ${u}`));
// 2. Prepare LHCI command
// We use --collect.url multiple times
const urlArgs = urls.map(u => `--collect.url="${u}"`).join(' ');
// Handle authentication for staging/testing
// Lighthouse can set cookies via --collect.settings.extraHeaders
const extraHeaders = JSON.stringify({
'Cookie': `klz_gatekeeper_session=${gatekeeperPassword}`
});
console.log(`\n🏗 Running Lighthouse CI...`);
// Using a more robust way to execute and capture output
const lhciCommand = `npx lhci collect ${urlArgs} --collect.settings.chromeFlags='--no-sandbox --disable-setuid-sandbox' --collect.settings.extraHeaders='${extraHeaders}' && npx lhci assert && npx lhci upload`;
console.log(`💻 Executing LHCI...`);
try {
const output = execSync(lhciCommand, {
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'inherit'] // Pipe stdout so we can parse it
});
console.log(output);
// Extract report URL from LHCI output
const reportMatch = output.match(/Sent to (https:\/\/storage\.googleapis\.com\/lighthouse-infrastructure\.appspot\.com\/reports\/[^\s]+)/);
if (reportMatch && reportMatch[1]) {
const reportUrl = reportMatch[1];
console.log(`\n📊 Report URL: ${reportUrl}`);
fs.writeFileSync('pagespeed-report-url.txt', reportUrl);
}
} catch (err: any) {
console.error('❌ LHCI execution failed.');
if (err.stdout) console.log(err.stdout);
if (err.stderr) console.error(err.stderr);
throw err;
}
console.log(`\n✨ PageSpeed tests completed successfully!`);
} catch (error: any) {
console.error(`\n❌ Error during PageSpeed test:`);
if (axios.isAxiosError(error)) {
console.error(`Status: ${error.response?.status}`);
console.error(`StatusText: ${error.response?.statusText}`);
console.error(`URL: ${error.config?.url}`);
} else {
console.error(error.message);
}
process.exit(1);
}
}
main();