Compare commits

..

16 Commits

Author SHA1 Message Date
4ae45b1aa7 chore(release): 2.3.33 - NA Kabel Leiterklassen Fix
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Successful in 1m11s
Build & Deploy / 🏗️ Build (push) Successful in 2m15s
Build & Deploy / 🚀 Deploy (push) Successful in 16s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m1s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-08-07 16:16:25 +02:00
76cbdaa37e fix: remove class 1 conductor shapes (RE, SE) from all NA cables
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
2026-08-07 14:49:27 +02:00
5b1a2eff7f fix(mdx): handle productTabs in MDXContent to resolve Unknown Block Type
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Successful in 1m13s
Build & Deploy / 🏗️ Build (push) Successful in 2m15s
Build & Deploy / 🚀 Deploy (push) Successful in 16s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m22s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-08-07 12:54:02 +02:00
60422da644 chore(glitchtip): ignore version drift errors like ChunkLoadError globally
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9m9s
Build & Deploy / 🧪 QA (push) Successful in 1m19s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-07-21 12:23:52 +02:00
fc6da4e9aa fix(mdx): Handle brace counting correctly inside strings for Next-MDX-Remote data props parsing
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Successful in 1m13s
Build & Deploy / 🏗️ Build (push) Successful in 2m31s
Build & Deploy / 🚀 Deploy (push) Successful in 16s
Build & Deploy / 🔔 Notify (push) Successful in 3s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m18s
2026-07-18 09:43:33 +02:00
f7411d3dc4 2.3.30
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 1m13s
Build & Deploy / 🏗️ Build (push) Successful in 2m31s
Build & Deploy / 🚀 Deploy (push) Successful in 16s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m21s
2026-07-17 15:54:38 +02:00
7be2ec605c fix(contact): correct pino logger argument order to pass typescript compiler 2026-07-17 15:54:35 +02:00
bd00175912 2.3.29
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m18s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-07-17 15:18:50 +02:00
9c9663eb88 fix(mdx): robust parsing for payload data and ignore sentry pipe noise 2026-07-17 15:18:35 +02:00
402b5c30c6 chore: trigger deployment with new Mailgun credentials
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🧪 QA (push) Failing after 1m28s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-06-27 08:55:09 +02:00
2bb0381de4 chore: revert deploy.yml fallback, using Gitea variables now
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
2026-06-27 08:24:40 +02:00
614b0a5ffd feat(contact): add klaus@klz-cables.com to recipients and fix pino logging
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
2026-06-27 08:20:34 +02:00
b1569a1ce2 chore: bump version to 2.3.29-rc.1
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 13s
Build & Deploy / 🧪 QA (push) Successful in 1m26s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-22 12:02:28 +02:00
2c35c6b8aa feat(og): add logo and dynamic faded background image to OG templates
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 11s
Build & Deploy / 🧪 QA (push) Successful in 1m31s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-22 11:41:32 +02:00
bff150b1a7 feat: add new business card assets to public downloads directory
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🧪 QA (push) Successful in 1m32s
Build & Deploy / 🏗️ Build (push) Successful in 3m15s
Build & Deploy / 🚀 Deploy (push) Successful in 20s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m38s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / 💨 Smoke & Health (push) Successful in 1m6s
2026-06-22 09:23:15 +02:00
8ffb1e5f6a chore(release): 2.3.28 - Add Gotify notifications
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 11s
Build & Deploy / 🧪 QA (push) Successful in 1m27s
Build & Deploy / 🏗️ Build (push) Successful in 3m15s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m30s
Build & Deploy / 🔔 Notify (push) Successful in 3s
Nightly QA / 💨 Smoke & Health (push) Successful in 1m23s
2026-06-21 09:34:05 +02:00
35 changed files with 810 additions and 573 deletions

View File

@@ -256,6 +256,10 @@ jobs:
# Analytics
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
# Notifications
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -309,6 +313,10 @@ jobs:
echo "UMAMI_WEBSITE_ID=$UMAMI_WEBSITE_ID"
echo "UMAMI_API_ENDPOINT=$UMAMI_API_ENDPOINT"
echo ""
echo "# Notifications"
echo "GOTIFY_URL=$GOTIFY_URL"
echo "GOTIFY_TOKEN=$GOTIFY_TOKEN"
echo ""
echo "TARGET=$TARGET"
echo "SENTRY_ENVIRONMENT=$TARGET"
echo "PROJECT_NAME=$PROJECT_NAME"

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ backups/
# Local data / backups
.data/
scratch/

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getPageBySlug } from '@/lib/pages';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -20,9 +20,12 @@ export default async function Image({
}
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate
image={bgImage}
logo={logo}
title={pageData.frontmatter.title}
description={pageData.frontmatter.excerpt}
label="Information"

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getPostBySlug } from '@/lib/blog';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
import { SITE_URL } from '@/lib/schema';
export const size = OG_IMAGE_SIZE;
@@ -35,6 +35,7 @@ export default async function Image({
}
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
// We don't have request.url here, but we can assume the domain from SITE_URL or config
// For local images during dev, relative paths in <img> might not work in Satori
@@ -55,10 +56,11 @@ export default async function Image({
return new ImageResponse(
<OGImageTemplate
logo={logo}
title={post.frontmatter.title}
description={post.frontmatter.excerpt}
label={post.frontmatter.category || 'Blog'}
image={base64Image || featuredImage}
image={base64Image || featuredImage || bgImage}
/>,
{
...OG_IMAGE_SIZE,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,9 +11,16 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate title={t('title')} description={t('description')} label="Blog" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={t('title')}
description={t('description')}
label="Blog"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Contact' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Contact" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Contact"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,9 +11,12 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Index.meta' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate
image={bgImage}
logo={logo}
title={t('title')}
description={t('description')}
label="Reliable Energy Infrastructure"

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,6 +11,7 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Products' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t.has('meta.title')
? t('meta.title')
@@ -20,7 +21,13 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Products" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Products"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Team' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t('meta.title') || t('hero.subtitle');
const description = t('meta.description') || t('hero.title');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Our Team" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Our Team"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -61,7 +61,7 @@ export async function sendContactFormAction(formData: FormData) {
productName,
message,
};
fs.appendFileSync(backupFile, JSON.stringify(leadData) + '\\n');
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) });
@@ -156,7 +156,7 @@ export async function sendContactFormAction(formData: FormData) {
// Notify via Gotify (Internal)
await services.notifications.notify({
title: `📩 ${notificationSubject}`,
title: `📩 [KLZ] ${notificationSubject}`,
message: `New message from ${name} (${email}):\n\n${message}`,
priority: 5,
});

View File

@@ -24,6 +24,7 @@ import VideoSection from './home/VideoSection';
import CTA from './home/CTA';
import { AgbHistoryBlock } from './AgbHistoryBlock';
import { PDFDownloadBlock } from './PDFDownloadBlock';
import ProductTechnicalData from './ProductTechnicalData';
function ContactSection(props: any) {
return (
<div className="p-8 border-2 border-dashed border-primary my-8 text-center text-primary font-bold">
@@ -100,6 +101,8 @@ async function Block(props: any) {
return <AgbHistoryBlock {...data} />;
case 'pdfDownload':
return <PDFDownloadBlock {...data} />;
case 'productTabs':
return <ProductTechnicalData data={data} />;
default:
return (

View File

@@ -5,6 +5,7 @@ interface OGImageTemplateProps {
description?: string;
label?: string;
image?: string;
logo?: string;
mode?: 'dark' | 'light' | 'image';
}
@@ -13,6 +14,7 @@ export function OGImageTemplate({
description,
label,
image,
logo,
mode = 'dark',
}: OGImageTemplateProps) {
const primaryBlue = '#001a4d';
@@ -152,21 +154,26 @@ export function OGImageTemplate({
height: '6px',
backgroundColor: accentGreen,
borderRadius: '3px',
marginRight: '24px',
marginRight: '32px',
}}
/>
<div
style={{
fontSize: '24px',
fontWeight: 700,
color: 'white',
textTransform: 'uppercase',
letterSpacing: '0.15em',
display: 'flex',
}}
>
KLZ Cables
</div>
{logo ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img src={logo} alt="KLZ Cables" height="40" style={{ objectFit: 'contain' }} />
) : (
<div
style={{
fontSize: '24px',
fontWeight: 700,
color: 'white',
textTransform: 'uppercase',
letterSpacing: '0.15em',
display: 'flex',
}}
>
KLZ Cables
</div>
)}
</div>
{/* Saturated Blue Brand Strip */}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -65,11 +65,8 @@ export async function getPostBySlug(slug: string, locale: string): Promise<PostD
const fileContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(fileContent);
// Fix MDX data props dropped by next-mdx-remote
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
return 'data="{' + p1.replace(/"/g, '&quot;') + '}"';
});
const { fixMdxDataProps } = await import('./mdx-utils');
const fixedContent = fixMdxDataProps(content);
let parsedContent = fixedContent;
try {

68
lib/mdx-utils.ts Normal file
View File

@@ -0,0 +1,68 @@
export function fixMdxDataProps(content: string): string {
let fixedContent = content;
let dataIndex = fixedContent.indexOf('data={{');
while (dataIndex !== -1) {
let openCount = 0;
let endIndex = -1;
const startObj = dataIndex + 6; // index of the first '{' in 'data={{'
let inString = false;
let isEscaped = false;
for (let i = startObj; i < fixedContent.length; i++) {
const char = fixedContent[i];
if (isEscaped) {
isEscaped = false;
continue;
}
if (char === '\\') {
isEscaped = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (!inString) {
if (char === '{') {
openCount++;
} else if (char === '}') {
openCount--;
if (openCount === 0) {
endIndex = i;
break;
}
}
}
}
if (endIndex !== -1) {
// jsonStr is the content INSIDE the outer curly braces
const jsonStr = fixedContent.substring(startObj + 1, endIndex);
const safeJsonStr = jsonStr.replace(/"/g, '&quot;');
const replacement = `data="{${safeJsonStr}}"`;
// We also need to consume the closing `}` of the `data={{`
const nextCharIndex = endIndex + 1;
const skipChars =
nextCharIndex < fixedContent.length && fixedContent[nextCharIndex] === '}' ? 2 : 1;
fixedContent =
fixedContent.substring(0, dataIndex) +
replacement +
fixedContent.substring(endIndex + skipChars);
dataIndex = fixedContent.indexOf('data={{', dataIndex + replacement.length);
} else {
// Malformed braces, prevent infinite loop
break;
}
}
return fixedContent;
}

View File

@@ -55,3 +55,36 @@ export const OG_IMAGE_SIZE = {
width: 1200,
height: 630,
};
/**
* Loads a local image and converts it to a base64 data URI for Satori
*/
export function getLocalImageBase64(filename: string): string | undefined {
try {
const filePath = join(process.cwd(), 'public', filename);
const buffer = readFileSync(filePath);
const ext = filename.split('.').pop()?.toLowerCase();
const mimeType =
ext === 'png'
? 'image/png'
: ext === 'jpg' || ext === 'jpeg'
? 'image/jpeg'
: 'image/svg+xml';
return `data:${mimeType};base64,${buffer.toString('base64')}`;
} catch (error) {
console.error(`[OG] Failed to load local image ${filename}:`, error);
return undefined;
}
}
/**
* Returns standard assets for OG images
*/
export function getOgAssets() {
return {
logo: getLocalImageBase64('logo-white.png'),
bgImage: getLocalImageBase64(
'media/aerial-view-of-electricity-station-surrounded-with-2023-11-27-05-33-40-utc-scaled.jpg',
),
};
}

View File

@@ -53,11 +53,8 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageD
const fileContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(fileContent);
// Fix MDX data props dropped by next-mdx-remote
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
return 'data="{' + p1.replace(/"/g, '&quot;') + '}"';
});
const { fixMdxDataProps } = await import('./mdx-utils');
const fixedContent = fixMdxDataProps(content);
let parsedContent = fixedContent;
try {

View File

@@ -39,11 +39,8 @@ export async function getProductBySlug(slug: string, locale: string): Promise<Pr
const fileContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(fileContent);
// Fix MDX data props dropped by next-mdx-remote
// Payload serializes as data={{"items":...}} which Acorn treats as a Block statement.
const fixedContent = content.replace(/data=\{\{([\s\S]*?)\}\}/g, (match, p1) => {
return 'data="{' + p1.replace(/"/g, '&quot;') + '}"';
});
const { fixMdxDataProps } = await import('./mdx-utils');
const fixedContent = fixMdxDataProps(content);
let parsedContent = fixedContent;
try {

View File

@@ -56,6 +56,11 @@ export class GlitchtipErrorReportingService implements ErrorReportingService {
tracesSampleRate: this.options.tracesSampleRate ?? 0.1,
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
ignoreErrors: [
'ChunkLoadError',
'Failed to fetch dynamically imported module',
'Failed to find Server Action',
],
});
}
return Sentry;

View File

@@ -116,7 +116,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.3.27",
"version": "2.3.33",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

View File

@@ -12,4 +12,5 @@ Sentry.init({
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
ignoreErrors: ['failed to pipe response'],
});

View File

@@ -12,4 +12,5 @@ Sentry.init({
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
ignoreErrors: ['failed to pipe response'],
});

View File

@@ -0,0 +1,47 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { GlitchtipErrorReportingService } from '../lib/services/errors/glitchtip-error-reporting-service';
// Mock the LoggerService
const mockLogger = {
child: vi.fn().mockReturnThis(),
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
} as any;
const mockSentryInit = vi.fn();
vi.mock('@sentry/nextjs', () => ({
init: (...args: any[]) => mockSentryInit(...args),
captureException: vi.fn(),
captureMessage: vi.fn(),
}));
describe('GlitchtipErrorReportingService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should ignore version drift errors (ChunkLoadError and Server Action) in Sentry config', async () => {
// Simulate window to force client-side init behavior instantly
const originalWindow = global.window;
global.window = {
requestIdleCallback: (cb: Function) => cb(),
} as any;
const service = new GlitchtipErrorReportingService({ enabled: true }, mockLogger);
// Give the dynamic import a moment to resolve
await new Promise((resolve) => setTimeout(resolve, 50));
expect(mockSentryInit).toHaveBeenCalledOnce();
const initCallArgs = mockSentryInit.mock.calls[0][0];
expect(initCallArgs.ignoreErrors).toBeDefined();
expect(initCallArgs.ignoreErrors).toContain('ChunkLoadError');
expect(initCallArgs.ignoreErrors).toContain('Failed to find Server Action');
global.window = originalWindow;
});
});

View File

@@ -0,0 +1,14 @@
import { describe, it, expect } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
describe('MDXContent Mapping', () => {
it('should have productTabs in the switch statement to prevent Unknown Block Type errors', () => {
const filePath = path.join(process.cwd(), 'components/MDXContent.tsx');
const fileContent = fs.readFileSync(filePath, 'utf-8');
// Check if the switch case for productTabs exists
expect(fileContent).toContain("case 'productTabs':");
expect(fileContent).toContain('<ProductTechnicalData');
});
});

22
tests/mdx-regex.test.ts Normal file
View File

@@ -0,0 +1,22 @@
import { describe, it, expect } from 'vitest';
import { fixMdxDataProps } from '../lib/mdx-utils';
describe('MDX Data Props Fixer', () => {
it('should correctly parse MDX with nested JSON containing }}', () => {
// This string simulates the exact structure that causes the bug in n2x2y.mdx
const mdxInput = `<Block type="productTabs" data={{"content":{"root":{"children":[]}},"id":"123"}} />`;
const result = fixMdxDataProps(mdxInput);
const expected = `<Block type="productTabs" data="{&quot;content&quot;:{&quot;root&quot;:{&quot;children&quot;:[]}},&quot;id&quot;:&quot;123&quot;}" />`;
expect(result).toBe(expected);
});
it('should not prematurely truncate if } appears inside a string literal', () => {
const mdxInput = `<Block type="productTabs" data={{"content":{"text":"}>"}}} />`;
const result = fixMdxDataProps(mdxInput);
const expected = `<Block type="productTabs" data="{&quot;content&quot;:{&quot;text&quot;:&quot;}&gt;&quot;}}" />`;
expect(result).toBe(expected.replace('&gt;', '>'));
});
});