Monorepo for Aesthetic.Computer aesthetic.computer
at main 196 lines 6.0 kB view raw
1#!/usr/bin/env node 2 3// Utility to extract and fetch all $codes from KidLisp source 4// Used by the Teia packer to create offline-capable packages 5 6import { promises as fs } from "fs"; 7import https from "https"; 8 9// Extract all $code references from KidLisp source text 10function extractCodes(source) { 11 const codes = new Set(); 12 13 // Match $codes in various contexts: 14 // - Standalone: $abc123XY 15 // - Function calls: ($abc123XY) 16 // - With parameters: ($abc123XY 128 128) 17 // - In embed calls: (embed $abc123XY) 18 // - In jump calls: (jump $abc123XY) 19 const codePattern = /\$([0-9A-Za-z]{3,12})\b/g; 20 21 let match; 22 while ((match = codePattern.exec(source)) !== null) { 23 codes.add(match[1]); // Add the code without the $ prefix 24 } 25 26 return Array.from(codes); 27} 28 29// Fetch a single code from the aesthetic.computer API 30async function fetchCode(codeId) { 31 const url = `https://aesthetic.computer/.netlify/functions/store-kidlisp?code=${codeId}`; 32 33 return new Promise((resolve, reject) => { 34 const options = { rejectUnauthorized: false }; // Allow self-signed certificates 35 36 https.get(url, options, (res) => { 37 let data = ''; 38 39 res.on('data', (chunk) => { 40 data += chunk; 41 }); 42 43 res.on('end', () => { 44 try { 45 const parsed = JSON.parse(data); 46 if (parsed && parsed.source) { 47 console.log(`✅ Fetched $${codeId} (${parsed.source.length} chars)`); 48 resolve({ id: codeId, source: parsed.source }); 49 } else { 50 console.error(`❌ No source found for $${codeId}`); 51 resolve(null); 52 } 53 } catch (err) { 54 console.error(`❌ Failed to parse response for $${codeId}:`, err); 55 resolve(null); 56 } 57 }); 58 }).on('error', (err) => { 59 console.error(`❌ Network error fetching $${codeId}:`, err); 60 resolve(null); 61 }); 62 }); 63} 64 65// Recursively fetch all codes and their dependencies 66async function fetchAllCodes(initialSource, maxDepth = 5) { 67 const allCodes = new Map(); // codeId -> { source, dependencies } 68 const toProcess = new Set(); 69 70 // Start with codes from the initial source 71 const initialCodes = extractCodes(initialSource); 72 console.log(`🔍 Found ${initialCodes.length} initial codes:`, initialCodes.map(c => `$${c}`).join(', ')); 73 74 for (const code of initialCodes) { 75 toProcess.add(code); 76 } 77 78 let depth = 0; 79 while (toProcess.size > 0 && depth < maxDepth) { 80 depth++; 81 console.log(`\n📚 Processing depth ${depth}...`); 82 83 const currentBatch = Array.from(toProcess); 84 toProcess.clear(); 85 86 // Fetch all codes in parallel 87 const promises = currentBatch.map(async (codeId) => { 88 if (allCodes.has(codeId)) return null; // Already processed 89 90 const result = await fetchCode(codeId); 91 if (result) { 92 const dependencies = extractCodes(result.source); 93 allCodes.set(codeId, { 94 source: result.source, 95 dependencies 96 }); 97 98 // Add new dependencies to next batch 99 for (const dep of dependencies) { 100 if (!allCodes.has(dep)) { 101 toProcess.add(dep); 102 } 103 } 104 105 if (dependencies.length > 0) { 106 console.log(` $${codeId} depends on: ${dependencies.map(c => `$${c}`).join(', ')}`); 107 } 108 } 109 return result; 110 }); 111 112 await Promise.all(promises); 113 } 114 115 if (toProcess.size > 0) { 116 console.log(`⚠️ Reached max depth ${maxDepth}, ${toProcess.size} codes not processed:`, 117 Array.from(toProcess).map(c => `$${c}`).join(', ')); 118 } 119 120 return allCodes; 121} 122 123// Generate JavaScript code to pre-populate the cache 124function generateCacheCode(codesMap) { 125 const entries = Array.from(codesMap.entries()).map(([id, data]) => { 126 return ` "${id}": ${JSON.stringify(data.source)}`; 127 }).join(',\n'); 128 129 return ` 130// 🗂️ Pre-populated KidLisp code cache for PACK mode 131// Generated by kidlisp-extractor.mjs 132 133// Define OBJKT KidLisp codes for kidlisp.mjs to auto-load 134// Set on multiple global objects for compatibility with main thread and worker contexts 135const objktKidlispCodes = { 136${entries} 137}; 138 139// Set cache on all available global scopes 140if (typeof window !== 'undefined') { 141 window.objktKidlispCodes = objktKidlispCodes; 142} 143if (typeof globalThis !== 'undefined') { 144 globalThis.objktKidlispCodes = objktKidlispCodes; 145} 146if (typeof global !== 'undefined') { 147 global.objktKidlispCodes = objktKidlispCodes; 148} 149if (typeof self !== 'undefined') { 150 self.objktKidlispCodes = objktKidlispCodes; 151} 152 153console.log('🎯 OBJKT cache defined with ${codesMap.size} codes');`; 154} 155 156// Main function 157async function extractAndFetch(sourceFile, outputFile) { 158 try { 159 console.log(`📖 Reading source from: ${sourceFile}`); 160 const source = await fs.readFile(sourceFile, 'utf8'); 161 162 console.log(`🔍 Extracting codes from source...`); 163 const codesMap = await fetchAllCodes(source); 164 165 console.log(`\n📦 Summary:`); 166 console.log(` Total codes fetched: ${codesMap.size}`); 167 for (const [id, data] of codesMap) { 168 console.log(` $${id}: ${data.source.length} chars, ${data.dependencies.length} deps`); 169 } 170 171 const cacheCode = generateCacheCode(codesMap); 172 await fs.writeFile(outputFile, cacheCode); 173 174 console.log(`\n✅ Cache code written to: ${outputFile}`); 175 return { codesMap, cacheCode }; 176 177 } catch (error) { 178 console.error('❌ Error:', error); 179 throw error; 180 } 181} 182 183// CLI usage 184if (import.meta.url === `file://${process.argv[1]}`) { 185 const [sourceFile, outputFile] = process.argv.slice(2); 186 187 if (!sourceFile || !outputFile) { 188 console.log('Usage: node kidlisp-extractor.mjs <source-file> <output-file>'); 189 console.log('Example: node kidlisp-extractor.mjs cow-source.lisp cache-preload.js'); 190 process.exit(1); 191 } 192 193 extractAndFetch(sourceFile, outputFile).catch(console.error); 194} 195 196export { extractCodes, fetchCode, fetchAllCodes, generateCacheCode };