Skip to content

Commit 3268cbb

Browse files
committed
feat(scripts): add status-services command to check if services are running
1 parent 3a44b53 commit 3268cbb

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

knowledge.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ bun dev # Starts db, studio, sdk, web, then CLI
7676
bun start-services # Start services in background, exits when ready
7777
bun start-cli # Start CLI in foreground
7878
bun stop-services # Stop background services
79+
bun status-services # Check if services are running
7980
```
8081

8182
**Services started:**

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"start-web": "bun start-db && bun --cwd web dev",
2323
"start-studio": "bun --cwd packages/internal db:studio",
2424
"stop-services": "bun scripts/stop-services.ts",
25+
"status-services": "bun scripts/status-services.ts",
2526
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
2627
"release:cli": "bun run --cwd=cli release",
2728
"release:sdk": "bun run --cwd=sdk release",

scripts/status-services.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env bun
2+
3+
/**
4+
* Check the status of development services
5+
*
6+
* Usage:
7+
* bun status-services # Check if services are running
8+
*
9+
* Bun automatically loads .env.local and .env.development.local,
10+
* so environment variables are available without manual sourcing.
11+
*/
12+
13+
import { spawnSync } from 'child_process'
14+
import { existsSync, readFileSync } from 'fs'
15+
import { join, resolve } from 'path'
16+
17+
const PROJECT_ROOT = resolve(import.meta.dir, '..')
18+
const LOG_DIR = join(PROJECT_ROOT, 'debug', 'console')
19+
const PID_FILE = join(LOG_DIR, 'services.json')
20+
21+
// Get config from environment (Bun loads .env files automatically)
22+
const APP_URL = process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'http://localhost:3000'
23+
const PORT = process.env.NEXT_PUBLIC_WEB_PORT || '3000'
24+
const STUDIO_PORT = '4983' // Drizzle Studio default port
25+
26+
interface ServicePids {
27+
studio?: number
28+
sdk?: number
29+
web?: number
30+
port: string
31+
}
32+
33+
function ok(name: string, message: string): void {
34+
console.log(` \x1b[32m✓\x1b[0m ${name.padEnd(10)} ${message}`)
35+
}
36+
37+
function fail(name: string, message: string): void {
38+
console.log(` \x1b[31m✗\x1b[0m ${name.padEnd(10)} ${message}`)
39+
}
40+
41+
function warn(name: string, message: string): void {
42+
console.log(` \x1b[33m?\x1b[0m ${name.padEnd(10)} ${message}`)
43+
}
44+
45+
function loadPids(): ServicePids | null {
46+
if (!existsSync(PID_FILE)) {
47+
return null
48+
}
49+
try {
50+
return JSON.parse(readFileSync(PID_FILE, 'utf-8'))
51+
} catch {
52+
return null
53+
}
54+
}
55+
56+
function isProcessRunning(pid: number): boolean {
57+
try {
58+
process.kill(pid, 0)
59+
return true
60+
} catch {
61+
return false
62+
}
63+
}
64+
65+
function getProcessesOnPort(port: string): number[] {
66+
try {
67+
const result = spawnSync('lsof', ['-ti', `:${port}`], { encoding: 'utf-8' })
68+
if (!result.stdout) return []
69+
70+
return result.stdout
71+
.trim()
72+
.split('\n')
73+
.filter(Boolean)
74+
.map((s) => parseInt(s, 10))
75+
.filter((n) => !isNaN(n))
76+
} catch {
77+
return []
78+
}
79+
}
80+
81+
function isDockerDbRunning(): boolean {
82+
try {
83+
const result = spawnSync('docker', ['ps', '--filter', 'name=manicode-db', '--format', '{{.Status}}'], {
84+
encoding: 'utf-8',
85+
})
86+
return result.stdout.includes('Up')
87+
} catch {
88+
return false
89+
}
90+
}
91+
92+
async function checkHealth(): Promise<boolean> {
93+
try {
94+
const response = await fetch(`${APP_URL}/api/healthz`, { signal: AbortSignal.timeout(3000) })
95+
return response.ok
96+
} catch {
97+
return false
98+
}
99+
}
100+
101+
async function main(): Promise<void> {
102+
console.log('')
103+
console.log(`Service Status (port ${PORT}):`)
104+
console.log('')
105+
106+
const pids = loadPids()
107+
let anyRunning = false
108+
109+
// Check database
110+
if (isDockerDbRunning()) {
111+
ok('db', 'running (Docker)')
112+
anyRunning = true
113+
} else {
114+
fail('db', 'not running')
115+
}
116+
117+
// Check web server
118+
const webProcesses = getProcessesOnPort(PORT)
119+
if (webProcesses.length > 0) {
120+
const healthy = await checkHealth()
121+
if (healthy) {
122+
ok('web', `running on port ${PORT} (healthy)`)
123+
} else {
124+
warn('web', `running on port ${PORT} (not responding to health check)`)
125+
}
126+
anyRunning = true
127+
} else {
128+
fail('web', `not running on port ${PORT}`)
129+
}
130+
131+
// Check studio by port
132+
const studioProcesses = getProcessesOnPort(STUDIO_PORT)
133+
if (studioProcesses.length > 0) {
134+
ok('studio', `running on port ${STUDIO_PORT}`)
135+
anyRunning = true
136+
} else {
137+
fail('studio', `not running on port ${STUDIO_PORT}`)
138+
}
139+
140+
// Check SDK build (if tracked)
141+
if (pids?.sdk) {
142+
if (isProcessRunning(pids.sdk)) {
143+
ok('sdk', `running (PID ${pids.sdk})`)
144+
anyRunning = true
145+
} else {
146+
// SDK build completes and exits, so this is expected
147+
ok('sdk', 'build completed')
148+
}
149+
} else {
150+
warn('sdk', 'not tracked')
151+
}
152+
153+
console.log('')
154+
155+
if (anyRunning) {
156+
console.log(' To stop: bun stop-services')
157+
} else {
158+
console.log(' To start: bun start-services')
159+
}
160+
161+
console.log('')
162+
}
163+
164+
main().catch((error) => {
165+
console.error('Error checking status:', error)
166+
process.exit(1)
167+
})

0 commit comments

Comments
 (0)