+4
-3
README.md
+4
-3
README.md
···
22
22
23
23
## Architecture
24
24
25
-
- **`/src`** - Main backend (OAuth, site management, custom domains)
26
-
- **`/hosting-service`** - Microservice that serves cached sites from disk
25
+
- **`/apps/main-app`** - Main backend (OAuth, site management, custom domains)
26
+
- **`/apps/hosting-service`** - Microservice that serves cached sites from disk
27
27
- **`/cli`** - Rust CLI for direct PDS uploads
28
-
- **`/public`** - React frontend
28
+
- **`/apps/main-app/public`** - React frontend
29
+
- **`/packages`** - Shared packages
29
30
30
31
### How it works
31
32
+22
apps/hosting-service/src/lib/firehose.ts
+22
apps/hosting-service/src/lib/firehose.ts
···
20
20
private idResolver: IdResolver
21
21
private isShuttingDown = false
22
22
private lastEventTime = Date.now()
23
+
private cacheCleanupInterval: NodeJS.Timeout | null = null
23
24
24
25
constructor(
25
26
private logger?: (msg: string, data?: Record<string, unknown>) => void
26
27
) {
27
28
this.idResolver = new IdResolver()
29
+
this.startCacheCleanup()
28
30
}
29
31
30
32
private log(msg: string, data?: Record<string, unknown>) {
···
32
34
log(`[FirehoseWorker] ${msg}`, data || {})
33
35
}
34
36
37
+
private startCacheCleanup() {
38
+
// Clear IdResolver cache every hour to prevent unbounded memory growth
39
+
// The IdResolver has an internal cache that never expires and can cause heap exhaustion
40
+
this.cacheCleanupInterval = setInterval(() => {
41
+
if (this.isShuttingDown) return
42
+
43
+
this.log('Clearing IdResolver cache to prevent memory leak')
44
+
45
+
// Recreate the IdResolver to clear its internal cache
46
+
this.idResolver = new IdResolver()
47
+
48
+
this.log('IdResolver cache cleared')
49
+
}, 60 * 60 * 1000) // Every hour
50
+
}
51
+
35
52
start() {
36
53
this.log('Starting firehose worker')
37
54
this.connect()
···
40
57
stop() {
41
58
this.log('Stopping firehose worker')
42
59
this.isShuttingDown = true
60
+
61
+
if (this.cacheCleanupInterval) {
62
+
clearInterval(this.cacheCleanupInterval)
63
+
this.cacheCleanupInterval = null
64
+
}
43
65
44
66
if (this.firehose) {
45
67
this.firehose.destroy()
+6
-6
apps/main-app/public/editor/tabs/CLITab.tsx
+6
-6
apps/main-app/public/editor/tabs/CLITab.tsx
···
17
17
<div className="flex items-center gap-2 mb-2">
18
18
<CardTitle>Wisp CLI Tool</CardTitle>
19
19
<Badge variant="secondary" className="text-xs">
20
-
v0.4.0
20
+
v0.4.1
21
21
</Badge>
22
22
<Badge variant="outline" className="text-xs">
23
23
Alpha
···
55
55
</div>
56
56
57
57
<div className="space-y-3">
58
-
<h3 className="text-sm font-semibold">Download v0.4.0</h3>
58
+
<h3 className="text-sm font-semibold">Download v0.4.1</h3>
59
59
<div className="grid gap-2">
60
60
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
61
61
<a
···
71
71
</a>
72
72
<div className="text-xs text-muted-foreground">
73
73
<span className="font-mono">
74
-
SHA-1: 2b5c1d6d0e21f9d764dd66ef869bfcd348e8a111
74
+
SHA-1: 69de880e160a2f26e3c26b7c28030457913754dc
75
75
</span>
76
76
</div>
77
77
</div>
···
87
87
</a>
88
88
<div className="text-xs text-muted-foreground">
89
89
<span className="font-mono">
90
-
SHA-1: 68d4a3831c07d2f32fdde8d3e809af1ab79e804e
90
+
SHA-1: 026189f2c10077a2be4705102e169e66cb207f84
91
91
</span>
92
92
</div>
93
93
</div>
···
103
103
</a>
104
104
<div className="text-xs text-muted-foreground">
105
105
<span className="font-mono">
106
-
SHA-1: 86e89f592b0ec53239c082f502cbe7a47ed8bbec
106
+
SHA-1: 4777cff07558906717402484a3313359880eae96
107
107
</span>
108
108
</div>
109
109
</div>
···
119
119
</a>
120
120
<div className="text-xs text-muted-foreground">
121
121
<span className="font-mono">
122
-
SHA-1: 227b735911ad260cff5af3ca3beefa4e1e3956a8
122
+
SHA-1: d6bc789d7fd7c787e5520eb476cee70c97ccb3ce
123
123
</span>
124
124
</div>
125
125
</div>
+9
-7
apps/main-app/src/index.ts
+9
-7
apps/main-app/src/index.ts
···
148
148
}
149
149
})
150
150
.get('/api/screenshots', async () => {
151
-
const { Glob } = await import('bun')
152
-
const glob = new Glob('*.png')
153
-
154
-
// Convert async iterator to array using Array.fromAsync
155
-
const files = glob.scan('./apps/main-app/public/screenshots')
156
-
const screenshots = await Array.fromAsync(files)
151
+
const fs = await import('fs/promises')
157
152
158
-
return { screenshots }
153
+
try {
154
+
const screenshotsDir = './apps/main-app/public/screenshots'
155
+
const files = await fs.readdir(screenshotsDir)
156
+
const screenshots = files.filter(file => file.endsWith('.png'))
157
+
return { screenshots }
158
+
} catch (error) {
159
+
return { screenshots: [] }
160
+
}
159
161
})
160
162
.get('/api/admin/test', () => {
161
163
return { message: 'Admin routes test works!' }
+6
-6
binaries/index.html
+6
-6
binaries/index.html
···
234
234
</head>
235
235
<body>
236
236
<div class="container">
237
-
<h1>Wisp CLI <span style="font-size: 1.5rem; color: var(--demo-text-secondary);">v0.4.0</span></h1>
237
+
<h1>Wisp CLI <span style="font-size: 1.5rem; color: var(--demo-text-secondary);">v0.4.1</span></h1>
238
238
<p class="subtitle">Deploy static sites to the AT Protocol</p>
239
239
240
240
<div class="description">
···
256
256
</div>
257
257
258
258
<div class="downloads">
259
-
<h2>Download v0.4.0</h2>
259
+
<h2>Download v0.4.1</h2>
260
260
<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" class="download-link" download>
261
261
<span class="platform">macOS (Apple Silicon):</span> wisp-cli-aarch64-darwin
262
262
</a>
···
271
271
</a>
272
272
273
273
<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">SHA-1 Checksums</h3>
274
-
<pre style="font-size: 0.75rem; padding: 1rem;"><code class="language-bash">2b5c1d6d0e21f9d764dd66ef869bfcd348e8a111 wisp-cli-aarch64-darwin
275
-
68d4a3831c07d2f32fdde8d3e809af1ab79e804e wisp-cli-aarch64-linux
276
-
86e89f592b0ec53239c082f502cbe7a47ed8bbec wisp-cli-x86_64-linux
277
-
227b735911ad260cff5af3ca3beefa4e1e3956a8 wisp-cli-x86_64-windows.exe</code></pre>
274
+
<pre style="font-size: 0.75rem; padding: 1rem;"><code class="language-bash">69de880e160a2f26e3c26b7c28030457913754dc wisp-cli-aarch64-darwin
275
+
026189f2c10077a2be4705102e169e66cb207f84 wisp-cli-aarch64-linux
276
+
4777cff07558906717402484a3313359880eae96 wisp-cli-x86_64-linux
277
+
d6bc789d7fd7c787e5520eb476cee70c97ccb3ce wisp-cli-x86_64-windows.exe</code></pre>
278
278
</div>
279
279
280
280
<div class="cicd-section">
binaries/wisp-cli-aarch64-darwin
binaries/wisp-cli-aarch64-darwin
This is a binary file and will not be displayed.
binaries/wisp-cli-aarch64-linux
binaries/wisp-cli-aarch64-linux
This is a binary file and will not be displayed.
binaries/wisp-cli-x86_64-linux
binaries/wisp-cli-x86_64-linux
This is a binary file and will not be displayed.
binaries/wisp-cli-x86_64-windows.exe
binaries/wisp-cli-x86_64-windows.exe
This is a binary file and will not be displayed.
+6
-6
docs/src/content/docs/cli.md
+6
-6
docs/src/content/docs/cli.md
···
1
1
---
2
-
title: Wisp CLI 0.4.0 (alpha)
2
+
title: Wisp CLI 0.4.1 (alpha)
3
3
description: Command-line tool for deploying static sites to the AT Protocol
4
4
---
5
5
···
19
19
20
20
<div class="downloads">
21
21
22
-
<h2>Download v0.4.0</h2>
22
+
<h2>Download v0.4.1</h2>
23
23
24
24
<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" class="download-link" download="">
25
25
···
48
48
<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">SHA-1 Checksums</h3>
49
49
50
50
<pre style="font-size: 0.75rem; padding: 1rem;" class="language-bash" tabindex="0"><code class="language-bash">
51
-
2b5c1d6d0e21f9d764dd66ef869bfcd348e8a111 wisp-cli-aarch64-darwin
51
+
69de880e160a2f26e3c26b7c28030457913754dc wisp-cli-aarch64-darwin
52
52
53
-
68d4a3831c07d2f32fdde8d3e809af1ab79e804e wisp-cli-aarch64-linux
53
+
026189f2c10077a2be4705102e169e66cb207f84 wisp-cli-aarch64-linux
54
54
55
-
86e89f592b0ec53239c082f502cbe7a47ed8bbec wisp-cli-x86_64-linux
55
+
4777cff07558906717402484a3313359880eae96 wisp-cli-x86_64-linux
56
56
57
-
227b735911ad260cff5af3ca3beefa4e1e3956a8 wisp-cli-x86_64-windows.exe
57
+
d6bc789d7fd7c787e5520eb476cee70c97ccb3ce wisp-cli-x86_64-windows.exe
58
58
</code></pre>
59
59
60
60
</div>