#!/usr/bin/env tsx /** * Updated link test for the Next.js blog with App Router * Tests: All references are valid, files exist */ import fs from "fs"; import path from "path"; console.log("šŸ”— Checking links and references (Next.js App Router)...\n"); let passed = 0; let failed = 0; function test(name: string, fn: () => void): void { try { fn(); console.log(`āœ… ${name}`); passed++; } catch (error) { console.log(`āŒ ${name}`); if (error instanceof Error) { console.log(` Error: ${error.message}`); } failed++; } } // Test 1: Check that blog posts reference valid data test("Blog posts reference valid data", () => { const blogPostsPath = path.join(process.cwd(), "src/data/blogPosts.ts"); const content = fs.readFileSync(blogPostsPath, "utf8"); // Extract all slugs const slugMatches = content.match(/slug:\s*['"]([^'"]+)['"]/g) || []; const slugs = slugMatches.map((m) => m.match(/['"]([^'"]+)['"]/)?.[1]); if (slugs.length === 0) { throw new Error("No slugs found in blogPosts.ts"); } // Verify dynamic route page exists const slugPagePath = path.join(process.cwd(), "app/blog/[slug]/page.tsx"); if (!fs.existsSync(slugPagePath)) { throw new Error( "Dynamic slug page app/blog/[slug]/page.tsx does not exist", ); } }); // Test 2: Verify tag references are valid test("Tag references are valid", () => { const blogPostsPath = path.join(process.cwd(), "src/data/blogPosts.ts"); const content = fs.readFileSync(blogPostsPath, "utf8"); // Extract all tags const tagMatches = content.match(/tags:\s*\[([^\]]+)\]/g) || []; if (tagMatches.length === 0) { throw new Error("No tags found in blogPosts.ts"); } // Verify tag page exists const tagPagePath = path.join(process.cwd(), "app/tags/[tag]/page.tsx"); if (!fs.existsSync(tagPagePath)) { throw new Error("Tag page app/tags/[tag]/page.tsx does not exist"); } }); // Test 3: Verify all component imports are valid test("All component imports are valid", () => { const components = [ "src/components/MediumCard.tsx", "src/components/SearchBar.tsx", "src/components/ArticleBlockquote.tsx", "src/components/ArticleHeading.tsx", "src/components/ArticleParagraph.tsx", "src/components/ArticleList.tsx", "src/components/Footer.tsx", "src/components/Hero.tsx", "src/components/Tag.tsx", "src/components/FileExample.tsx", "src/components/FileExamplesList.tsx", ]; for (const component of components) { const componentPath = path.join(process.cwd(), component); if (!fs.existsSync(componentPath)) { throw new Error(`Component missing: ${component}`); } } }); // Test 4: Verify all required pages exist test("All required pages exist", () => { const requiredPages = [ "app/page.tsx", "app/blog/[slug]/page.tsx", "app/tags/[tag]/page.tsx", "app/api/download-zip/route.ts", ]; for (const page of requiredPages) { const pagePath = path.join(process.cwd(), page); if (!fs.existsSync(pagePath)) { throw new Error(`Required page missing: ${page}`); } } }); // Test 5: Verify layout files are valid test("Layout files are valid", () => { const layoutPath = path.join(process.cwd(), "app/layout.tsx"); if (!fs.existsSync(layoutPath)) { throw new Error("Layout missing: app/layout.tsx"); } const content = fs.readFileSync(layoutPath, "utf8"); if (!content.includes("")) { throw new Error("RootLayout does not contain proper HTML structure"); } if (!content.includes("")) { throw new Error("RootLayout missing body section"); } }); // Test 6: Verify global styles are properly imported test("Global styles are properly imported", () => { const stylesPath = path.join(process.cwd(), "app/globals.css"); if (!fs.existsSync(stylesPath)) { throw new Error("Global styles file missing: app/globals.css"); } const content = fs.readFileSync(stylesPath, "utf8"); // Check for Tailwind imports if ( !content.includes("@tailwind base") || !content.includes("@tailwind components") || !content.includes("@tailwind utilities") ) { throw new Error("Global styles missing Tailwind imports"); } // Check for required classes (Next.js version uses different ones or we check the ones we found) const requiredClasses = [".container", ".post-card", ".highlighter-tag"]; for (const className of requiredClasses) { if (!content.includes(className)) { throw new Error(`Global styles missing required class: ${className}`); } } }); // Test 7: Verify file examples data structure test("File examples data structure is valid", () => { const fileExamplesPath = path.join(process.cwd(), "src/data/fileExamples.ts"); if (!fs.existsSync(fileExamplesPath)) { throw new Error("File examples data file missing"); } const content = fs.readFileSync(fileExamplesPath, "utf8"); if ( !content.includes("export interface FileExample") && !content.includes("type FileExample") ) { throw new Error("FileExample interface/type not found"); } if (!content.includes("export const sampleFileExamples")) { throw new Error("sampleFileExamples not exported"); } }); // Test 8: Verify API endpoint structure test("API endpoint structure is valid", () => { const apiPath = path.join(process.cwd(), "app/api/download-zip/route.ts"); if (!fs.existsSync(apiPath)) { throw new Error("API route missing"); } const content = fs.readFileSync(apiPath, "utf8"); if (!content.includes("export async function POST")) { throw new Error("API missing POST handler"); } if (!content.includes("export async function GET")) { throw new Error("API missing GET handler"); } }); // Summary console.log("\n" + "=".repeat(50)); console.log(`Tests passed: ${passed}`); console.log(`Tests failed: ${failed}`); console.log("=".repeat(50)); if (failed === 0) { console.log("\nšŸŽ‰ All link checks passed! All references are valid."); console.log("\nVerified:"); console.log(" āœ… Blog posts data and routing (Next.js)"); console.log(" āœ… Tag filtering system"); console.log(" āœ… All components exist"); console.log(" āœ… All pages exist"); console.log(" āœ… Layout structure (App Router)"); console.log(" āœ… File examples functionality"); console.log(" āœ… API routes"); process.exit(0); } else { console.log("\nāŒ Some checks failed. Please fix the errors above."); process.exit(1); }