diff --git a/package.json b/package.json index 0760313..8207b88 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "@11ty/eleventy": "^2.0.1", "gray-matter": "^4.0.3", "marked": "^11.1.1", - "lunr": "^2.3.9" + "lunr": "^2.3.9", + "js-yaml": "^4.1.0" }, "license": "MIT" } diff --git a/src/config/loadConfig.js b/src/config/loadConfig.js new file mode 100644 index 0000000..9ee969f --- /dev/null +++ b/src/config/loadConfig.js @@ -0,0 +1,69 @@ +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); + +function deepMerge(target, source) { + for (const key of Object.keys(source)) { + if ( + source[key] && + typeof source[key] === 'object' && + !Array.isArray(source[key]) + ) { + target[key] = deepMerge(target[key] || {}, source[key]); + } else if (source[key] !== undefined) { + target[key] = source[key]; + } + } + return target; +} + +function loadConfig(configPath = path.join(process.cwd(), 'config.yaml')) { + let raw = {}; + if (fs.existsSync(configPath)) { + try { + raw = yaml.load(fs.readFileSync(configPath, 'utf8')) || {}; + } catch (e) { + console.error(`Failed to parse ${configPath}: ${e.message}`); + process.exit(1); + } + } + + const defaults = { + site: { + title: 'DocForge', + description: '', + logo: '', + favicon: '' + }, + navigation: { + search: true + }, + footer: {}, + theme: { + name: 'minimal', + darkMode: false + }, + features: {}, + plugins: [] + }; + + const config = deepMerge(defaults, raw); + + const errors = []; + if ( + !config.site || + typeof config.site.title !== 'string' || + !config.site.title.trim() + ) { + errors.push('site.title is required in config.yaml'); + } + + if (errors.length) { + errors.forEach(err => console.error(`Config error: ${err}`)); + process.exit(1); + } + + return config; +} + +module.exports = loadConfig; diff --git a/src/generator/index.js b/src/generator/index.js index 1d3cc71..3af63e5 100644 --- a/src/generator/index.js +++ b/src/generator/index.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const matter = require('gray-matter'); const Eleventy = require('@11ty/eleventy'); +const loadConfig = require('../config/loadConfig'); async function readDirRecursive(dir) { const entries = await fs.promises.readdir(dir, { withFileTypes: true }); @@ -50,7 +51,8 @@ function buildNav(pages) { return tree.children || []; } -async function generate({ contentDir = 'content', outputDir = '_site' } = {}) { +async function generate({ contentDir = 'content', outputDir = '_site', configPath } = {}) { + const config = loadConfig(configPath); if (!fs.existsSync(contentDir)) { console.error(`Content directory not found: ${contentDir}`); return; @@ -82,6 +84,7 @@ async function generate({ contentDir = 'content', outputDir = '_site' } = {}) { const nav = buildNav(pages); await fs.promises.mkdir(outputDir, { recursive: true }); await fs.promises.writeFile(path.join(outputDir, 'navigation.json'), JSON.stringify(nav, null, 2)); + await fs.promises.writeFile(path.join(outputDir, 'config.json'), JSON.stringify(config, null, 2)); const elev = new Eleventy(contentDir, outputDir); elev.setConfig({ @@ -93,6 +96,7 @@ async function generate({ contentDir = 'content', outputDir = '_site' } = {}) { }); elev.configFunction = function(eleventyConfig) { eleventyConfig.addGlobalData('navigation', nav); + eleventyConfig.addGlobalData('config', config); }; await elev.write();