178 lines
5.0 KiB
TypeScript
178 lines
5.0 KiB
TypeScript
#!/usr/bin/env tsx
|
|
|
|
/**
|
|
* Simple link checker for the blog
|
|
* Tests: All internal links work, no broken references
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
console.log('🔗 Checking links and references...\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 all referenced blog posts exist
|
|
test('All blog post files exist', () => {
|
|
const blogDir = path.join(process.cwd(), 'src/pages/blog');
|
|
const files = fs.readdirSync(blogDir);
|
|
|
|
// Get all .astro files
|
|
const astroFiles = files.filter(f => f.endsWith('.astro'));
|
|
|
|
if (astroFiles.length === 0) {
|
|
throw new Error('No blog post files found');
|
|
}
|
|
|
|
// Check blogPosts.ts references exist
|
|
const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts');
|
|
const content = fs.readFileSync(blogPostsPath, 'utf8');
|
|
|
|
// Extract slug references
|
|
const slugMatches = content.match(/slug:\s*['"]([^'"]+)['"]/g);
|
|
if (!slugMatches) {
|
|
throw new Error('No slugs found in blogPosts.ts');
|
|
}
|
|
|
|
const slugs = slugMatches.map(m => m.match(/['"]([^'"]+)['"]/)?.[1]);
|
|
|
|
for (const slug of slugs) {
|
|
const expectedFile = `${slug}.astro`;
|
|
if (!astroFiles.includes(expectedFile)) {
|
|
throw new Error(`Blog post file missing: ${expectedFile} (referenced in blogPosts.ts)`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Test 2: Check that tag pages reference valid tags
|
|
test('Tag references are valid', () => {
|
|
const blogPostsPath = path.join(process.cwd(), 'src/data/blogPosts.ts');
|
|
const content = fs.readFileSync(blogPostsPath, 'utf8');
|
|
|
|
// Extract tags
|
|
const tagsMatch = content.match(/tags:\s*\[([^\]]+)\]/);
|
|
if (!tagsMatch) {
|
|
throw new Error('No tags found in blogPosts.ts');
|
|
}
|
|
|
|
const tagsContent = tagsMatch[1];
|
|
const tags = tagsContent.match(/['"]([^'"]+)['"]/g)?.map(t => t.replace(/['"]/g, ''));
|
|
|
|
if (!tags || tags.length === 0) {
|
|
throw new Error('No tags extracted');
|
|
}
|
|
|
|
// Check that tag page exists
|
|
const tagPagePath = path.join(process.cwd(), 'src/pages/tags/[tag].astro');
|
|
if (!fs.existsSync(tagPagePath)) {
|
|
throw new Error('Tag page template missing');
|
|
}
|
|
});
|
|
|
|
// Test 3: Check component imports
|
|
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/ArticleList.tsx',
|
|
'src/components/ArticleParagraph.tsx'
|
|
];
|
|
|
|
for (const component of components) {
|
|
const componentPath = path.join(process.cwd(), component);
|
|
if (!fs.existsSync(componentPath)) {
|
|
throw new Error(`Component missing: ${component}`);
|
|
}
|
|
|
|
// Check for export
|
|
const content = fs.readFileSync(componentPath, 'utf8');
|
|
if (!content.includes('export')) {
|
|
throw new Error(`Component has no exports: ${component}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Test 4: Check page structure
|
|
test('All required pages exist', () => {
|
|
const requiredPages = [
|
|
'src/pages/index.astro',
|
|
'src/pages/about.astro',
|
|
'src/pages/blog.astro',
|
|
'src/pages/tags/[tag].astro'
|
|
];
|
|
|
|
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: Check layout structure
|
|
test('Layout files are valid', () => {
|
|
const layouts = [
|
|
'src/layouts/BaseLayout.astro',
|
|
'src/layouts/BlogLayout.astro'
|
|
];
|
|
|
|
for (const layout of layouts) {
|
|
const layoutPath = path.join(process.cwd(), layout);
|
|
if (!fs.existsSync(layoutPath)) {
|
|
throw new Error(`Layout missing: ${layout}`);
|
|
}
|
|
|
|
// Check for basic structure
|
|
const content = fs.readFileSync(layoutPath, 'utf8');
|
|
if (!content.includes('<slot')) {
|
|
throw new Error(`Layout missing slot: ${layout}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Test 6: Check style imports
|
|
test('Global styles are properly imported', () => {
|
|
const baseLayoutPath = path.join(process.cwd(), 'src/layouts/BaseLayout.astro');
|
|
const content = fs.readFileSync(baseLayoutPath, 'utf8');
|
|
|
|
if (!content.includes('global.css')) {
|
|
throw new Error('BaseLayout does not import global.css');
|
|
}
|
|
|
|
const globalCssPath = path.join(process.cwd(), 'src/styles/global.css');
|
|
if (!fs.existsSync(globalCssPath)) {
|
|
throw new Error('Global CSS file missing');
|
|
}
|
|
});
|
|
|
|
// 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! Your blog structure is solid.');
|
|
console.log('\nYour blog is ready to use!');
|
|
process.exit(0);
|
|
} else {
|
|
console.log('\n❌ Some checks failed. Please fix the errors above.');
|
|
process.exit(1);
|
|
} |