@tothalex/nulljs 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ui.tsx DELETED
@@ -1,363 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { createCliRenderer } from '@opentui/core'
3
- import { createRoot } from '@opentui/react'
4
- import {
5
- getSystemLogs,
6
- searchDeploymentLogs,
7
- type LogEntry,
8
- type SystemLogEntry
9
- } from '@nulljs/api'
10
-
11
- import { startServer, type BinarySource } from './lib/server'
12
- import { startWatcher, forceDeployAll } from './lib/watcher'
13
- import { startViteServer, stopViteServer } from './lib/vite'
14
- import { getOrCreateLocalDevConfig, type Config } from './config'
15
- import { DeploymentLogsPane } from './components/DeploymentLogsPane'
16
- import { SystemLogsPane } from './components/SystemLogsPane'
17
- import { DeployAnimation, type DeployedFunction } from './components/DeployAnimation'
18
- import { Header } from './components/Header'
19
- import { HelpModal } from './components/HelpModal'
20
-
21
- type WatcherEvent = {
22
- type: 'deploy' | 'change'
23
- deployed?: DeployedFunction[]
24
- filePath?: string
25
- success?: boolean
26
- skipped?: boolean
27
- error?: string
28
- affectedCount?: number
29
- timestamp: Date
30
- }
31
-
32
- const App = (props: {
33
- config: Config
34
- binarySource: BinarySource
35
- functionCount: number
36
- viteRunning: boolean
37
- onWatcherEvent: (handler: (event: WatcherEvent) => void) => void
38
- onForceDeploy: () => Promise<void>
39
- }) => {
40
- const [error, setError] = useState<string | null>(null)
41
- const [loading, setLoading] = useState(true)
42
- const [deploymentLogs, setDeploymentLogs] = useState<LogEntry[]>([])
43
- const [systemLogs, setSystemLogs] = useState<SystemLogEntry[]>([])
44
- const [activeTab, setActiveTab] = useState<'deployment' | 'system'>('system')
45
- const [autoScroll, setAutoScroll] = useState(true)
46
- const [jumpTrigger, setJumpTrigger] = useState(0)
47
- const [watcherStatus, setWatcherStatus] = useState<WatcherEvent | null>(null)
48
- const [showKeyBindings, setShowKeyBindings] = useState(false)
49
- const [isDeploying, setIsDeploying] = useState(false)
50
-
51
- useEffect(() => {
52
- props.onWatcherEvent((event) => {
53
- console.error('DEBUG: received watcher event:', event.type, event.deployed?.length)
54
- setWatcherStatus(event)
55
- })
56
- }, [props.onWatcherEvent])
57
-
58
- useEffect(() => {
59
- const fn = async () => {
60
- try {
61
- setLoading(true)
62
- setError(null)
63
-
64
- const [deploymentLogsData, systemLogsData] = await Promise.all([
65
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
66
- getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
67
- ])
68
-
69
- setDeploymentLogs(deploymentLogsData)
70
- setSystemLogs(systemLogsData)
71
- } catch (err) {
72
- setError(err instanceof Error ? err.message : String(err))
73
- console.error('Failed to fetch logs:', err)
74
- } finally {
75
- setLoading(false)
76
- }
77
- }
78
-
79
- fn()
80
- }, [props.config.key.public, props.config.api])
81
-
82
- // Poll for new logs every 2 seconds
83
- useEffect(() => {
84
- const interval = setInterval(async () => {
85
- if (loading) return
86
-
87
- try {
88
- const [newDeploymentLogs, newSystemLogs] = await Promise.all([
89
- searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
90
- getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
91
- ])
92
-
93
- // Only update if we have new logs
94
- if (newDeploymentLogs.length > 0) {
95
- setDeploymentLogs(newDeploymentLogs)
96
- }
97
- if (newSystemLogs.length > 0) {
98
- setSystemLogs(newSystemLogs)
99
- }
100
- } catch (err) {
101
- console.error('Failed to poll for new logs:', err)
102
- }
103
- }, 2000)
104
-
105
- return () => clearInterval(interval)
106
- }, [loading, props.config.key.public, props.config.api])
107
-
108
- useEffect(() => {
109
- const handleKeyPress = (data: Buffer) => {
110
- const key = data.toString()
111
-
112
- // Number keys to jump to specific panel
113
- if (key === '1') {
114
- setActiveTab('system')
115
- return
116
- }
117
- if (key === '2') {
118
- setActiveTab('deployment')
119
- return
120
- }
121
-
122
- // Tab key cycles between panels
123
- if (key === '\t') {
124
- setActiveTab((prev) => (prev === 'system' ? 'deployment' : 'system'))
125
- return
126
- }
127
-
128
- // h/l for vim-style navigation
129
- if (key === 'h') {
130
- setActiveTab('system')
131
- return
132
- }
133
- if (key === 'l') {
134
- setActiveTab('deployment')
135
- return
136
- }
137
-
138
- // g - jump to top and disable auto-scroll
139
- if (key === 'g') {
140
- setAutoScroll(false)
141
- setJumpTrigger((prev) => prev + 1)
142
- return
143
- }
144
-
145
- // Shift+G - jump to bottom and enable auto-scroll
146
- if (key === 'G') {
147
- setAutoScroll(true)
148
- setJumpTrigger((prev) => prev + 1)
149
- return
150
- }
151
-
152
- // ? - toggle key bindings modal
153
- if (key === '?') {
154
- setShowKeyBindings((prev) => !prev)
155
- return
156
- }
157
-
158
- // ESC - close key bindings modal
159
- if (key === '\x1b') {
160
- setShowKeyBindings(false)
161
- return
162
- }
163
-
164
- // d - force deploy all
165
- if (key === 'd' && !isDeploying) {
166
- setIsDeploying(true)
167
- props.onForceDeploy().finally(() => {
168
- setIsDeploying(false)
169
- })
170
- return
171
- }
172
- }
173
-
174
- process.stdin.setRawMode(true)
175
- process.stdin.on('data', handleKeyPress)
176
-
177
- return () => {
178
- process.stdin.setRawMode(false)
179
- process.stdin.off('data', handleKeyPress)
180
- }
181
- }, [isDeploying, props.onForceDeploy])
182
-
183
- if (error) {
184
- return (
185
- <box flexGrow={1} flexDirection="column" padding={1}>
186
- <text>
187
- <span fg="red">Error: {error}</span>
188
- </text>
189
- </box>
190
- )
191
- }
192
-
193
- return (
194
- <box flexDirection="column">
195
- <Header
196
- activeTab={activeTab}
197
- functionCount={props.functionCount}
198
- viteRunning={props.viteRunning}
199
- isDeploying={isDeploying}
200
- binarySource={props.binarySource}
201
- />
202
-
203
- <box paddingTop={1} flexDirection="column">
204
- {activeTab === 'system' ? (
205
- <SystemLogsPane
206
- logs={systemLogs}
207
- loading={loading}
208
- autoScroll={autoScroll}
209
- jumpTrigger={jumpTrigger}
210
- />
211
- ) : (
212
- <DeploymentLogsPane
213
- logs={deploymentLogs}
214
- loading={loading}
215
- autoScroll={autoScroll}
216
- jumpTrigger={jumpTrigger}
217
- />
218
- )}
219
- {watcherStatus?.type === 'deploy' &&
220
- watcherStatus.deployed &&
221
- watcherStatus.deployed.length > 0 && (
222
- <DeployAnimation
223
- deployed={watcherStatus.deployed}
224
- onComplete={() => {
225
- console.error('DEBUG: animation complete')
226
- setWatcherStatus(null)
227
- }}
228
- />
229
- )}
230
- </box>
231
-
232
- <HelpModal visible={showKeyBindings} />
233
- </box>
234
- )
235
- }
236
-
237
- const main = async () => {
238
- let serverInfo: Awaited<ReturnType<typeof startServer>> | null = null
239
- let viteInfo: Awaited<ReturnType<typeof startViteServer>> | null = null
240
- let stopWatcher: (() => void) | null = null
241
- let isCleaningUp = false
242
-
243
- const cleanup = async (exitAfter = true) => {
244
- if (isCleaningUp) return
245
- isCleaningUp = true
246
-
247
- if (stopWatcher) {
248
- stopWatcher()
249
- stopWatcher = null
250
- }
251
- if (viteInfo) {
252
- await stopViteServer(viteInfo)
253
- viteInfo = null
254
- }
255
- if (serverInfo) {
256
- const pid = serverInfo.process.pid
257
- serverInfo = null
258
- try {
259
- // Kill the server process and its children
260
- process.kill(pid, 'SIGTERM')
261
- } catch {
262
- // Process might already be dead
263
- }
264
- }
265
- if (exitAfter) {
266
- process.exit(0)
267
- }
268
- }
269
-
270
- process.on('SIGINT', () => cleanup(true))
271
- process.on('SIGTERM', () => cleanup(true))
272
- process.on('exit', () => cleanup(false))
273
-
274
- try {
275
- serverInfo = await startServer()
276
-
277
- const devConfig = await getOrCreateLocalDevConfig()
278
-
279
- const srcPath = process.cwd() + '/src'
280
-
281
- viteInfo = await startViteServer({
282
- srcDir: srcPath,
283
- onLog: (_msg, _level) => {}
284
- })
285
-
286
- let watcherEventHandler: ((event: WatcherEvent) => void) | null = null
287
- let pendingEvents: WatcherEvent[] = []
288
- let functionCount = 0
289
-
290
- const sendEvent = (event: WatcherEvent) => {
291
- if (watcherEventHandler) {
292
- watcherEventHandler(event)
293
- } else {
294
- pendingEvents.push(event)
295
- }
296
- }
297
-
298
- const renderer = await createCliRenderer()
299
- const root = createRoot(renderer)
300
-
301
- const handleForceDeploy = async () => {
302
- const deployResults: DeployedFunction[] = []
303
-
304
- await forceDeployAll(srcPath, {
305
- onDeploy: (filePath, success, error) => {
306
- deployResults.push({ name: filePath, success, error })
307
- }
308
- })
309
-
310
- if (deployResults.length > 0) {
311
- sendEvent({
312
- type: 'deploy',
313
- deployed: deployResults,
314
- timestamp: new Date()
315
- })
316
- }
317
- }
318
-
319
- // Start watcher after renderer is ready
320
- stopWatcher = await startWatcher(srcPath, {
321
- silent: true,
322
- onReady: (count) => {
323
- functionCount = count
324
- },
325
- onDeployBatch: (results) => {
326
- sendEvent({
327
- type: 'deploy',
328
- deployed: results.map((r) => ({
329
- name: r.name,
330
- success: r.success,
331
- error: r.error
332
- })),
333
- timestamp: new Date()
334
- })
335
- }
336
- })
337
-
338
- root.render(
339
- <App
340
- config={devConfig}
341
- binarySource={serverInfo.binarySource}
342
- functionCount={functionCount}
343
- viteRunning={viteInfo !== null}
344
- onWatcherEvent={(handler) => {
345
- watcherEventHandler = handler
346
- // Flush any pending events
347
- if (pendingEvents.length > 0) {
348
- for (const event of pendingEvents) {
349
- handler(event)
350
- }
351
- pendingEvents = []
352
- }
353
- }}
354
- onForceDeploy={handleForceDeploy}
355
- />
356
- )
357
- } catch (error) {
358
- console.error('Failed to start server:', error)
359
- cleanup()
360
- }
361
- }
362
-
363
- main()
package/tsconfig.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "jsxImportSource": "@opentui/react",
10
- "allowJs": true,
11
-
12
- // Bundler mode
13
- "moduleResolution": "bundler",
14
- "allowImportingTsExtensions": true,
15
- "verbatimModuleSyntax": true,
16
- "noEmit": true,
17
-
18
- // Best practices
19
- "strict": true,
20
- "skipLibCheck": true,
21
- "noFallthroughCasesInSwitch": true,
22
- "noUncheckedIndexedAccess": true,
23
- "noImplicitOverride": true,
24
-
25
- // Some stricter flags (disabled by default)
26
- "noUnusedLocals": false,
27
- "noUnusedParameters": false,
28
- "noPropertyAccessFromIndexSignature": false
29
- }
30
- }