Files
LuHost/utils/config-parser.js
Nathan Schneider 8aadab1b50 Implement configuration-based mod management and fix navigation spacing
## Major Features Added

### Configuration-Based Mod Management
- Implement proper Luanti mod system using load_mod_* entries in world.mt
- Add mod enable/disable via configuration instead of file copying
- Support both global mods (config-enabled) and world mods (physically installed)
- Clear UI distinction with badges: "Global (Enabled)", "World Copy", "Missing"
- Automatic registry verification to sync database with filesystem state

### Game ID Alias System
- Fix minetest_game/minetest technical debt with proper alias mapping
- Map minetest_game → minetest for world.mt files (matches Luanti internal behavior)
- Reference: c9d4c33174/src/content/subgames.cpp (L21)

### Navigation Improvements
- Fix navigation menu spacing and text overflow issues
- Change "Configuration" to "Config" for better fit
- Implement responsive font sizing with clamp() for better scaling
- Even distribution of nav buttons across full width

### Package Registry Enhancements
- Add verifyAndCleanRegistry() to automatically remove stale package entries
- Periodic verification (every 5 minutes) to keep registry in sync with filesystem
- Fix "already installed" errors for manually deleted packages
- Integration across dashboard, ContentDB, and installation workflows

## Technical Improvements

### Mod System Architecture
- Enhanced ConfigParser to handle load_mod_* entries in world.mt files
- Support for both configuration-based and file-based mod installations
- Proper mod type detection and management workflows
- Updated world details to show comprehensive mod information

### UI/UX Enhancements
- Responsive navigation with proper text scaling
- Improved mod management interface with clear action buttons
- Better visual hierarchy and status indicators
- Enhanced error handling and user feedback

### Code Quality
- Clean up gitignore to properly exclude runtime files
- Add package-lock.json for consistent dependency management
- Remove excess runtime database and log files
- Add .claude/ directory to gitignore

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 10:31:08 -06:00

142 lines
3.8 KiB
JavaScript

const fs = require('fs').promises;
const path = require('path');
class ConfigParser {
static async parseConfig(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const config = {};
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const equalIndex = trimmed.indexOf('=');
if (equalIndex === -1) continue;
const key = trimmed.substring(0, equalIndex).trim();
const value = trimmed.substring(equalIndex + 1).trim();
config[key] = value;
}
return config;
} catch (error) {
if (error.code === 'ENOENT') {
return {};
}
throw error;
}
}
static async writeConfig(filePath, config) {
const lines = [];
for (const [key, value] of Object.entries(config)) {
if (value !== undefined && value !== null) {
lines.push(`${key} = ${value}`);
}
}
await fs.writeFile(filePath, lines.join('\n') + '\n', 'utf8');
}
static async parseModConfig(filePath) {
const config = await this.parseConfig(filePath);
if (config.depends) {
config.depends = config.depends.split(',').map(dep => dep.trim()).filter(Boolean);
} else {
config.depends = [];
}
if (config.optional_depends) {
config.optional_depends = config.optional_depends.split(',').map(dep => dep.trim()).filter(Boolean);
} else {
config.optional_depends = [];
}
return config;
}
static async writeModConfig(filePath, config) {
const configCopy = { ...config };
if (Array.isArray(configCopy.depends)) {
configCopy.depends = configCopy.depends.join(', ');
}
if (Array.isArray(configCopy.optional_depends)) {
configCopy.optional_depends = configCopy.optional_depends.join(', ');
}
await this.writeConfig(filePath, configCopy);
}
static async parseWorldConfig(filePath) {
const config = await this.parseConfig(filePath);
const booleanFields = ['creative_mode', 'enable_damage', 'enable_pvp', 'server_announce'];
for (const field of booleanFields) {
if (config[field] !== undefined) {
config[field] = config[field] === 'true';
}
}
// Extract mod configurations
config.enabled_mods = {};
for (const [key, value] of Object.entries(config)) {
if (key.startsWith('load_mod_')) {
const modName = key.replace('load_mod_', '');
config.enabled_mods[modName] = value === 'true';
}
}
return config;
}
static async writeWorldConfig(filePath, config) {
const configCopy = { ...config };
const booleanFields = ['creative_mode', 'enable_damage', 'enable_pvp', 'server_announce'];
for (const field of booleanFields) {
if (typeof configCopy[field] === 'boolean') {
configCopy[field] = configCopy[field].toString();
}
}
// Handle mod configurations
if (configCopy.enabled_mods) {
for (const [modName, enabled] of Object.entries(configCopy.enabled_mods)) {
configCopy[`load_mod_${modName}`] = enabled.toString();
}
delete configCopy.enabled_mods; // Remove the helper object
}
await this.writeConfig(filePath, configCopy);
}
static async parseGameConfig(filePath) {
const config = await this.parseConfig(filePath);
// Parse common game config fields
if (config.name) {
config.name = config.name.trim();
}
if (config.title) {
config.title = config.title.trim();
}
if (config.description) {
config.description = config.description.trim();
}
if (config.author) {
config.author = config.author.trim();
}
return config;
}
}
module.exports = ConfigParser;