Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44d3e8585b | |||
| 5652f27c71 | |||
| c769da5f26 |
@@ -427,19 +427,23 @@ jobs:
|
|||||||
echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
|
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
|
echo "//${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}/:_authToken=${{ secrets.REGISTRY_PASS }}" >> .npmrc
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
id: deps
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# ── Critical Smoke Tests (MUST pass) ──────────────────────────────────
|
# ── Critical Smoke Tests (MUST pass) ──────────────────────────────────
|
||||||
- name: 🚀 OG Image Check
|
- name: 🚀 OG Image Check
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
env:
|
env:
|
||||||
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
run: pnpm run check:og
|
run: pnpm run check:og
|
||||||
- name: 🌐 Full Sitemap HTTP Validation
|
- name: 🌐 Full Sitemap HTTP Validation
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
run: pnpm run check:http
|
run: pnpm run check:http
|
||||||
- name: 🌐 Locale & Language Switcher Validation
|
- name: 🌐 Locale & Language Switcher Validation
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
@@ -447,24 +451,28 @@ jobs:
|
|||||||
|
|
||||||
# ── Quality Gates (informational, don't block pipeline) ───────────────
|
# ── Quality Gates (informational, don't block pipeline) ───────────────
|
||||||
- name: 🌐 HTML DOM Validation
|
- name: 🌐 HTML DOM Validation
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
run: pnpm check:html
|
run: pnpm check:html
|
||||||
- name: 🔒 Security Headers Scan
|
- name: 🔒 Security Headers Scan
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
run: pnpm check:security
|
run: pnpm check:security
|
||||||
- name: 🔗 Lychee Deep Link Crawl
|
- name: 🔗 Lychee Deep Link Crawl
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
run: pnpm check:links
|
run: pnpm check:links
|
||||||
- name: 🖼️ Dynamic Asset & Image Integrity Scan
|
- name: 🖼️ Dynamic Asset & Image Integrity Scan
|
||||||
|
if: always() && steps.deps.outcome == 'success'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
@@ -561,12 +569,42 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: 🔔 Gotify
|
- name: 🔔 Gotify
|
||||||
run: |
|
run: |
|
||||||
STATUS="${{ needs.deploy.result }}"
|
DEPLOY="${{ needs.deploy.result }}"
|
||||||
SMOKE="${{ needs.post_deploy_checks.result }}"
|
SMOKE="${{ needs.post_deploy_checks.result }}"
|
||||||
TITLE="klz-cables.com: deploy=$STATUS smoke=$SMOKE"
|
PERF="${{ needs.performance.result }}"
|
||||||
[[ "$STATUS" == "success" && "$SMOKE" == "success" ]] && PRIORITY=5 || PRIORITY=8
|
TARGET="${{ needs.prepare.outputs.target }}"
|
||||||
|
VERSION="${{ needs.prepare.outputs.image_tag }}"
|
||||||
|
URL="${{ needs.prepare.outputs.next_public_url }}"
|
||||||
|
|
||||||
|
# Gotify priority scale:
|
||||||
|
# 1-3 = low (silent/info)
|
||||||
|
# 4-5 = normal
|
||||||
|
# 6-7 = high (warning)
|
||||||
|
# 8-10 = critical (alarm)
|
||||||
|
if [[ "$DEPLOY" != "success" ]]; then
|
||||||
|
PRIORITY=10
|
||||||
|
EMOJI="🚨"
|
||||||
|
STATUS_LINE="DEPLOY FAILED"
|
||||||
|
elif [[ "$SMOKE" != "success" ]]; then
|
||||||
|
PRIORITY=8
|
||||||
|
EMOJI="⚠️"
|
||||||
|
STATUS_LINE="Smoke tests failed"
|
||||||
|
elif [[ "$PERF" != "success" ]]; then
|
||||||
|
PRIORITY=5
|
||||||
|
EMOJI="📉"
|
||||||
|
STATUS_LINE="Performance degraded"
|
||||||
|
else
|
||||||
|
PRIORITY=2
|
||||||
|
EMOJI="✅"
|
||||||
|
STATUS_LINE="All checks passed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TITLE="$EMOJI klz-cables.com $VERSION → $TARGET"
|
||||||
|
MESSAGE="$STATUS_LINE
|
||||||
|
Deploy: $DEPLOY | Smoke: $SMOKE | Perf: $PERF
|
||||||
|
$URL"
|
||||||
|
|
||||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||||
-F "title=$TITLE" \
|
-F "title=$TITLE" \
|
||||||
-F "message=Deploy to ${{ needs.prepare.outputs.target }} finished.\nDeploy: $STATUS | Smoke: $SMOKE\nVersion: ${{ needs.prepare.outputs.image_tag }}" \
|
-F "message=$MESSAGE" \
|
||||||
-F "priority=$PRIORITY" || true
|
-F "priority=$PRIORITY" || true
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: `${SITE_URL}/${locale}/contact`,
|
canonical: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
||||||
languages: {
|
languages: {
|
||||||
de: `${SITE_URL}/de/contact`,
|
de: `${SITE_URL}/de/kontakt`,
|
||||||
en: `${SITE_URL}/en/contact`,
|
en: `${SITE_URL}/en/contact`,
|
||||||
'x-default': `${SITE_URL}/en/contact`,
|
'x-default': `${SITE_URL}/en/contact`,
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
title: `${title} | KLZ Cables`,
|
title: `${title} | KLZ Cables`,
|
||||||
description,
|
description,
|
||||||
url: `${SITE_URL}/${locale}/contact`,
|
url: `${SITE_URL}/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`,
|
||||||
siteName: 'KLZ Cables',
|
siteName: 'KLZ Cables',
|
||||||
locale: `${locale.toUpperCase()}_DE`,
|
locale: `${locale.toUpperCase()}_DE`,
|
||||||
type: 'website',
|
type: 'website',
|
||||||
|
|||||||
@@ -55,14 +55,23 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
|||||||
alternates: {
|
alternates: {
|
||||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${productSlug}`,
|
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${productSlug}`,
|
||||||
languages: {
|
languages: {
|
||||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(fileSlug, 'de')}`,
|
||||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(fileSlug, 'en')}`,
|
||||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, '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);
|
const product = await getProductBySlug(productSlug, locale);
|
||||||
if (!product) return {};
|
if (!product) return {};
|
||||||
|
|
||||||
@@ -72,9 +81,9 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
|||||||
alternates: {
|
alternates: {
|
||||||
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
canonical: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
|
||||||
languages: {
|
languages: {
|
||||||
de: `${SITE_URL}/de/${await mapFileSlugToTranslated('products', 'de')}/${await mapFileSlugToTranslated(slug[0], 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
de: `${SITE_URL}/de/${await getLocalizedPath('de')}`,
|
||||||
en: `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
en: `${SITE_URL}/en/${await getLocalizedPath('en')}`,
|
||||||
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
'x-default': `${SITE_URL}/en/${await getLocalizedPath('en')}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import { getAllPostsMetadata } from '@/lib/blog';
|
|||||||
import { getAllPagesMetadata } from '@/lib/pages';
|
import { getAllPagesMetadata } from '@/lib/pages';
|
||||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||||
|
|
||||||
export const revalidate = 3600; // Revalidate every hour
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
const baseUrl = process.env.CI
|
const baseUrl = config.baseUrl || 'https://klz-cables.com';
|
||||||
? 'http://klz.localhost'
|
|
||||||
: config.baseUrl || 'https://klz-cables.com';
|
|
||||||
const locales = ['de', 'en'];
|
const locales = ['de', 'en'];
|
||||||
|
|
||||||
const sitemapEntries: MetadataRoute.Sitemap = [];
|
const sitemapEntries: MetadataRoute.Sitemap = [];
|
||||||
|
|||||||
@@ -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
|
// If the text node contains raw HTML (from messy migrations), render it as HTML instead of escaping it
|
||||||
text: ({ node }: any) => {
|
text: ({ node }: any) => {
|
||||||
const text = node.text;
|
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
|
// Handle markdown-style lists embedded in text nodes from MDX migration
|
||||||
if (text && text.includes('\n- ')) {
|
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
|
// If first part doesn't start with "- ", it's a prefix paragraph
|
||||||
const startsWithDash = text.trimStart().startsWith('- ');
|
const startsWithDash = text.trimStart().startsWith('- ');
|
||||||
const prefix = startsWithDash ? null : parts.shift();
|
const prefix = startsWithDash ? null : parts.shift();
|
||||||
return (
|
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">
|
<ul className="list-disc pl-6 my-4 space-y-2">
|
||||||
{parts.map((item: string, i: number) => (
|
{parts.map((item: string, i: number) => {
|
||||||
<li key={i}>{item.trim()}</li>
|
const cleanItem = item.trim();
|
||||||
))}
|
if (cleanItem.includes('<')) {
|
||||||
|
return <li key={i} dangerouslySetInnerHTML={{ __html: cleanItem }} />;
|
||||||
|
}
|
||||||
|
return <li key={i}>{cleanItem}</li>;
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text && (text.includes('<') || text.includes('data-start'))) {
|
||||||
|
return <span dangerouslySetInnerHTML={{ __html: text }} />;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle markdown-style links [text](url) from MDX migration
|
// Handle markdown-style links [text](url) from MDX migration
|
||||||
if (text && /\[([^\]]+)\]\(([^)]+)\)/.test(text)) {
|
if (text && /\[([^\]]+)\]\(([^)]+)\)/.test(text)) {
|
||||||
const parts: React.ReactNode[] = [];
|
const parts: React.ReactNode[] = [];
|
||||||
|
|||||||
47
debug-sitemap.ts
Normal file
47
debug-sitemap.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
console.log('DEBUG SCRIPT STARTING...');
|
||||||
|
|
||||||
|
async function debug() {
|
||||||
|
console.log('Importing dependencies...');
|
||||||
|
try {
|
||||||
|
const { getAllProductsMetadata } = await import('./lib/mdx');
|
||||||
|
const { getAllPostsMetadata } = await import('./lib/blog');
|
||||||
|
const { getAllPagesMetadata } = await import('./lib/pages');
|
||||||
|
|
||||||
|
console.log('Dependencies imported.');
|
||||||
|
|
||||||
|
const locales = ['de', 'en'];
|
||||||
|
for (const locale of locales) {
|
||||||
|
console.log(`--- Locale: ${locale} ---`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const products = await getAllProductsMetadata(locale);
|
||||||
|
console.log(`Products (${locale}): ${products.length}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to get products for ${locale}:`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const posts = await getAllPostsMetadata(locale);
|
||||||
|
console.log(`Posts (${locale}): ${posts.length}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to get posts for ${locale}:`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pages = await getAllPagesMetadata(locale);
|
||||||
|
console.log(`Pages (${locale}): ${pages.length}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to get pages for ${locale}:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Debug failed during setup/imports:', err);
|
||||||
|
}
|
||||||
|
console.log('DEBUG SCRIPT FINISHED.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug().catch((err) => {
|
||||||
|
console.error('Unhandled retransmission error in debug():', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -123,6 +123,8 @@ export async function getAllPosts(locale: string): Promise<PostMdx[]> {
|
|||||||
limit: 100,
|
limit: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`[Payload] getAllPosts for ${locale}: Found ${docs.length} docs`);
|
||||||
|
|
||||||
return docs.map((doc) => {
|
return docs.map((doc) => {
|
||||||
return {
|
return {
|
||||||
slug: doc.slug,
|
slug: doc.slug,
|
||||||
|
|||||||
59
lib/mdx.ts
59
lib/mdx.ts
@@ -186,35 +186,30 @@ export async function getAllProducts(locale: string): Promise<ProductMdx[]> {
|
|||||||
select: selectFields,
|
select: selectFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
let products: ProductMdx[] = result.docs
|
console.log(`[Payload] getAllProducts for ${locale}: Found ${result.docs.length} docs`);
|
||||||
.filter((doc) => {
|
|
||||||
const resolvedImages = ((doc.images as any[]) || [])
|
|
||||||
.map((img) => (typeof img === 'string' ? img : img.url))
|
|
||||||
.filter(Boolean);
|
|
||||||
return resolvedImages.length > 0;
|
|
||||||
})
|
|
||||||
.map((doc) => {
|
|
||||||
const resolvedImages = ((doc.images as any[]) || [])
|
|
||||||
.map((img) => (typeof img === 'string' ? img : img.url))
|
|
||||||
.filter(Boolean) as string[];
|
|
||||||
|
|
||||||
const plainCategories = Array.isArray(doc.categories)
|
let products: ProductMdx[] = result.docs.map((doc) => {
|
||||||
? doc.categories.map((c: any) => String(c.category))
|
const resolvedImages = ((doc.images as any[]) || [])
|
||||||
: [];
|
.map((img) => (typeof img === 'string' ? img : img.url))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
return {
|
const plainCategories = Array.isArray(doc.categories)
|
||||||
slug: String(doc.slug),
|
? doc.categories.map((c: any) => String(c.category))
|
||||||
frontmatter: {
|
: [];
|
||||||
title: String(doc.title),
|
|
||||||
sku: doc.sku ? String(doc.sku) : '',
|
return {
|
||||||
description: doc.description ? String(doc.description) : '',
|
slug: String(doc.slug),
|
||||||
categories: plainCategories,
|
frontmatter: {
|
||||||
images: resolvedImages,
|
title: String(doc.title),
|
||||||
locale: String(doc.locale),
|
sku: doc.sku ? String(doc.sku) : '',
|
||||||
},
|
description: doc.description ? String(doc.description) : '',
|
||||||
content: null,
|
categories: plainCategories,
|
||||||
};
|
images: resolvedImages,
|
||||||
});
|
locale: String(doc.locale),
|
||||||
|
},
|
||||||
|
content: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Also include English fallbacks for slugs not in this locale
|
// Also include English fallbacks for slugs not in this locale
|
||||||
if (locale !== 'en') {
|
if (locale !== 'en') {
|
||||||
@@ -227,14 +222,12 @@ export async function getAllProducts(locale: string): Promise<ProductMdx[]> {
|
|||||||
select: selectFields,
|
select: selectFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[Payload] getAllProducts (en fallbacks) for ${locale}: Found ${enResult.docs.length} docs`,
|
||||||
|
);
|
||||||
|
|
||||||
const fallbacks = enResult.docs
|
const fallbacks = enResult.docs
|
||||||
.filter((doc) => !localeSlugs.has(doc.slug))
|
.filter((doc) => !localeSlugs.has(doc.slug))
|
||||||
.filter((doc) => {
|
|
||||||
const resolvedImages = ((doc.images as any[]) || [])
|
|
||||||
.map((img) => (typeof img === 'string' ? img : img.url))
|
|
||||||
.filter(Boolean);
|
|
||||||
return resolvedImages.length > 0;
|
|
||||||
})
|
|
||||||
.map((doc) => {
|
.map((doc) => {
|
||||||
const resolvedImages = ((doc.images as any[]) || [])
|
const resolvedImages = ((doc.images as any[]) || [])
|
||||||
.map((img) => (typeof img === 'string' ? img : img.url))
|
.map((img) => (typeof img === 'string' ? img : img.url))
|
||||||
|
|||||||
Reference in New Issue
Block a user