# Netlify Function Plan: `bundle-keep-html.mjs` → API Endpoint
## Overview
Convert the existing `tezos/bundle-keep-html.mjs` CLI tool into a Netlify serverless function that can generate KidLisp bundles on-demand via HTTP requests.
**Current CLI Usage:**
```bash
node tezos/bundle-keep-html.mjs 39j
```
**Proposed API Usage:**
```
GET https://aesthetic.computer/api/bundle-html?code=39j
# Returns: .lisp.html file as download or base64 response
```
---
## Technical Challenges & Solutions
### 1. **File System Access**
| Challenge | Current CLI | Netlify Function Solution |
|-----------|-------------|---------------------------|
| Reading source files | `fs.readFile()` from disk | Use `included_files` in `netlify.toml` to bundle assets |
| Writing output | `fs.writeFile()` to `keep-bundles/` | Return directly in HTTP response (no file write needed) |
| Git version info | `execSync('git rev-parse')` | Use build-time env var or hardcode at deploy time |
### 2. **Dependencies**
| Dependency | Purpose | Netlify Compatible? |
|------------|---------|---------------------|
| `@swc/wasm` | JS minification | ✅ WASM version - drop-in replacement for `@swc/core` |
| `zlib.gzipSync` | Compression | ✅ Built into Node.js |
| `fetch` | API calls | ✅ Built into Node 18+ |
> **Note:** `@swc/wasm` is the WebAssembly version of SWC with the exact same API as `@swc/core`.
> Simply change the import from `@swc/core` to `@swc/wasm` - no other code changes needed.
> - 1.2M+ weekly downloads on npm
> - Same `transform()` function signature
> - No native binaries, works in serverless environments
**Required Package Change:**
```bash
# Add to system/package.json
cd system && npm install @swc/wasm
```
### 3. **Execution Time**
- **Current bundle time:** ~5-15 seconds (depending on piece complexity)
- **Netlify timeout:** 10 seconds (default), 26 seconds (background functions)
- **Solution:** Use background function or optimize for speed
---
## Implementation Plan
### Phase 1: Create the Function Structure ✅ IMPLEMENTED
```
system/netlify/functions/bundle-html.mjs # Main function (all-in-one)
```
### Phase 2: Configure `netlify.toml` ✅ IMPLEMENTED
```toml
[functions.bundle-html]
# Include all aesthetic.computer source files needed for VFS
included_files = [
"public/aesthetic.computer/**/*.mjs",
"public/aesthetic.computer/**/*.js",
"public/aesthetic.computer/disks/drawings/font_1/**/*.json",
"public/aesthetic.computer/dep/**/*.mjs",
"backend/**/*.mjs"
]
# @swc/wasm is the WASM version of SWC - same API as @swc/core
# It needs to be marked as external so Netlify includes the .wasm files
external_node_modules = ["@swc/wasm"]
# Environment variables
included_env_vars = ["GIT_COMMIT", "CONTEXT"]
# Extended timeout for bundle generation (may take 5-15s)
# Note: Background functions get 26s timeout if needed
```
**Key Configuration Notes:**
- `@swc/wasm` must be in `external_node_modules` so Netlify properly bundles the WASM binary
- The WASM file is ~15MB but loads fast at runtime
- No native compilation required - works on any Node.js runtime
### Phase 3: API Design
#### Request
```
GET /api/bundle-html?code=
Query Parameters:
code (required) KidLisp piece code without $ (e.g., "39j")
format (optional) "html" (default) | "base64" | "json"
```
#### Response Formats
**HTML (default)** - Direct download:
```
Content-Type: text/html
Content-Disposition: attachment; filename="$39j-@fifi-2025.11.27.12.30.00.000.lisp.html"
...
```
**Base64** - For programmatic use:
```json
{
"filename": "$39j-@fifi-2025.11.27.12.30.00.000.lisp.html",
"content": "PCFET0NUWVBFIGh0bWw+...",
"sizeKB": 180,
"author": "@fifi"
}
```
### Phase 4: Code Migration
#### Key Changes from CLI to Function
| CLI Code | Function Equivalent |
|----------|---------------------|
| `process.argv[2]` | `event.queryStringParameters.code` |
| `fs.readFile(fullPath)` | `fs.readFile(path.join(process.cwd(), 'public/aesthetic.computer', relativePath))` |
| `fs.writeFile(outputPath, content)` | `return { body: content, headers: {...} }` |
| `execSync('git rev-parse')` | `process.env.GIT_COMMIT` or hardcoded |
| `console.log(...)` | Kept for CloudWatch logs |
#### Function Skeleton
```javascript
// system/netlify/functions/bundle-keep.mjs
import { promises as fs } from "fs";
import path from "path";
import { gzipSync } from "zlib";
import { transform } from "@swc/wasm"; // Same API as @swc/core!
import { respond } from "../../backend/http.mjs";
// Essential files list (same as CLI)
const ESSENTIAL_FILES = [
'boot.mjs', 'bios.mjs', 'lib/loop.mjs', 'lib/disk.mjs',
// ... rest of essential files
];
export async function handler(event) {
const code = event.queryStringParameters?.code;
if (!code) {
return respond(400, { error: "Missing 'code' parameter" });
}
try {
// 1. Fetch KidLisp source from API
const kidlispSources = await getKidLispSourceWithDeps(code);
// 2. Build VFS from included files
const vfs = await buildVFS(kidlispSources);
// 3. Generate HTML bundle
const html = generateBundle(code, kidlispSources, vfs);
// 4. Compress with gzip
const compressed = createGzipBundle(html);
// 5. Return as download
const filename = generateFilename(code);
return {
statusCode: 200,
headers: {
"Content-Type": "text/html",
"Content-Disposition": `attachment; filename="${filename}"`,
},
body: compressed,
};
} catch (error) {
return respond(500, { error: error.message });
}
}
```
---
## File Changes Required
### New Files
1. `system/netlify/functions/bundle-keep.mjs` - Main function
2. `system/netlify/functions/bundle-keep/helpers.mjs` - Shared utilities
### Modified Files
1. `system/netlify.toml` - Add function configuration
### Optional Refactoring
- Extract shared code from `tezos/bundle-keep-html.mjs` into a shared module that both CLI and Netlify function can use
---
## Risks & Mitigations
| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| ~~SWC native binary incompatible~~ | ~~High~~ | ~~Blocker~~ | ✅ **SOLVED:** Use `@swc/wasm` (WASM version) |
| Timeout on large pieces | Medium | Degraded UX | Use background function or caching |
| VFS files missing at runtime | Medium | Broken bundles | Extensive `included_files` config |
| Memory limits (~1GB) | Low | Failure | Already well under limit |
| WASM cold start | Low | Slower first request | ~100-200ms overhead, acceptable |
---
## Testing Strategy
1. **Local Testing:**
```bash
netlify dev
curl "http://localhost:8888/api/bundle-keep?code=39j" > test.html
open test.html
```
2. **Deploy Preview:**
- Push to branch, test on deploy preview URL
- Verify paintings load, fonts work, piece runs
3. **Production Validation:**
- Compare output to CLI-generated bundle
- Verify byte-for-byte similarity (minus timestamps)
---
## Estimated Effort
| Phase | Time Estimate |
|-------|---------------|
| Phase 1: Function structure | 2-3 hours |
| Phase 2: netlify.toml config | 30 min |
| Phase 3: API implementation | 2-3 hours |
| Phase 4: Testing & debugging | 2-4 hours |
| **Total** | **7-11 hours** |
---
## Future Enhancements
1. **Caching:** Store generated bundles in R2/S3 with TTL
2. **Webhooks:** Trigger bundle regeneration on piece update
3. **Batch Generation:** Generate multiple pieces in one request
4. **IPFS Upload:** Optionally pin bundle to IPFS and return CID
---
## Decision Points
1. **~~Should we use `esbuild` or try to make `@swc/core` work?~~**
- ✅ **RESOLVED:** Use `@swc/wasm` - it's the WASM version of SWC with identical API
- No code changes needed beyond `import { transform } from "@swc/wasm"`
- Keeps the exact same minification behavior as the CLI tool
2. **Background function (26s timeout) or standard (10s)?**
- Recommendation: Start with standard, switch to background if needed
3. **Should CLI and function share code?**
- Recommendation: Yes, create `tezos/bundle-keep-shared.mjs` module
4. **Where to store generated bundles?**
- Option A: Don't store, generate on every request
- Option B: Cache in S3/R2 with piece code + git hash as key
- Recommendation: Option A first, add caching if needed