Monorepo for Aesthetic.Computer
aesthetic.computer
1# Netlify Function Plan: `bundle-keep-html.mjs` → API Endpoint
2
3## Overview
4
5Convert the existing `tezos/bundle-keep-html.mjs` CLI tool into a Netlify serverless function that can generate KidLisp bundles on-demand via HTTP requests.
6
7**Current CLI Usage:**
8```bash
9node tezos/bundle-keep-html.mjs 39j
10```
11
12**Proposed API Usage:**
13```
14GET https://aesthetic.computer/api/bundle-html?code=39j
15# Returns: .lisp.html file as download or base64 response
16```
17
18---
19
20## Technical Challenges & Solutions
21
22### 1. **File System Access**
23| Challenge | Current CLI | Netlify Function Solution |
24|-----------|-------------|---------------------------|
25| Reading source files | `fs.readFile()` from disk | Use `included_files` in `netlify.toml` to bundle assets |
26| Writing output | `fs.writeFile()` to `keep-bundles/` | Return directly in HTTP response (no file write needed) |
27| Git version info | `execSync('git rev-parse')` | Use build-time env var or hardcode at deploy time |
28
29### 2. **Dependencies**
30| Dependency | Purpose | Netlify Compatible? |
31|------------|---------|---------------------|
32| `@swc/wasm` | JS minification | ✅ WASM version - drop-in replacement for `@swc/core` |
33| `zlib.gzipSync` | Compression | ✅ Built into Node.js |
34| `fetch` | API calls | ✅ Built into Node 18+ |
35
36> **Note:** `@swc/wasm` is the WebAssembly version of SWC with the exact same API as `@swc/core`.
37> Simply change the import from `@swc/core` to `@swc/wasm` - no other code changes needed.
38> - 1.2M+ weekly downloads on npm
39> - Same `transform()` function signature
40> - No native binaries, works in serverless environments
41
42**Required Package Change:**
43```bash
44# Add to system/package.json
45cd system && npm install @swc/wasm
46```
47
48### 3. **Execution Time**
49- **Current bundle time:** ~5-15 seconds (depending on piece complexity)
50- **Netlify timeout:** 10 seconds (default), 26 seconds (background functions)
51- **Solution:** Use background function or optimize for speed
52
53---
54
55## Implementation Plan
56
57### Phase 1: Create the Function Structure ✅ IMPLEMENTED
58
59```
60system/netlify/functions/bundle-html.mjs # Main function (all-in-one)
61```
62
63### Phase 2: Configure `netlify.toml` ✅ IMPLEMENTED
64
65```toml
66[functions.bundle-html]
67# Include all aesthetic.computer source files needed for VFS
68included_files = [
69 "public/aesthetic.computer/**/*.mjs",
70 "public/aesthetic.computer/**/*.js",
71 "public/aesthetic.computer/disks/drawings/font_1/**/*.json",
72 "public/aesthetic.computer/dep/**/*.mjs",
73 "backend/**/*.mjs"
74]
75
76# @swc/wasm is the WASM version of SWC - same API as @swc/core
77# It needs to be marked as external so Netlify includes the .wasm files
78external_node_modules = ["@swc/wasm"]
79
80# Environment variables
81included_env_vars = ["GIT_COMMIT", "CONTEXT"]
82
83# Extended timeout for bundle generation (may take 5-15s)
84# Note: Background functions get 26s timeout if needed
85```
86
87**Key Configuration Notes:**
88- `@swc/wasm` must be in `external_node_modules` so Netlify properly bundles the WASM binary
89- The WASM file is ~15MB but loads fast at runtime
90- No native compilation required - works on any Node.js runtime
91
92### Phase 3: API Design
93
94#### Request
95```
96GET /api/bundle-html?code=<piece-code>
97
98Query Parameters:
99 code (required) KidLisp piece code without $ (e.g., "39j")
100 format (optional) "html" (default) | "base64" | "json"
101```
102
103#### Response Formats
104
105**HTML (default)** - Direct download:
106```
107Content-Type: text/html
108Content-Disposition: attachment; filename="$39j-@fifi-2025.11.27.12.30.00.000.lisp.html"
109
110<!DOCTYPE html>...
111```
112
113**Base64** - For programmatic use:
114```json
115{
116 "filename": "$39j-@fifi-2025.11.27.12.30.00.000.lisp.html",
117 "content": "PCFET0NUWVBFIGh0bWw+...",
118 "sizeKB": 180,
119 "author": "@fifi"
120}
121```
122
123### Phase 4: Code Migration
124
125#### Key Changes from CLI to Function
126
127| CLI Code | Function Equivalent |
128|----------|---------------------|
129| `process.argv[2]` | `event.queryStringParameters.code` |
130| `fs.readFile(fullPath)` | `fs.readFile(path.join(process.cwd(), 'public/aesthetic.computer', relativePath))` |
131| `fs.writeFile(outputPath, content)` | `return { body: content, headers: {...} }` |
132| `execSync('git rev-parse')` | `process.env.GIT_COMMIT` or hardcoded |
133| `console.log(...)` | Kept for CloudWatch logs |
134
135#### Function Skeleton
136
137```javascript
138// system/netlify/functions/bundle-keep.mjs
139
140import { promises as fs } from "fs";
141import path from "path";
142import { gzipSync } from "zlib";
143import { transform } from "@swc/wasm"; // Same API as @swc/core!
144import { respond } from "../../backend/http.mjs";
145
146// Essential files list (same as CLI)
147const ESSENTIAL_FILES = [
148 'boot.mjs', 'bios.mjs', 'lib/loop.mjs', 'lib/disk.mjs',
149 // ... rest of essential files
150];
151
152export async function handler(event) {
153 const code = event.queryStringParameters?.code;
154
155 if (!code) {
156 return respond(400, { error: "Missing 'code' parameter" });
157 }
158
159 try {
160 // 1. Fetch KidLisp source from API
161 const kidlispSources = await getKidLispSourceWithDeps(code);
162
163 // 2. Build VFS from included files
164 const vfs = await buildVFS(kidlispSources);
165
166 // 3. Generate HTML bundle
167 const html = generateBundle(code, kidlispSources, vfs);
168
169 // 4. Compress with gzip
170 const compressed = createGzipBundle(html);
171
172 // 5. Return as download
173 const filename = generateFilename(code);
174 return {
175 statusCode: 200,
176 headers: {
177 "Content-Type": "text/html",
178 "Content-Disposition": `attachment; filename="${filename}"`,
179 },
180 body: compressed,
181 };
182
183 } catch (error) {
184 return respond(500, { error: error.message });
185 }
186}
187```
188
189---
190
191## File Changes Required
192
193### New Files
1941. `system/netlify/functions/bundle-keep.mjs` - Main function
1952. `system/netlify/functions/bundle-keep/helpers.mjs` - Shared utilities
196
197### Modified Files
1981. `system/netlify.toml` - Add function configuration
199
200### Optional Refactoring
201- Extract shared code from `tezos/bundle-keep-html.mjs` into a shared module that both CLI and Netlify function can use
202
203---
204
205## Risks & Mitigations
206
207| Risk | Likelihood | Impact | Mitigation |
208|------|------------|--------|------------|
209| ~~SWC native binary incompatible~~ | ~~High~~ | ~~Blocker~~ | ✅ **SOLVED:** Use `@swc/wasm` (WASM version) |
210| Timeout on large pieces | Medium | Degraded UX | Use background function or caching |
211| VFS files missing at runtime | Medium | Broken bundles | Extensive `included_files` config |
212| Memory limits (~1GB) | Low | Failure | Already well under limit |
213| WASM cold start | Low | Slower first request | ~100-200ms overhead, acceptable |
214
215---
216
217## Testing Strategy
218
2191. **Local Testing:**
220 ```bash
221 netlify dev
222 curl "http://localhost:8888/api/bundle-keep?code=39j" > test.html
223 open test.html
224 ```
225
2262. **Deploy Preview:**
227 - Push to branch, test on deploy preview URL
228 - Verify paintings load, fonts work, piece runs
229
2303. **Production Validation:**
231 - Compare output to CLI-generated bundle
232 - Verify byte-for-byte similarity (minus timestamps)
233
234---
235
236## Estimated Effort
237
238| Phase | Time Estimate |
239|-------|---------------|
240| Phase 1: Function structure | 2-3 hours |
241| Phase 2: netlify.toml config | 30 min |
242| Phase 3: API implementation | 2-3 hours |
243| Phase 4: Testing & debugging | 2-4 hours |
244| **Total** | **7-11 hours** |
245
246---
247
248## Future Enhancements
249
2501. **Caching:** Store generated bundles in R2/S3 with TTL
2512. **Webhooks:** Trigger bundle regeneration on piece update
2523. **Batch Generation:** Generate multiple pieces in one request
2534. **IPFS Upload:** Optionally pin bundle to IPFS and return CID
254
255---
256
257## Decision Points
258
2591. **~~Should we use `esbuild` or try to make `@swc/core` work?~~**
260 - ✅ **RESOLVED:** Use `@swc/wasm` - it's the WASM version of SWC with identical API
261 - No code changes needed beyond `import { transform } from "@swc/wasm"`
262 - Keeps the exact same minification behavior as the CLI tool
263
2642. **Background function (26s timeout) or standard (10s)?**
265 - Recommendation: Start with standard, switch to background if needed
266
2673. **Should CLI and function share code?**
268 - Recommendation: Yes, create `tezos/bundle-keep-shared.mjs` module
269
2704. **Where to store generated bundles?**
271 - Option A: Don't store, generate on every request
272 - Option B: Cache in S3/R2 with piece code + git hash as key
273 - Recommendation: Option A first, add caching if needed