UNPKG

veko

Version:

Ultra-lightweight Node.js framework with ZERO dependencies - VSV components, Tailwind CSS, asset imports, SSR

314 lines (271 loc) 7.43 kB
/** * Veko Configuration Loader * Handles veko.config.js/ts loading and validation * * @module veko/core/config */ const fs = require('fs'); const path = require('path'); /** * Default configuration */ const defaultConfig = { // Project settings name: 'veko-app', version: '1.0.0', // Server settings port: 3000, host: 'localhost', // Directories dirs: { pages: 'pages', components: 'components', public: 'public', themes: 'themes', modules: 'modules', plugins: 'plugins', widgets: 'widgets', api: 'api', output: '.veko' }, // Build settings build: { outDir: 'dist', staticDir: 'dist/static', minify: true, sourceMaps: false, analyze: false }, // Page defaults pages: { defaultType: 'static', // static | ssg | dynamic extensions: ['.jsv', '.tsv', '.jsx', '.tsx'] }, // Ultra dynamic pages configuration ultra: { enabled: false, dirs: [], // Directories to treat as ultra-dynamic files: [], // Specific files to treat as ultra-dynamic patterns: [], // Glob patterns for ultra-dynamic pages cacheTime: 60000, // Cache TTL in ms lazyComponents: true }, // Module system moduleSystem: { enabled: true, autoLoad: [], // Modules to auto-enable defaultTheme: null // Default theme to activate }, // VSV settings vsv: { ssr: true, hydrate: true, precompile: true, tailwind: false }, // Security security: { rateLimit: { enabled: true, windowMs: 15 * 60 * 1000, max: 100 }, cors: { enabled: false, origins: ['*'] }, headers: { xFrameOptions: 'DENY', xContentTypeOptions: 'nosniff', xXssProtection: '1; mode=block' } }, // Development dev: { hotReload: true, verbose: false, openBrowser: false } }; /** * Configuration loader */ class ConfigLoader { constructor() { this.config = null; this.configPath = null; } /** * Find and load configuration file */ async load(baseDir = process.cwd()) { const configFiles = [ 'veko.config.js', 'veko.config.ts', 'veko.config.mjs', 'veko.config.json' ]; for (const file of configFiles) { const configPath = path.join(baseDir, file); if (fs.existsSync(configPath)) { this.configPath = configPath; this.config = await this.loadConfigFile(configPath); return this.config; } } // No config file found, use defaults this.config = { ...defaultConfig }; return this.config; } /** * Load a configuration file */ async loadConfigFile(configPath) { const ext = path.extname(configPath); try { let userConfig; if (ext === '.json') { const content = fs.readFileSync(configPath, 'utf-8'); userConfig = JSON.parse(content); } else if (ext === '.ts') { // TypeScript config - needs transpilation userConfig = await this.loadTypeScriptConfig(configPath); } else { // JavaScript config delete require.cache[require.resolve(configPath)]; userConfig = require(configPath); // Handle ES modules if (userConfig.default) { userConfig = userConfig.default; } // Handle function config if (typeof userConfig === 'function') { userConfig = await userConfig(); } } return this.mergeConfig(userConfig); } catch (error) { console.error(`Failed to load config from ${configPath}: ${error.message}`); return { ...defaultConfig }; } } /** * Load TypeScript config */ async loadTypeScriptConfig(configPath) { // Simple TS transpilation (no external deps) const content = fs.readFileSync(configPath, 'utf-8'); // Basic transpilation: remove type annotations let jsContent = content .replace(/:\s*\w+(\[\])?(\s*[=,\)])/g, '$2') // Remove type annotations .replace(/import\s+type\s+.*?;/g, '') // Remove type imports .replace(/<\w+>/g, '') // Remove generics .replace(/export\s+default\s+/, 'module.exports = ') .replace(/export\s+const\s+defineConfig\s*=/, 'const defineConfig ='); // Create temp JS file const tempPath = configPath.replace('.ts', '.temp.js'); fs.writeFileSync(tempPath, jsContent); try { delete require.cache[require.resolve(tempPath)]; const config = require(tempPath); fs.unlinkSync(tempPath); return config.default || config; } catch (error) { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); throw error; } } /** * Merge user config with defaults */ mergeConfig(userConfig) { return this.deepMerge(defaultConfig, userConfig); } /** * Deep merge objects */ deepMerge(target, source) { const result = { ...target }; for (const key of Object.keys(source)) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * Get configuration value */ get(key) { const keys = key.split('.'); let value = this.config; for (const k of keys) { if (value === undefined) return undefined; value = value[k]; } return value; } /** * Validate configuration */ validate() { const errors = []; const warnings = []; // Validate required directories exist const requiredDirs = ['pages', 'components']; for (const dir of requiredDirs) { const dirPath = path.join(process.cwd(), this.config.dirs[dir]); if (!fs.existsSync(dirPath)) { warnings.push(`Directory '${dir}' not found at ${dirPath}`); } } // Validate ultra config if (this.config.ultra.enabled) { if (!this.config.ultra.dirs.length && !this.config.ultra.files.length && !this.config.ultra.patterns.length) { warnings.push('Ultra dynamic mode enabled but no dirs/files/patterns specified'); } } // Validate port if (this.config.port < 1 || this.config.port > 65535) { errors.push(`Invalid port: ${this.config.port}`); } return { valid: errors.length === 0, errors, warnings }; } } /** * Define config helper (for TypeScript) */ function defineConfig(config) { return config; } /** * Global config instance */ let globalConfig = null; /** * Get or load config */ async function getConfig(baseDir) { if (!globalConfig) { const loader = new ConfigLoader(); globalConfig = await loader.load(baseDir); } return globalConfig; } /** * Reset config (for testing) */ function resetConfig() { globalConfig = null; } module.exports = { defaultConfig, ConfigLoader, defineConfig, getConfig, resetConfig };