secureweb/sweb/scripts/generateNavData.ts
Mahmoud Emad da0ced9b4a feat: Add Tailwind CSS and navigation generation
- Added Tailwind CSS for styling.
- Implemented automatic navigation data generation from markdown files.
- Created `NavItem` component for rendering navigation items.
- Added scripts for navigation generation and updated build process.
2025-05-12 11:28:10 +03:00

138 lines
3.8 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Define the NavItem interface to match the existing type
interface NavItem {
label: string;
link: string;
children?: NavItem[];
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const docsDir = path.resolve(__dirname, '../src/docs');
const outputFile = path.resolve(__dirname, '../src/data/navData.json');
function toTitleCase(str: string): string {
return str
.replace(/[-_]/g, ' ')
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
function getBaseName(filePath: string): string {
return path.basename(filePath, path.extname(filePath));
}
function isHidden(name: string): boolean {
return name.startsWith('.');
}
/**
* Recursively checks if any markdown file exists in a directory or its subdirectories
*/
function hasMarkdownFilesDeep(dirPath: string): boolean {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (isHidden(entry.name)) continue;
if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') {
return true;
} else if (entry.isDirectory()) {
if (hasMarkdownFilesDeep(fullPath)) return true;
}
}
return false;
}
/**
* Recursively scans a directory and builds a navigation structure
*/
function scanDirectory(dirPath: string, relativePath: string = ''): NavItem[] {
const items: NavItem[] = [];
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
const directories = entries.filter(entry => entry.isDirectory() && !isHidden(entry.name));
for (const dir of directories) {
const dirRelativePath = path.join(relativePath, dir.name);
const dirFullPath = path.join(dirPath, dir.name);
if (!hasMarkdownFilesDeep(dirFullPath)) {
continue; // Skip folders that don't contain markdown files even in subdirs
}
const navItem: NavItem = {
label: toTitleCase(dir.name),
link: '/' + dirRelativePath,
children: scanDirectory(dirFullPath, dirRelativePath)
};
items.push(navItem);
}
const files = entries.filter(entry =>
entry.isFile() &&
!isHidden(entry.name) &&
path.extname(entry.name).toLowerCase() === '.md'
);
for (const file of files) {
const baseName = getBaseName(file.name);
const fileRelativePath = path.join(relativePath, baseName);
const navItem: NavItem = {
label: toTitleCase(baseName),
link: '/' + fileRelativePath
};
items.push(navItem);
}
return items;
}
function generateNavData(): void {
try {
console.log('Generating navigation data...');
const topLevelDirs = fs.readdirSync(docsDir, { withFileTypes: true })
.filter(entry => entry.isDirectory() && !isHidden(entry.name));
const navData: NavItem[] = [];
for (const dir of topLevelDirs) {
const dirPath = path.join(docsDir, dir.name);
if (!hasMarkdownFilesDeep(dirPath)) {
continue;
}
const children = scanDirectory(dirPath, dir.name);
const navItem: NavItem = {
label: toTitleCase(dir.name),
link: '/' + dir.name,
children
};
navData.push(navItem);
}
fs.writeFileSync(outputFile, JSON.stringify(navData, null, 2));
console.log(`Navigation data generated successfully: ${outputFile}`);
} catch (error) {
console.error('Error generating navigation data:', error);
process.exit(1);
}
}
// Run the generator
generateNavData();