UNPKG

markpage

Version:

Build and manage markdown-based content with distributed navigation - framework agnostic content management system

331 lines (330 loc) 11 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const fs = require("fs"); const path = require("path"); const types = require("../types.cjs"); class ParserError extends Error { constructor(message, filePath) { super(message); this.filePath = filePath; this.name = "ParserError"; } } function parseIndexFile(filePath) { try { const content = fs.readFileSync(filePath, "utf-8"); const data = JSON.parse(content); if (!types.validateIndexFile(data)) { throw new ParserError( `Invalid .index.json format: Invalid data structure`, filePath ); } return data.items; } catch (error) { if (error instanceof ParserError) { throw error; } if (error instanceof SyntaxError) { throw new ParserError( `Invalid JSON in .index.json: ${error.message}`, filePath ); } throw new ParserError( `Failed to read .index.json: ${error instanceof Error ? error.message : "Unknown error"}`, filePath ); } } function autoDiscoverContent(dirPath) { try { const items = fs.readdirSync(dirPath, { withFileTypes: true }); const discoveredItems = []; const markdownFiles = items.filter((item) => item.isFile() && item.name.endsWith(".md")).map((item) => ({ ...item, name: item.name.slice(0, -3) })).sort((a, b) => a.name.localeCompare(b.name)); const directories = items.filter((item) => item.isDirectory() && !item.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name)); for (const file of markdownFiles) { if (file.name.toLowerCase() !== "index" && file.name.toLowerCase() !== "readme") { discoveredItems.push({ name: file.name, type: "page", label: generateLabel(file.name) }); } } for (const dir of directories) { discoveredItems.push({ name: dir.name, type: "section", label: generateLabel(dir.name) }); } return discoveredItems; } catch (error) { throw new ParserError( `Failed to auto-discover content in directory: ${error instanceof Error ? error.message : "Unknown error"}`, dirPath ); } } function generateLabel(name) { return name.replace(/[-_]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()).trim(); } function hasIndexFile(dirPath) { try { const indexPath = path.join(dirPath, ".index.json"); return fs.statSync(indexPath).isFile(); } catch { return false; } } function buildNavigationTree(contentPath, options = {}) { const { basePath = contentPath, validateFiles = true, autoDiscover = true } = options; try { if (!fs.statSync(contentPath).isDirectory()) { throw new ParserError(`Content path is not a directory: ${contentPath}`); } } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError(`Content path does not exist: ${contentPath}`, contentPath); } const rootIndexPath = path.join(contentPath, ".index.json"); let rootItems; const hasIndex = hasIndexFile(contentPath); if (hasIndex) { try { rootItems = parseIndexFile(rootIndexPath); } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError(`Root .index.json not found: ${rootIndexPath}`, rootIndexPath); } } else if (autoDiscover) { rootItems = autoDiscoverContent(contentPath); } else { throw new ParserError(`Root .index.json not found: ${rootIndexPath}`, rootIndexPath); } const navigationItems = []; for (const item of rootItems) { const navigationItem = processNavigationItem( item, contentPath, basePath, validateFiles, autoDiscover ); navigationItems.push(navigationItem); } return navigationItems; } function processNavigationItem(item, currentPath, basePath, validateFiles, autoDiscover) { const navigationItem = { ...item }; if (item.type === "section") { const sectionPath = path.join(currentPath, item.name); if (hasIndexFile(sectionPath)) { const sectionIndexPath = path.join(sectionPath, ".index.json"); if (validateFiles) { try { if (!fs.statSync(sectionIndexPath).isFile()) { throw new ParserError( `Section .index.json not found: ${sectionIndexPath}`, sectionIndexPath ); } } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError( `Section .index.json not found: ${sectionIndexPath}`, sectionIndexPath ); } } try { const sectionItems = parseIndexFile(sectionIndexPath); navigationItem.items = sectionItems.map( (subItem) => processNavigationItem(subItem, sectionPath, basePath, validateFiles, autoDiscover) ); } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError( `Failed to process section ${item.name}: ${error instanceof Error ? error.message : "Unknown error"}`, sectionIndexPath ); } } else if (autoDiscover) { try { const sectionItems = autoDiscoverContent(sectionPath); navigationItem.items = sectionItems.map( (subItem) => processNavigationItem(subItem, sectionPath, basePath, validateFiles, autoDiscover) ); } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError( `Failed to auto-discover section ${item.name}: ${error instanceof Error ? error.message : "Unknown error"}`, sectionPath ); } } else { const sectionIndexPath = path.join(sectionPath, ".index.json"); throw new ParserError( `Section .index.json not found: ${sectionIndexPath}`, sectionIndexPath ); } const indexPath = path.join(sectionPath, "index.md"); const readmePath = path.join(sectionPath, "README.md"); const readmeLowerPath = path.join(sectionPath, "readme.md"); let sectionRootPath; if (validateFiles) { try { if (fs.statSync(indexPath).isFile()) { sectionRootPath = indexPath; } } catch { try { if (fs.statSync(readmePath).isFile()) { sectionRootPath = readmePath; } } catch { try { if (fs.statSync(readmeLowerPath).isFile()) { sectionRootPath = readmeLowerPath; } } catch { } } } } else { if (fs.existsSync(indexPath)) { sectionRootPath = indexPath; } else if (fs.existsSync(readmePath)) { sectionRootPath = readmePath; } else if (fs.existsSync(readmeLowerPath)) { sectionRootPath = readmeLowerPath; } } if (sectionRootPath) { navigationItem.path = path.relative(basePath, sectionRootPath); } } else if (item.type === "page") { const pagePath = path.join(currentPath, `${item.name}.md`); const relativePath = path.relative(basePath, pagePath); if (validateFiles) { try { if (!fs.statSync(pagePath).isFile()) { throw new ParserError( `Page markdown file not found: ${pagePath}`, pagePath ); } } catch (error) { if (error instanceof ParserError) { throw error; } throw new ParserError( `Page markdown file not found: ${pagePath}`, pagePath ); } } navigationItem.path = relativePath; } return navigationItem; } function validateContentStructure(contentPath, options = {}) { const { autoDiscover = true } = options; const errors = []; function validateDirectory(dirPath, depth = 0) { if (depth > 10) { errors.push(`Maximum directory depth exceeded: ${dirPath}`); return; } const indexPath = path.join(dirPath, ".index.json"); if (hasIndexFile(dirPath)) { try { const items = parseIndexFile(indexPath); for (const item of items) { if (item.type === "section") { const sectionPath = path.join(dirPath, item.name); try { if (!fs.statSync(sectionPath).isDirectory()) { errors.push(`Section directory not found: ${sectionPath}`); continue; } } catch (error) { errors.push(`Section directory not found: ${sectionPath}`); continue; } validateDirectory(sectionPath, depth + 1); } else if (item.type === "page") { const pagePath = path.join(dirPath, `${item.name}.md`); try { if (!fs.statSync(pagePath).isFile()) { errors.push(`Page markdown file not found: ${pagePath}`); } } catch (error) { errors.push(`Page markdown file not found: ${pagePath}`); } } } } catch (error) { errors.push(`Failed to parse ${indexPath}: ${error instanceof Error ? error.message : "Unknown error"}`); } } else if (autoDiscover) { try { const items = autoDiscoverContent(dirPath); for (const item of items) { if (item.type === "section") { const sectionPath = path.join(dirPath, item.name); try { if (!fs.statSync(sectionPath).isDirectory()) { errors.push(`Section directory not found: ${sectionPath}`); continue; } } catch (error) { errors.push(`Section directory not found: ${sectionPath}`); continue; } validateDirectory(sectionPath, depth + 1); } else if (item.type === "page") { const pagePath = path.join(dirPath, `${item.name}.md`); try { if (!fs.statSync(pagePath).isFile()) { errors.push(`Page markdown file not found: ${pagePath}`); } } catch (error) { errors.push(`Page markdown file not found: ${pagePath}`); } } } } catch (error) { errors.push(`Failed to auto-discover content in ${dirPath}: ${error instanceof Error ? error.message : "Unknown error"}`); } } else { errors.push(`Missing .index.json: ${indexPath}`); return; } } validateDirectory(contentPath); if (errors.length > 0) { throw new ParserError( `Content structure validation failed: ${errors.join("\n")}`, contentPath ); } } exports.ParserError = ParserError; exports.buildNavigationTree = buildNavigationTree; exports.parseIndexFile = parseIndexFile; exports.validateContentStructure = validateContentStructure; //# sourceMappingURL=parser.cjs.map