veko
Version:
Ultra-lightweight Node.js framework with ZERO dependencies - VSV components, Tailwind CSS, asset imports, SSR
314 lines (271 loc) • 7.43 kB
JavaScript
/**
* 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
};