Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 25s
Build & Deploy / 🧪 QA (push) Successful in 1m53s
Build & Deploy / 🏗️ Build (push) Successful in 3m56s
Build & Deploy / 🚀 Deploy (push) Successful in 32s
Build & Deploy / 🧪 Smoke Test (push) Successful in 1m2s
Build & Deploy / ⚡ Lighthouse (push) Successful in 2m40s
Build & Deploy / 🛡️ Quality Gates (push) Failing after 2m47s
Build & Deploy / 📸 Visual Diff (push) Failing after 6m37s
Build & Deploy / ♿ WCAG (push) Successful in 7m8s
Build & Deploy / 🔔 Notify (push) Successful in 2s
187 lines
5.1 KiB
TypeScript
187 lines
5.1 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import matter from 'gray-matter';
|
|
import { mapSlugToFileSlug } from './slugs';
|
|
|
|
export interface ProductFrontmatter {
|
|
title: string;
|
|
sku: string;
|
|
description: string;
|
|
categories: string[];
|
|
images: string[];
|
|
locale: string;
|
|
}
|
|
|
|
export interface ProductMdx {
|
|
slug: string;
|
|
frontmatter: ProductFrontmatter;
|
|
content: string;
|
|
}
|
|
|
|
export async function getProductMetadata(
|
|
slug: string,
|
|
locale: string,
|
|
): Promise<Partial<ProductMdx> | null> {
|
|
// Map translated slug to file slug
|
|
const fileSlug = await mapSlugToFileSlug(slug, locale);
|
|
const productsDir = path.join(process.cwd(), 'data', 'products', locale);
|
|
|
|
if (!fs.existsSync(productsDir)) return null;
|
|
|
|
// Recursive search for the file
|
|
const findFile = (dir: string): string | null => {
|
|
const files = fs.readdirSync(dir);
|
|
for (const file of files) {
|
|
const fullPath = path.join(dir, file);
|
|
const stat = fs.statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
const found = findFile(fullPath);
|
|
if (found) return found;
|
|
} else if (file === `${fileSlug}.mdx` || file === `${fileSlug}-2.mdx`) {
|
|
return fullPath;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
let filePath = findFile(productsDir);
|
|
|
|
if (!filePath && locale !== 'en') {
|
|
// Fallback to English
|
|
const enProductsDir = path.join(process.cwd(), 'data', 'products', 'en');
|
|
if (fs.existsSync(enProductsDir)) {
|
|
filePath = findFile(enProductsDir);
|
|
}
|
|
}
|
|
|
|
if (filePath && fs.existsSync(filePath)) {
|
|
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
const { data } = matter(fileContent);
|
|
|
|
// Filter out products without images to match getProductBySlug behavior
|
|
if (!data.images || data.images.length === 0 || !data.images[0]) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
slug: fileSlug,
|
|
frontmatter: {
|
|
...data,
|
|
isFallback: filePath.includes('/en/'),
|
|
} as any,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export async function getProductBySlug(slug: string, locale: string): Promise<ProductMdx | null> {
|
|
// Map translated slug to file slug
|
|
const fileSlug = await mapSlugToFileSlug(slug, locale);
|
|
const productsDir = path.join(process.cwd(), 'data', 'products', locale);
|
|
|
|
if (!fs.existsSync(productsDir)) return null;
|
|
|
|
// Recursive search for the file
|
|
const findFile = (dir: string): string | null => {
|
|
const files = fs.readdirSync(dir);
|
|
for (const file of files) {
|
|
const fullPath = path.join(dir, file);
|
|
const stat = fs.statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
const found = findFile(fullPath);
|
|
if (found) return found;
|
|
} else if (file === `${fileSlug}.mdx` || file === `${fileSlug}-2.mdx`) {
|
|
return fullPath;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
let filePath = findFile(productsDir);
|
|
let isFallback = false;
|
|
|
|
if (!filePath && locale !== 'en') {
|
|
// Fallback to English
|
|
const enProductsDir = path.join(process.cwd(), 'data', 'products', 'en');
|
|
if (fs.existsSync(enProductsDir)) {
|
|
filePath = findFile(enProductsDir);
|
|
if (filePath) isFallback = true;
|
|
}
|
|
}
|
|
|
|
if (filePath && fs.existsSync(filePath)) {
|
|
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
const { data, content } = matter(fileContent);
|
|
const product = {
|
|
slug: fileSlug,
|
|
frontmatter: {
|
|
...data,
|
|
isFallback,
|
|
} as any,
|
|
content,
|
|
};
|
|
|
|
// Filter out products without images
|
|
if (
|
|
!product.frontmatter.images ||
|
|
product.frontmatter.images.length === 0 ||
|
|
!product.frontmatter.images[0]
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return product;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export async function getAllProductSlugs(locale: string): Promise<string[]> {
|
|
const productsDir = path.join(process.cwd(), 'data', 'products', locale);
|
|
if (!fs.existsSync(productsDir)) return [];
|
|
|
|
const slugs: string[] = [];
|
|
const walk = (dir: string) => {
|
|
const files = fs.readdirSync(dir);
|
|
for (const file of files) {
|
|
const fullPath = path.join(dir, file);
|
|
const stat = fs.statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
walk(fullPath);
|
|
} else if (file.endsWith('.mdx')) {
|
|
slugs.push(file.replace(/\.mdx$/, ''));
|
|
}
|
|
}
|
|
};
|
|
|
|
walk(productsDir);
|
|
return slugs;
|
|
}
|
|
|
|
export async function getAllProducts(locale: string): Promise<ProductMdx[]> {
|
|
const slugs = await getAllProductSlugs(locale);
|
|
let allSlugs = slugs;
|
|
|
|
if (locale !== 'en') {
|
|
const enSlugs = await getAllProductSlugs('en');
|
|
allSlugs = Array.from(new Set([...slugs, ...enSlugs]));
|
|
}
|
|
|
|
const products = await Promise.all(allSlugs.map((slug) => getProductBySlug(slug, locale)));
|
|
return products.filter((p): p is ProductMdx => p !== null);
|
|
}
|
|
|
|
export async function getAllProductsMetadata(locale: string): Promise<Partial<ProductMdx>[]> {
|
|
const slugs = await getAllProductSlugs(locale);
|
|
let allSlugs = slugs;
|
|
|
|
if (locale !== 'en') {
|
|
const enSlugs = await getAllProductSlugs('en');
|
|
allSlugs = Array.from(new Set([...slugs, ...enSlugs]));
|
|
}
|
|
|
|
const metadata = await Promise.all(allSlugs.map((slug) => getProductMetadata(slug, locale)));
|
|
return metadata.filter((m): m is Partial<ProductMdx> => m !== null);
|
|
}
|