Skip to main content
Glama
generate-mod-examples-manifest.ts11.1 kB
#!/usr/bin/env npx tsx /** * Mod Examples Manifest Generator * * Generates a manifest file for the mod-examples.db database * with version, hash, and metadata for distribution. * * NOT included in npm package - for local/maintainer use only. */ import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; import Database from 'better-sqlite3'; // ═══════════════════════════════════════════════════════════════════════════════ // CONFIGURATION // ═══════════════════════════════════════════════════════════════════════════════ const CONFIG = { dbPath: path.join(process.cwd(), 'data', 'mod-examples.db'), manifestPath: path.join(process.cwd(), 'data', 'mod-examples-manifest.json'), downloadUrlTemplate: 'https://github.com/OGMatrix/mcmodding-mcp/releases/download/examples-v{version}/mod-examples.db', }; // ═══════════════════════════════════════════════════════════════════════════════ // INTERFACES // ═══════════════════════════════════════════════════════════════════════════════ interface ModExamplesManifest { version: string; timestamp: string; hash: string; size: number; downloadUrl: string; stats: { mods: number; examples: number; relations: number; categories: number; featuredExamples: number; }; mods: Array<{ name: string; repo: string; loader: string; exampleCount: number; categories: string[]; starCount: number; }>; topCategories: Array<{ slug: string; name: string; exampleCount: number; }>; qualityMetrics: { avgQualityScore: number; highQualityCount: number; // score >= 0.7 expertLevelCount: number; advancedLevelCount: number; }; changelog: string; } // ═══════════════════════════════════════════════════════════════════════════════ // UTILITY FUNCTIONS // ═══════════════════════════════════════════════════════════════════════════════ function calculateFileHash(filePath: string): Promise<string> { return new Promise((resolve, reject) => { const hash = crypto.createHash('sha256'); const stream = fs.createReadStream(filePath); stream.on('data', (data) => hash.update(data)); stream.on('end', () => resolve(hash.digest('hex'))); stream.on('error', reject); }); } function log(level: 'info' | 'warn' | 'error' | 'success', message: string): void { const icons = { info: 'ℹ️', warn: '⚠️', error: '❌', success: '✅' }; const colors = { info: '\x1b[36m', warn: '\x1b[33m', error: '\x1b[31m', success: '\x1b[32m' }; const reset = '\x1b[0m'; const method = level === 'error' ? console.error : level === 'warn' ? console.warn : console.info; method(`${colors[level]}${icons[level]} ${message}${reset}`); } // ═══════════════════════════════════════════════════════════════════════════════ // MAIN LOGIC // ═══════════════════════════════════════════════════════════════════════════════ async function generateManifest(): Promise<void> { console.info('\n'); console.info('╔══════════════════════════════════════════════════════════════╗'); console.info('║ Mod Examples Manifest Generator ║'); console.info('╚══════════════════════════════════════════════════════════════╝'); console.info('\n'); // Parse command line arguments const args = process.argv.slice(2); const packageJson = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8') ); const version = args.find((a) => a.startsWith('--version='))?.split('=')[1] || packageJson.version; const changelog = args.find((a) => a.startsWith('--changelog='))?.split('=')[1] || 'Mod examples update'; // Check database exists if (!fs.existsSync(CONFIG.dbPath)) { log('error', `Database not found: ${CONFIG.dbPath}`); log('info', 'Run the mod indexer first: npx tsx scripts/index-mod-examples.ts'); process.exit(1); } log('info', `Database: ${CONFIG.dbPath}`); log('info', `Version: ${version}`); // Calculate hash log('info', 'Calculating file hash...'); const hash = await calculateFileHash(CONFIG.dbPath); const stats = fs.statSync(CONFIG.dbPath); log('success', `Hash: ${hash.slice(0, 16)}...`); log('success', `Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); // Open database and gather statistics log('info', 'Gathering database statistics...'); const db = new Database(CONFIG.dbPath, { readonly: true }); // Basic stats const basicStats = db .prepare( ` SELECT (SELECT COUNT(*) FROM mods) as mods, (SELECT COUNT(*) FROM examples) as examples, (SELECT COUNT(*) FROM example_relations) as relations, (SELECT COUNT(*) FROM categories) as categories, (SELECT COUNT(*) FROM examples WHERE is_featured = TRUE) as featured ` ) .get() as { mods: number; examples: number; relations: number; categories: number; featured: number; }; // Mod details const modDetails = db .prepare( ` SELECT m.name, m.repo, m.loader, m.star_count as starCount, COUNT(e.id) as exampleCount, GROUP_CONCAT(DISTINCT c.slug) as categories FROM mods m LEFT JOIN examples e ON e.mod_id = m.id LEFT JOIN categories c ON e.category_id = c.id GROUP BY m.id ORDER BY m.priority DESC, exampleCount DESC ` ) .all() as Array<{ name: string; repo: string; loader: string; starCount: number; exampleCount: number; categories: string | null; }>; // Top categories const topCategories = db .prepare( ` SELECT c.slug, c.name, COUNT(e.id) as exampleCount FROM categories c LEFT JOIN examples e ON e.category_id = c.id GROUP BY c.id HAVING exampleCount > 0 ORDER BY exampleCount DESC LIMIT 10 ` ) .all() as Array<{ slug: string; name: string; exampleCount: number; }>; // Quality metrics const qualityMetrics = db .prepare( ` SELECT AVG(quality_score) as avgQualityScore, SUM(CASE WHEN quality_score >= 0.7 THEN 1 ELSE 0 END) as highQualityCount, SUM(CASE WHEN complexity = 'expert' THEN 1 ELSE 0 END) as expertLevelCount, SUM(CASE WHEN complexity = 'advanced' THEN 1 ELSE 0 END) as advancedLevelCount FROM examples ` ) .get() as { avgQualityScore: number; highQualityCount: number; expertLevelCount: number; advancedLevelCount: number; }; db.close(); // Build manifest const manifest: ModExamplesManifest = { version, timestamp: new Date().toISOString(), hash, size: stats.size, downloadUrl: CONFIG.downloadUrlTemplate.replace('{version}', version), stats: { mods: basicStats.mods, examples: basicStats.examples, relations: basicStats.relations, categories: basicStats.categories, featuredExamples: basicStats.featured, }, mods: modDetails.map((m) => ({ name: m.name, repo: m.repo, loader: m.loader, exampleCount: m.exampleCount, categories: m.categories?.split(',').filter(Boolean) || [], starCount: m.starCount, })), topCategories, qualityMetrics: { avgQualityScore: Math.round((qualityMetrics.avgQualityScore || 0) * 100) / 100, highQualityCount: qualityMetrics.highQualityCount || 0, expertLevelCount: qualityMetrics.expertLevelCount || 0, advancedLevelCount: qualityMetrics.advancedLevelCount || 0, }, changelog, }; // Write manifest fs.writeFileSync(CONFIG.manifestPath, JSON.stringify(manifest, null, 2)); log('success', `Manifest written: ${CONFIG.manifestPath}`); // Print summary console.info('\n'); console.info('╔══════════════════════════════════════════════════════════════╗'); console.info('║ Manifest Generated Successfully ║'); console.info('╠══════════════════════════════════════════════════════════════╣'); console.info(`║ 📦 Version: ${version.padEnd(10)} ║`); console.info( `║ 📝 Total Examples: ${String(basicStats.examples).padStart(6)} ║` ); console.info( `║ 🎯 High Quality: ${String(qualityMetrics.highQualityCount).padStart(6)} ║` ); console.info( `║ ⭐ Featured: ${String(basicStats.featured).padStart(6)} ║` ); console.info( `║ 📊 Avg Quality: ${(qualityMetrics.avgQualityScore || 0).toFixed(2).padStart(6)} ║` ); console.info('╚══════════════════════════════════════════════════════════════╝'); console.info('\n'); // Print mods summary console.info('Indexed Mods:'); console.info('─'.repeat(60)); for (const mod of manifest.mods) { console.info( ` ${mod.name.padEnd(25)} ${String(mod.exampleCount).padStart(4)} examples ⭐ ${mod.starCount}` ); } console.info('\n'); } generateManifest().catch((error: unknown) => { const message = error instanceof Error ? error.message : String(error); log('error', `Fatal error: ${message}`); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OGMatrix/mcmodding-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server