Compare commits
15 Commits
eda8e322a8
...
v2.3.22-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| ad3708e29e | |||
| 8b0cd6028b | |||
| 94627ba20e | |||
| a782fdfe53 | |||
| f17a8c8c36 | |||
| adbb2419b7 | |||
| 5809291741 | |||
| e263e7f25c | |||
| 1fee592ee7 | |||
| ff73aa7c4e | |||
| 26f6ddc098 | |||
| db48e03cb9 | |||
| 53941cdf01 | |||
| fa71e00f69 | |||
| afd4a73a05 |
@@ -42,10 +42,3 @@ jobs:
|
||||
|
||||
- name: 🏗️ Build
|
||||
run: pnpm build
|
||||
|
||||
- name: ♿ Accessibility Check
|
||||
run: pnpm start-server-and-test start http://localhost:3000 "pnpm check:a11y http://localhost:3000"
|
||||
|
||||
- name: ♿ WCAG Sitemap Audit
|
||||
run: pnpm start-server-and-test start http://localhost:3000 "pnpm run check:wcag http://localhost:3000"
|
||||
# monitor trigger
|
||||
|
||||
@@ -394,65 +394,20 @@ jobs:
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 📦 Cache APT Packages
|
||||
uses: actions/cache@v4
|
||||
- name: Setup Chrome
|
||||
id: setup-chrome
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-cache-${{ runner.os }}-${{ runner.arch }}-chromium
|
||||
install-dependencies: true
|
||||
|
||||
- name: 💾 Cache Chromium
|
||||
id: cache-chromium
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/bin/chromium
|
||||
key: ${{ runner.os }}-chromium-native-${{ hashFiles('package.json') }}
|
||||
|
||||
- name: 🔍 Install Chromium (Native & ARM64)
|
||||
if: steps.cache-chromium.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||
apt-get update
|
||||
apt-get install -y gnupg wget ca-certificates
|
||||
OS_ID=$(. /etc/os-release && echo $ID)
|
||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
||||
if [ "$OS_ID" = "debian" ]; then
|
||||
apt-get install -y chromium
|
||||
else
|
||||
mkdir -p /etc/apt/keyrings
|
||||
KEY_ID="82BB6851C64F6880"
|
||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_ID" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
||||
apt-get update
|
||||
apt-get install -y --allow-downgrades chromium
|
||||
fi
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser
|
||||
|
||||
|
||||
|
||||
- name: 🚀 OG Image Check
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
run: pnpm run check:og
|
||||
- name: 🌐 Core Smoke Tests (HTTP, API, Locale)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
uses: https://git.infra.mintel.me/mmintel/at-mintel/.gitea/actions/core-smoke-tests@main
|
||||
with:
|
||||
TARGET_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}
|
||||
|
||||
- name: 📝 E2E Form Submission Test
|
||||
# ── Minimalist Smoke Test ──────────────────────────────────────────
|
||||
- name: 🌐 Smoke Test (Essential Flow)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET || 'you-need-to-set-a-payload-secret' }}
|
||||
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
||||
run: pnpm run check:forms
|
||||
PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
run: npx tsx scripts/smoke.ts
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# JOB 7: Notifications
|
||||
|
||||
@@ -10,11 +10,8 @@ env:
|
||||
PROJECT_NAME: 'klz-2026'
|
||||
|
||||
jobs:
|
||||
# ────────────────────────────────────────────────────
|
||||
# 1. Static Checks (HTML, Assets, HTTP)
|
||||
# ────────────────────────────────────────────────────
|
||||
static:
|
||||
name: 🔍 Static Analysis
|
||||
smoke:
|
||||
name: 💨 Smoke & Health
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -38,199 +35,23 @@ jobs:
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: 🌐 HTML Validation
|
||||
- name: 🚀 Run Smoke Test
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:html
|
||||
- name: 🖼️ Broken Assets
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
ASSET_CHECK_LIMIT: 10
|
||||
run: pnpm run check:assets
|
||||
- name: 🔒 HTTP Headers
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:http
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 2. Accessibility (WCAG)
|
||||
# ────────────────────────────────────────────────────
|
||||
a11y:
|
||||
name: ♿ Accessibility
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
||||
run: npx tsx scripts/smoke.ts
|
||||
- name: 🔔 Notify
|
||||
if: always()
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: ♿ WCAG Scan
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:wcag
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 3. Performance (Lighthouse)
|
||||
# ────────────────────────────────────────────────────
|
||||
lighthouse:
|
||||
name: 🎭 Lighthouse
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 🌐 Install Chrome & Dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
|
||||
npx puppeteer browsers install chrome
|
||||
- name: 🎭 Desktop
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
PAGESPEED_LIMIT: 5
|
||||
run: pnpm run pagespeed:test -- --collect.settings.preset=desktop
|
||||
- name: 📱 Mobile
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
PAGESPEED_LIMIT: 5
|
||||
run: pnpm run pagespeed:test -- --collect.settings.preset=mobile
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 4. Link Check & Dependency Audit
|
||||
# ────────────────────────────────────────────────────
|
||||
links:
|
||||
name: 🔗 Links & Deps
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
|
||||
- name: 📦 Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
id: cache-deps
|
||||
with:
|
||||
path: node_modules
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
- name: 📦 Depcheck
|
||||
continue-on-error: true
|
||||
run: pnpm dlx depcheck --ignores="*eslint*,*typescript*,*tailwindcss*,*postcss*,*prettier*,*@types/*,*husky*,*lint-staged*,*@next/*,*@lhci/*,*commitlint*,*cspell*,*rimraf*,*@payloadcms/*,*start-server-and-test*,*html-validate*,*critters*,*dotenv*,*turbo*" || true
|
||||
- name: 🔗 Lychee Link Check
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
args: --accept 200,204,429 --timeout 15 --insecure --exclude "file://*" --exclude "https://logs.infra.***.me/*" --exclude "https://git.infra.***.me/*" --exclude "https://umami.is/docs/best-practices" --exclude "https://***/*" .
|
||||
fail: true
|
||||
|
||||
# ────────────────────────────────────────────────────
|
||||
# 5. Notification
|
||||
# ────────────────────────────────────────────────────
|
||||
notify:
|
||||
name: 🔔 Notify
|
||||
needs: [static, a11y, lighthouse, links]
|
||||
if: always()
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: 🔔 Gotify
|
||||
shell: bash
|
||||
run: |
|
||||
STATIC="${{ needs.static.result }}"
|
||||
A11Y="${{ needs.a11y.result }}"
|
||||
LIGHTHOUSE="${{ needs.lighthouse.result }}"
|
||||
LINKS="${{ needs.links.result }}"
|
||||
|
||||
if [[ "$STATIC" != "success" || "$LIGHTHOUSE" != "success" ]]; then
|
||||
PRIORITY=8
|
||||
EMOJI="🚨"
|
||||
STATUS="Failed"
|
||||
else
|
||||
PRIORITY=2
|
||||
EMOJI="✅"
|
||||
STATUS="Passed"
|
||||
fi
|
||||
|
||||
TITLE="$EMOJI ${{ env.PROJECT_NAME }} QA $STATUS"
|
||||
MESSAGE="Static: $STATIC | A11y: $A11Y | Lighthouse: $LIGHTHOUSE | Links: $LINKS
|
||||
${{ env.TARGET_URL }}"
|
||||
|
||||
if [[ -z "${{ secrets.GOTIFY_URL }}" || -z "${{ secrets.GOTIFY_TOKEN }}" ]]; then
|
||||
echo "⚠️ Gotify credentials missing, skipping notification."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
STATUS="${{ job.status }}"
|
||||
TITLE="💨 ${{ env.PROJECT_NAME }} Nightly Smoke $STATUS"
|
||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" || true
|
||||
-F "message=Status: $STATUS | URL: ${{ env.TARGET_URL }}" \
|
||||
-F "priority=5" || true
|
||||
|
||||
5
.gitignore
vendored
@@ -2,10 +2,7 @@ node_modules
|
||||
.next
|
||||
.DS_Store
|
||||
.pnpm-store
|
||||
public/uploads
|
||||
public/media
|
||||
|
||||
# Lighthouse CI
|
||||
# lighthouse CI
|
||||
.lighthouseci/
|
||||
lighthouserc.cjs
|
||||
.lighthouserc.json
|
||||
|
||||
23
Dockerfile
@@ -56,15 +56,16 @@ ENV UV_THREADPOOL_SIZE=3
|
||||
RUN pnpm build
|
||||
|
||||
# Stage 2: Runner
|
||||
FROM node:20-alpine AS runner
|
||||
FROM node:20-slim AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Install curl for health checks
|
||||
RUN apk add --no-cache curl
|
||||
# Install curl for health checks and procps
|
||||
RUN apt-get update && apt-get install -y curl procps && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create nextjs user and group (standardized in runtime image but ensuring local ownership)
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs && \
|
||||
# Create nextjs user and group
|
||||
RUN groupadd --system --gid 1001 nodejs && \
|
||||
useradd --system --uid 1001 nextjs && \
|
||||
mkdir -p .next/cache/images && \
|
||||
chown -R nextjs:nodejs /app
|
||||
|
||||
USER nextjs
|
||||
@@ -72,11 +73,15 @@ USER nextjs
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENV PORT=3000
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_SHARP_PATH=/app/node_modules/sharp
|
||||
|
||||
# Copy standalone output and static files
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
# Copy standalone output
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
# Copy public and static files - standalone expects them in specific locations
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
|
||||
|
||||
# Ensure cache directory is writable for the nextjs user
|
||||
# (Already handled by chown -R above, but explicit is better)
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
@@ -45,6 +45,28 @@ export async function sendContactFormAction(formData: FormData) {
|
||||
email,
|
||||
});
|
||||
|
||||
// 1.5. Simple Fail-Safe Backup to Disk
|
||||
// To ensure leads are never lost if email fails or Gotify is down, we append them to a local JSON Lines file.
|
||||
try {
|
||||
const fs = await import('fs');
|
||||
const path = await import('path');
|
||||
const backupDir = path.join(process.cwd(), '.data');
|
||||
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
|
||||
|
||||
const backupFile = path.join(backupDir, 'leads-backup.jsonl');
|
||||
const leadData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
name,
|
||||
email,
|
||||
productName,
|
||||
message,
|
||||
};
|
||||
fs.appendFileSync(backupFile, JSON.stringify(leadData) + '\\n');
|
||||
logger.info('Successfully saved lead to local backup file', { backupFile });
|
||||
} catch (backupError) {
|
||||
logger.error('Failed to write to local leads backup', { error: String(backupError) });
|
||||
}
|
||||
|
||||
// 2. Send Emails
|
||||
logger.info('Sending branded emails', { email, productName });
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ services:
|
||||
environment:
|
||||
POSTGRES_URI: postgres://${PAYLOAD_DB_USER:-payload}:${PAYLOAD_DB_PASSWORD:-payload}@klz-db:5432/${PAYLOAD_DB_NAME:-payload}
|
||||
PAYLOAD_SECRET: ${PAYLOAD_SECRET:-fallback-secret-for-production-needs-change}
|
||||
volumes:
|
||||
- klz_media_data:/app/public/media
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# HTTP ⇒ HTTPS redirect
|
||||
|
||||
@@ -26,6 +26,8 @@ export default async function middleware(request: NextRequest) {
|
||||
pathname.startsWith('/errors') ||
|
||||
pathname.startsWith('/health') ||
|
||||
pathname.startsWith('/uploads') ||
|
||||
pathname.startsWith('/media') ||
|
||||
pathname.startsWith('/_next/image') ||
|
||||
pathname.includes('/api/og') ||
|
||||
pathname.includes('opengraph-image') ||
|
||||
pathname.endsWith('sitemap.xml') ||
|
||||
@@ -61,7 +63,7 @@ export default async function middleware(request: NextRequest) {
|
||||
if (['GET', 'HEAD'].includes(request.method)) {
|
||||
// Clone headers to ensure all Next.js internal headers (like router state) are preserved
|
||||
const clonedHeaders = new Headers(request.headers);
|
||||
|
||||
|
||||
effectiveRequest = new NextRequest(urlObj, {
|
||||
headers: clonedHeaders,
|
||||
method: request.method,
|
||||
|
||||
@@ -403,7 +403,7 @@ const nextConfig = {
|
||||
},
|
||||
images: {
|
||||
formats: ['image/webp'],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
|
||||
14
package.json
@@ -95,18 +95,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:og": "vitest run tests/og-image.test.ts",
|
||||
"check:og": "tsx scripts/check-og-images.ts",
|
||||
"check:a11y": "pa11y-ci",
|
||||
"check:wcag": "tsx ./scripts/wcag-sitemap.ts",
|
||||
"check:html": "tsx ./scripts/check-html.ts",
|
||||
"check:http": "tsx ./scripts/check-http.ts",
|
||||
"check:locale": "tsx ./scripts/check-locale.ts",
|
||||
"check:smoke": "tsx scripts/smoke.ts",
|
||||
"check:spell": "cspell \"content/**/*.{md,mdx}\" \"app/**/*.tsx\" \"components/**/*.tsx\"",
|
||||
"check:security": "tsx ./scripts/check-security.ts",
|
||||
"check:links": "bash ./scripts/check-links.sh",
|
||||
"check:assets": "tsx ./scripts/check-broken-assets.ts",
|
||||
"check:forms": "tsx ./scripts/check-forms.ts",
|
||||
"check:apis": "tsx ./scripts/check-apis.ts",
|
||||
"pdf:datasheets": "tsx ./scripts/generate-pdf-datasheets.ts",
|
||||
"pdf:datasheets:legacy": "tsx ./scripts/generate-pdf-datasheets-pdf-lib.ts",
|
||||
"assets:push:testing": "bash ./scripts/assets-sync.sh local testing",
|
||||
@@ -123,7 +113,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.3.22-rc.6",
|
||||
"version": "2.3.22-rc.16",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
BIN
public/media/1-1.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/1-2.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/1-3.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/1-4-1024x576.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/media/1-4-600x338.webp
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/media/1-4-768x432.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/media/1-4.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/media/1-5-1024x576.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/media/1-5-600x338.webp
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/media/1-5-768x432.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/media/1-5.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/media/1.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/1234adws21312-scaled-1024x768.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/media/1234adws21312-scaled-600x450.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/media/1234adws21312-scaled-768x576.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/media/1234adws21312-scaled.jpg
Normal file
|
After Width: | Height: | Size: 668 KiB |
BIN
public/media/1694273920124-copy-1.webp
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
public/media/1694273920124-copy-2-1.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
public/media/1694273920124-copy-2-2.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
public/media/1694273920124-copy-2-3.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
public/media/1694273920124-copy-2.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
public/media/1694273920124-copy-3.webp
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
public/media/1694273920124-copy.webp
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
public/media/1759325528650-1.webp
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
public/media/1759325528650-2.webp
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
public/media/1759325528650-3.webp
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
public/media/1759325528650-4-1024x769.webp
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/media/1759325528650-4-600x450.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/media/1759325528650-4-768x577.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/media/1759325528650-4.webp
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
public/media/1759325528650-5-1024x769.webp
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/media/1759325528650-5-600x450.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/media/1759325528650-5-768x577.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/media/1759325528650-5.webp
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
public/media/1759325528650.webp
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
public/media/3-1.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/media/3-2.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/media/3-3.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/media/3.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/media/5-1.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/media/5-2.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/media/5-3.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/media/5-4-1024x576.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/media/5-4-600x338.webp
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
public/media/5-4-768x432.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/media/5-4.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/media/5-5-1024x576.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/media/5-5-600x338.webp
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
public/media/5-5-768x432.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/media/5-5.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/media/5.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/media/AGB KLZ 3-2026.pdf
Normal file
BIN
public/media/AGB-KLZ-1-2026.pdf
Normal file
BIN
public/media/AVB-KLZ-4-2026.pdf
Normal file
BIN
public/media/DSC07387-Large-600x400-1.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/media/DSC07387-Large-600x400-2.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/media/DSC07387-Large-600x400-3.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/media/DSC07387-Large-600x400.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/media/DSC07433-Large-600x400-1.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/media/DSC07433-Large-600x400-2.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/media/DSC07433-Large-600x400-3.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/media/DSC07433-Large-600x400.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/media/DSC07460-Large-600x400-1.webp
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/media/DSC07460-Large-600x400-2.webp
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/media/DSC07460-Large-600x400-3.webp
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/media/DSC07460-Large-600x400.webp
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/media/DSC07469-Large-600x400-1.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/DSC07469-Large-600x400-2.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/DSC07469-Large-600x400-3.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/DSC07469-Large-600x400.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/media/DSC07539-Large-600x400-1.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/media/DSC07539-Large-600x400-2.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/media/DSC07539-Large-600x400-3.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/media/DSC07539-Large-600x400.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/media/DSC07655-Large-1.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/media/DSC07655-Large-2.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/media/DSC07655-Large-3.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/media/DSC07655-Large.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
public/media/DSC07768-Large-1.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/DSC07768-Large-2.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/DSC07768-Large-3.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/DSC07768-Large.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/media/DSC07963-Large-1.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/media/DSC07963-Large-2.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/media/DSC07963-Large-3.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/media/DSC07963-Large.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/media/DSC08036-Large-1.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/media/DSC08036-Large-2.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/media/DSC08036-Large-3.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/media/DSC08036-Large.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |