From 4cada38548af6664a6590a3d0e5dc1745e316167 Mon Sep 17 00:00:00 2001 From: Ben Lucyk Date: Wed, 31 Dec 2025 19:43:38 -0600 Subject: [PATCH] fix: normalize CRLF line endings in markdown parsers Markdown files created on Windows use CRLF (\\r\\n) line endings, which caused the regex patterns in parseProductOverview and parseProductRoadmap to fail matching section headers. This fix normalizes line endings to LF (\\n) before parsing, ensuring cross-platform compatibility. --- src/lib/product-loader.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib/product-loader.ts b/src/lib/product-loader.ts index 39508a9..d6878a6 100644 --- a/src/lib/product-loader.ts +++ b/src/lib/product-loader.ts @@ -55,16 +55,19 @@ export function parseProductOverview(md: string): ProductOverview | null { if (!md || !md.trim()) return null try { + // Normalize line endings (Windows CRLF → LF) + const normalizedMd = md.replace(/\r\n/g, '\n') + // Extract product name from first # heading - const nameMatch = md.match(/^#\s+(.+)$/m) + const nameMatch = normalizedMd.match(/^#\s+(.+)$/m) const name = nameMatch?.[1]?.trim() || 'Product Overview' // Extract description - content between ## Description and next ## - const descMatch = md.match(/## Description\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) + const descMatch = normalizedMd.match(/## Description\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) const description = descMatch?.[1]?.trim() || '' // Extract problems - ### Problem N: Title pattern - const problemsSection = md.match(/## Problems & Solutions\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) + const problemsSection = normalizedMd.match(/## Problems & Solutions\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) const problems: Problem[] = [] if (problemsSection?.[1]) { @@ -78,7 +81,7 @@ export function parseProductOverview(md: string): ProductOverview | null { } // Extract features - bullet list after ## Key Features - const featuresSection = md.match(/## Key Features\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) + const featuresSection = normalizedMd.match(/## Key Features\s*\n+([\s\S]*?)(?=\n## |\n#[^#]|$)/) const features: string[] = [] if (featuresSection?.[1]) { @@ -122,8 +125,11 @@ export function parseProductRoadmap(md: string): ProductRoadmap | null { try { const sections: Section[] = [] + // Normalize line endings (Windows CRLF → LF) + const normalizedMd = md.replace(/\r\n/g, '\n') + // Match sections with pattern ### N. Title - const sectionMatches = [...md.matchAll(/### (\d+)\.\s*(.+)\n+([\s\S]*?)(?=\n### |\n## |\n#[^#]|$)/g)] + const sectionMatches = [...normalizedMd.matchAll(/### (\d+)\.\s*(.+)\n+([\s\S]*?)(?=\n### |\n## |\n#[^#]|$)/g)] for (const match of sectionMatches) { const order = parseInt(match[1], 10)