numux 0.0.1 → 1.1.0

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.
@@ -0,0 +1,32 @@
1
+ import { appendFileSync, existsSync, mkdirSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+
4
+ let enabled = false
5
+ let logFile = ''
6
+
7
+ export function enableDebugLog(dir?: string): void {
8
+ const logDir = dir ?? resolve(process.cwd(), '.numux')
9
+ logFile = resolve(logDir, 'debug.log')
10
+ if (!existsSync(logDir)) {
11
+ mkdirSync(logDir, { recursive: true })
12
+ }
13
+ enabled = true
14
+ }
15
+
16
+ export function log(message: string, ...args: unknown[]): void {
17
+ if (!enabled) return
18
+ try {
19
+ const timestamp = new Date().toISOString()
20
+ const formatted = args.length > 0 ? `${message} ${args.map(a => JSON.stringify(a)).join(' ')}` : message
21
+ appendFileSync(logFile, `[${timestamp}] ${formatted}\n`)
22
+ } catch {
23
+ // Disk errors in debug logging should not crash the app
24
+ enabled = false
25
+ }
26
+ }
27
+
28
+ /** Reset logger state (for testing only) */
29
+ export function _resetLogger(): void {
30
+ enabled = false
31
+ logFile = ''
32
+ }
@@ -0,0 +1,39 @@
1
+ import type { App } from '../ui/app'
2
+ import type { LogWriter } from './log-writer'
3
+ import { log } from './logger'
4
+
5
+ export function setupShutdownHandlers(app: App, logWriter?: LogWriter): void {
6
+ let shuttingDown = false
7
+
8
+ const shutdown = () => {
9
+ if (shuttingDown) {
10
+ process.exit(1)
11
+ }
12
+ shuttingDown = true
13
+ app.shutdown().finally(() => {
14
+ logWriter?.close()
15
+ process.exit(app.hasFailures() ? 1 : 0)
16
+ })
17
+ }
18
+
19
+ process.on('SIGINT', shutdown)
20
+ process.on('SIGTERM', shutdown)
21
+ process.on('uncaughtException', err => {
22
+ log('Uncaught exception:', err?.message ?? err)
23
+ process.stderr.write(`numux: unexpected error: ${err?.stack ?? err}\n`)
24
+ app.shutdown().finally(() => {
25
+ logWriter?.close()
26
+ process.exit(1)
27
+ })
28
+ })
29
+
30
+ process.on('unhandledRejection', (reason: unknown) => {
31
+ const message = reason instanceof Error ? reason.message : String(reason)
32
+ log('Unhandled rejection:', message)
33
+ process.stderr.write(`numux: unhandled rejection: ${message}\n`)
34
+ app.shutdown().finally(() => {
35
+ logWriter?.close()
36
+ process.exit(1)
37
+ })
38
+ })
39
+ }
@@ -0,0 +1,53 @@
1
+ import { type FSWatcher, watch } from 'node:fs'
2
+ import { log } from './logger'
3
+
4
+ const DEBOUNCE_MS = 300
5
+ const IGNORED_SEGMENTS = new Set(['node_modules', '.git'])
6
+
7
+ export class FileWatcher {
8
+ private watchers: FSWatcher[] = []
9
+ private debounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
10
+
11
+ watch(name: string, patterns: string[], cwd: string, onChanged: (path: string) => void): void {
12
+ const globs = patterns.map(p => new Bun.Glob(p))
13
+
14
+ try {
15
+ const watcher = watch(cwd, { recursive: true }, (_event, filename) => {
16
+ if (!filename) return
17
+
18
+ // Skip node_modules, .git, etc.
19
+ const segments = filename.split('/')
20
+ if (segments.some(s => IGNORED_SEGMENTS.has(s))) return
21
+
22
+ // Check if changed file matches any pattern
23
+ if (!globs.some(g => g.match(filename))) return
24
+
25
+ // Debounce per process
26
+ const existing = this.debounceTimers.get(name)
27
+ if (existing) clearTimeout(existing)
28
+ this.debounceTimers.set(
29
+ name,
30
+ setTimeout(() => {
31
+ this.debounceTimers.delete(name)
32
+ onChanged(filename)
33
+ }, DEBOUNCE_MS)
34
+ )
35
+ })
36
+ this.watchers.push(watcher)
37
+ log(`[${name}] Watching: ${patterns.join(', ')}`)
38
+ } catch (err) {
39
+ log(`[${name}] Failed to set up file watcher: ${err}`)
40
+ }
41
+ }
42
+
43
+ close(): void {
44
+ for (const timer of this.debounceTimers.values()) {
45
+ clearTimeout(timer)
46
+ }
47
+ this.debounceTimers.clear()
48
+ for (const watcher of this.watchers) {
49
+ watcher.close()
50
+ }
51
+ this.watchers = []
52
+ }
53
+ }