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