wip
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
/**
|
||||
* Simple link checker for the blog
|
||||
* Tests: All internal links work, no broken references
|
||||
* Updated link test for the blog with file examples
|
||||
* Tests: All references are valid, files exist
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
@@ -27,72 +27,59 @@ function test(name: string, fn: () => void): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 slug references
|
||||
const slugMatches = content.match(/slug:\s*['"]([^'"]+)['"]/g);
|
||||
if (!slugMatches) {
|
||||
// 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');
|
||||
}
|
||||
|
||||
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)`);
|
||||
}
|
||||
// Verify [slug].astro exists for dynamic routing
|
||||
const slugPagePath = path.join(process.cwd(), 'src/pages/blog/[slug].astro');
|
||||
if (!fs.existsSync(slugPagePath)) {
|
||||
throw new Error('Dynamic slug page [slug].astro does not exist');
|
||||
}
|
||||
});
|
||||
|
||||
// Test 2: Check that tag pages reference valid tags
|
||||
// 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 tags
|
||||
const tagsMatch = content.match(/tags:\s*\[([^\]]+)\]/);
|
||||
if (!tagsMatch) {
|
||||
// Extract all tags
|
||||
const tagMatches = content.match(/tags:\s*\[([^\]]+)\]/g) || [];
|
||||
|
||||
if (tagMatches.length === 0) {
|
||||
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
|
||||
// Verify 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');
|
||||
throw new Error('Tag page [tag].astro does not exist');
|
||||
}
|
||||
});
|
||||
|
||||
// Test 3: Check component imports
|
||||
// Test 3: Verify all component imports are valid
|
||||
test('All component imports are valid', () => {
|
||||
const components = [
|
||||
'src/components/MediumCard.tsx',
|
||||
'src/components/MediumCard.astro',
|
||||
'src/components/SearchBar.tsx',
|
||||
'src/components/ArticleBlockquote.tsx',
|
||||
'src/components/ArticleHeading.tsx',
|
||||
'src/components/ArticleParagraph.tsx',
|
||||
'src/components/ArticleList.tsx',
|
||||
'src/components/ArticleParagraph.tsx'
|
||||
'src/components/Footer.tsx',
|
||||
'src/components/Hero.tsx',
|
||||
'src/components/Tag.astro',
|
||||
'src/components/FileExample.astro',
|
||||
'src/components/FileExamplesList.astro'
|
||||
];
|
||||
|
||||
for (const component of components) {
|
||||
@@ -100,22 +87,16 @@ test('All component imports are valid', () => {
|
||||
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 4: Verify all required pages exist
|
||||
test('All required pages exist', () => {
|
||||
const requiredPages = [
|
||||
'src/pages/index.astro',
|
||||
'src/pages/about.astro',
|
||||
'src/pages/blog.astro',
|
||||
'src/pages/tags/[tag].astro'
|
||||
'src/pages/blog/[slug].astro',
|
||||
'src/pages/tags/[tag].astro',
|
||||
'src/pages/api/download-zip.ts'
|
||||
];
|
||||
|
||||
for (const page of requiredPages) {
|
||||
@@ -126,42 +107,123 @@ test('All required pages exist', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Test 5: Check layout structure
|
||||
// Test 5: Verify layout files are valid
|
||||
test('Layout files are valid', () => {
|
||||
const layouts = [
|
||||
'src/layouts/BaseLayout.astro',
|
||||
'src/layouts/BlogLayout.astro'
|
||||
];
|
||||
const layoutPath = path.join(process.cwd(), 'src/layouts/BaseLayout.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}`);
|
||||
if (!fs.existsSync(layoutPath)) {
|
||||
throw new Error('Layout missing: src/layouts/BaseLayout.astro');
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(layoutPath, 'utf8');
|
||||
|
||||
if (!content.includes('<html') || !content.includes('</html>')) {
|
||||
throw new Error('BaseLayout does not contain proper HTML structure');
|
||||
}
|
||||
|
||||
if (!content.includes('<head') || !content.includes('</head>')) {
|
||||
throw new Error('BaseLayout missing head section');
|
||||
}
|
||||
|
||||
if (!content.includes('<body') || !content.includes('</body>')) {
|
||||
throw new Error('BaseLayout missing body section');
|
||||
}
|
||||
});
|
||||
|
||||
// Test 6: Verify global styles are properly imported
|
||||
test('Global styles are properly imported', () => {
|
||||
const stylesPath = path.join(process.cwd(), 'src/styles/global.css');
|
||||
|
||||
if (!fs.existsSync(stylesPath)) {
|
||||
throw new Error('Global styles file missing');
|
||||
}
|
||||
|
||||
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
|
||||
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 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');
|
||||
// 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 (!content.includes('global.css')) {
|
||||
throw new Error('BaseLayout does not import global.css');
|
||||
if (!fs.existsSync(fileExamplesPath)) {
|
||||
throw new Error('File examples data file missing');
|
||||
}
|
||||
|
||||
const globalCssPath = path.join(process.cwd(), 'src/styles/global.css');
|
||||
if (!fs.existsSync(globalCssPath)) {
|
||||
throw new Error('Global CSS file missing');
|
||||
const content = fs.readFileSync(fileExamplesPath, 'utf8');
|
||||
|
||||
if (!content.includes('export interface FileExample')) {
|
||||
throw new Error('FileExample interface not exported');
|
||||
}
|
||||
|
||||
if (!content.includes('export const sampleFileExamples')) {
|
||||
throw new Error('sampleFileExamples not exported');
|
||||
}
|
||||
|
||||
// Check for required fields in interface
|
||||
const requiredFields = ['id', 'filename', 'content', 'language'];
|
||||
for (const field of requiredFields) {
|
||||
if (!content.includes(field)) {
|
||||
throw new Error(`FileExample interface missing field: ${field}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Test 8: Verify API endpoint structure
|
||||
test('API endpoint structure is valid', () => {
|
||||
const apiPath = path.join(process.cwd(), 'src/pages/api/download-zip.ts');
|
||||
|
||||
if (!fs.existsSync(apiPath)) {
|
||||
throw new Error('API endpoint missing');
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(apiPath, 'utf8');
|
||||
|
||||
if (!content.includes('export const POST')) {
|
||||
throw new Error('API missing POST handler');
|
||||
}
|
||||
|
||||
if (!content.includes('export const GET')) {
|
||||
throw new Error('API missing GET handler');
|
||||
}
|
||||
|
||||
if (!content.includes('FileExampleManager')) {
|
||||
throw new Error('API does not use FileExampleManager');
|
||||
}
|
||||
});
|
||||
|
||||
// Test 9: Verify blog template imports file examples
|
||||
test('Blog template imports file examples', () => {
|
||||
const blogTemplatePath = path.join(process.cwd(), 'src/pages/blog/[slug].astro');
|
||||
|
||||
if (!fs.existsSync(blogTemplatePath)) {
|
||||
throw new Error('Blog template missing');
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(blogTemplatePath, 'utf8');
|
||||
|
||||
if (!content.includes('FileExamplesList')) {
|
||||
throw new Error('Blog template does not import FileExamplesList');
|
||||
}
|
||||
|
||||
if (!content.includes('FileExamplesList')) {
|
||||
throw new Error('Blog template does not reference file examples');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log(`Tests passed: ${passed}`);
|
||||
@@ -169,8 +231,15 @@ 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!');
|
||||
console.log('\n🎉 All link checks passed! All references are valid.');
|
||||
console.log('\nVerified:');
|
||||
console.log(' ✅ Blog posts data and routing');
|
||||
console.log(' ✅ Tag filtering system');
|
||||
console.log(' ✅ All components exist');
|
||||
console.log(' ✅ All pages exist');
|
||||
console.log(' ✅ Layout structure');
|
||||
console.log(' ✅ File examples functionality');
|
||||
console.log(' ✅ API endpoints');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n❌ Some checks failed. Please fix the errors above.');
|
||||
|
||||
Reference in New Issue
Block a user