fix: resolve hreflang mismatched products/contact slugs, fix pipeline check short-circuiting, fix MDX parser HTML+Markdown lists overlapping
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 39s
Build & Deploy / 🧪 QA (push) Successful in 2m16s
Build & Deploy / 🏗️ Build (push) Successful in 6m9s
Build & Deploy / 🚀 Deploy (push) Successful in 22s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m38s
Build & Deploy / ⚡ Performance & Accessibility (push) Successful in 5m55s
Build & Deploy / 🔔 Notify (push) Successful in 2s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 39s
Build & Deploy / 🧪 QA (push) Successful in 2m16s
Build & Deploy / 🏗️ Build (push) Successful in 6m9s
Build & Deploy / 🚀 Deploy (push) Successful in 22s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m38s
Build & Deploy / ⚡ Performance & Accessibility (push) Successful in 5m55s
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
@@ -427,19 +427,23 @@ jobs:
|
||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
||||
echo "//${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}/:_authToken=${{ secrets.REGISTRY_PASS }}" >> .npmrc
|
||||
- name: Install dependencies
|
||||
id: deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# ── Critical Smoke Tests (MUST pass) ──────────────────────────────────
|
||||
- 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: 🌐 Full Sitemap HTTP Validation
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
run: pnpm run check:http
|
||||
- name: 🌐 Locale & Language Switcher Validation
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
@@ -447,24 +451,28 @@ jobs:
|
||||
|
||||
# ── Quality Gates (informational, don't block pipeline) ───────────────
|
||||
- name: 🌐 HTML DOM Validation
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
run: pnpm check:html
|
||||
- name: 🔒 Security Headers Scan
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
run: pnpm check:security
|
||||
- name: 🔗 Lychee Deep Link Crawl
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||
run: pnpm check:links
|
||||
- name: 🖼️ Dynamic Asset & Image Integrity Scan
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
|
||||
@@ -24,9 +24,9 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/contact`,
|
||||
canonical: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
||||
languages: {
|
||||
de: `${SITE_URL}/de/contact`,
|
||||
de: `${SITE_URL}/de/kontakt`,
|
||||
en: `${SITE_URL}/en/contact`,
|
||||
'x-default': `${SITE_URL}/en/contact`,
|
||||
},
|
||||
@@ -34,7 +34,7 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
|
||||
openGraph: {
|
||||
title: `${title} | KLZ Cables`,
|
||||
description,
|
||||
url: `${SITE_URL}/${locale}/contact`,
|
||||
url: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
||||
siteName: 'KLZ Cables',
|
||||
locale: `${locale.toUpperCase()}_DE`,
|
||||
type: 'website',
|
||||
|
||||
@@ -55,14 +55,23 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${productSlug}`,
|
||||
languages: {
|
||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(fileSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(fileSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(fileSlug, 'en')}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fileSlugs = await Promise.all(slug.map((s) => mapSlugToFileSlug(s, locale)));
|
||||
const getLocalizedPath = async (lang: string) => {
|
||||
const parts = await Promise.all([
|
||||
mapFileSlugToTranslated('products', lang),
|
||||
...fileSlugs.map((fs) => mapFileSlugToTranslated(fs, lang)),
|
||||
]);
|
||||
return parts.join('/');
|
||||
};
|
||||
|
||||
const product = await getProductBySlug(productSlug, locale);
|
||||
if (!product) return {};
|
||||
|
||||
@@ -72,9 +81,9 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
||||
languages: {
|
||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(slug[0], 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
de: `${SITE_URL}/de/${await getLocalizedPath('de')}`,
|
||||
en: `${SITE_URL}/en/${await getLocalizedPath('en')}`,
|
||||
'x-default': `${SITE_URL}/en/${await getLocalizedPath('en')}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
|
||||
@@ -23,28 +23,36 @@ const jsxConverters: JSXConverters = {
|
||||
// If the text node contains raw HTML (from messy migrations), render it as HTML instead of escaping it
|
||||
text: ({ node }: any) => {
|
||||
const text = node.text;
|
||||
if (text && (text.includes('<') || text.includes('data-start'))) {
|
||||
return <span dangerouslySetInnerHTML={{ __html: text }} />;
|
||||
}
|
||||
|
||||
// Handle markdown-style lists embedded in text nodes from MDX migration
|
||||
if (text && text.includes('\n- ')) {
|
||||
const parts = text.split('\n- ').filter(Boolean);
|
||||
const parts = text.split('\n- ').filter((p: string) => p.trim() !== '');
|
||||
// If first part doesn't start with "- ", it's a prefix paragraph
|
||||
const startsWithDash = text.trimStart().startsWith('- ');
|
||||
const prefix = startsWithDash ? null : parts.shift();
|
||||
return (
|
||||
<>
|
||||
{prefix && <span>{prefix}</span>}
|
||||
{prefix && (
|
||||
<span dangerouslySetInnerHTML={prefix.includes('<') ? { __html: prefix } : undefined}>
|
||||
{!prefix.includes('<') ? prefix : undefined}
|
||||
</span>
|
||||
)}
|
||||
<ul className="list-disc pl-6 my-4 space-y-2">
|
||||
{parts.map((item: string, i: number) => (
|
||||
<li key={i}>{item.trim()}</li>
|
||||
))}
|
||||
{parts.map((item: string, i: number) => {
|
||||
const cleanItem = item.trim();
|
||||
if (cleanItem.includes('<')) {
|
||||
return <li key={i} dangerouslySetInnerHTML={{ __html: cleanItem }} />;
|
||||
}
|
||||
return <li key={i}>{cleanItem}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (text && (text.includes('<') || text.includes('data-start'))) {
|
||||
return <span dangerouslySetInnerHTML={{ __html: text }} />;
|
||||
}
|
||||
|
||||
// Handle markdown-style links [text](url) from MDX migration
|
||||
if (text && /\[([^\]]+)\]\(([^)]+)\)/.test(text)) {
|
||||
const parts: React.ReactNode[] = [];
|
||||
|
||||
Reference in New Issue
Block a user