Monorepo for Aesthetic.Computer
aesthetic.computer
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 };