Openstatus www.openstatus.dev
at 2aa970b7fc2afaca5e3ec584beda69c1f69ca34d 164 lines 4.3 kB view raw
1import fs from "node:fs"; 2import path from "node:path"; 3import { z } from "zod"; 4 5const metadataSchema = z.object({ 6 title: z.string(), 7 publishedAt: z.coerce.date(), 8 description: z.string(), 9 category: z.string(), 10 author: z.string(), 11 image: z.string().optional(), 12}); 13 14export type Metadata = z.infer<typeof metadataSchema>; 15 16function parseFrontmatter(fileContent: string) { 17 const frontmatterRegex = /---\s*([\s\S]*?)\s*---/; 18 const match = frontmatterRegex.exec(fileContent); 19 const frontMatterBlock = match?.[1]; 20 const content = fileContent.replace(frontmatterRegex, "").trim(); 21 const frontMatterLines = frontMatterBlock?.trim().split("\n"); 22 const metadata: Record<string, string> = {}; 23 24 frontMatterLines?.forEach((line) => { 25 const [key, ...valueArr] = line.split(": "); 26 let value = valueArr.join(": ").trim(); 27 value = value.replace(/^['"](.*)['"]$/, "$1"); // Remove quotes 28 metadata[key.trim()] = value; 29 }); 30 31 const validatedMetadata = metadataSchema.safeParse(metadata); 32 33 if (!validatedMetadata.success) { 34 console.error(validatedMetadata.error); 35 throw new Error(`Invalid metadata ${fileContent}`); 36 } 37 38 return { metadata: validatedMetadata.data, content }; 39} 40 41function getMDXFiles(dir: string) { 42 return fs.readdirSync(dir).filter((file) => path.extname(file) === ".mdx"); 43} 44 45function readMDXFile(filePath: string) { 46 const rawContent = fs.readFileSync(filePath, "utf-8"); 47 return parseFrontmatter(rawContent); 48} 49 50function getMDXDataFromDir(dir: string) { 51 const mdxFiles = getMDXFiles(dir); 52 return mdxFiles.map((file) => { 53 return getMDXDataFromFile(path.join(dir, file)); 54 }); 55} 56 57function getMDXDataFromFile(filePath: string) { 58 const { metadata, content } = readMDXFile(filePath); 59 const slug = path.basename(filePath, path.extname(filePath)); 60 return { 61 metadata, 62 slug, 63 content, 64 }; 65} 66 67export type MDXData = ReturnType<typeof getMDXDataFromFile>; 68 69export function getBlogPosts(): MDXData[] { 70 return getMDXDataFromDir( 71 path.join(process.cwd(), "src", "content", "pages", "blog"), 72 ); 73} 74 75export function getChangelogPosts(): MDXData[] { 76 return getMDXDataFromDir( 77 path.join(process.cwd(), "src", "content", "pages", "changelog"), 78 ); 79} 80 81export function getProductPages(): MDXData[] { 82 return getMDXDataFromDir( 83 path.join(process.cwd(), "src", "content", "pages", "product"), 84 ); 85} 86 87export function getUnrelatedPages(): MDXData[] { 88 return getMDXDataFromDir( 89 path.join(process.cwd(), "src", "content", "pages", "unrelated"), 90 ); 91} 92 93export function getUnrelatedPage(slug: string): MDXData { 94 return getMDXDataFromFile( 95 path.join( 96 process.cwd(), 97 "src", 98 "content", 99 "pages", 100 "unrelated", 101 `${slug}.mdx`, 102 ), 103 ); 104} 105 106export function getMainPages(): MDXData[] { 107 return [...getUnrelatedPages(), ...getProductPages()]; 108} 109 110export function getComparePages(): MDXData[] { 111 return getMDXDataFromDir( 112 path.join(process.cwd(), "src", "content", "pages", "compare"), 113 ); 114} 115 116export function getHomePage(): MDXData { 117 return getMDXDataFromFile( 118 path.join(process.cwd(), "src", "content", "pages", "home.mdx"), 119 ); 120} 121 122export function getToolsPages(): MDXData[] { 123 return getMDXDataFromDir( 124 path.join(process.cwd(), "src", "content", "pages", "tools"), 125 ); 126} 127 128export function getToolsPage(slug: string): MDXData { 129 return getMDXDataFromFile( 130 path.join(process.cwd(), "src", "content", "pages", "tools", `${slug}.mdx`), 131 ); 132} 133 134export function formatDate(targetDate: Date, includeRelative = false) { 135 const currentDate = new Date(); 136 137 const yearsAgo = currentDate.getFullYear() - targetDate.getFullYear(); 138 const monthsAgo = currentDate.getMonth() - targetDate.getMonth(); 139 const daysAgo = currentDate.getDate() - targetDate.getDate(); 140 141 let formattedDate = ""; 142 143 if (yearsAgo > 0) { 144 formattedDate = `${yearsAgo}y ago`; 145 } else if (monthsAgo > 0) { 146 formattedDate = `${monthsAgo}mo ago`; 147 } else if (daysAgo > 0) { 148 formattedDate = `${daysAgo}d ago`; 149 } else { 150 formattedDate = "Today"; 151 } 152 153 const fullDate = targetDate.toLocaleString("en-us", { 154 month: "short", 155 day: "2-digit", 156 year: "numeric", 157 }); 158 159 if (!includeRelative) { 160 return fullDate; 161 } 162 163 return `${fullDate} (${formattedDate})`; 164}