bit-ship
Version:
Bit-Ship CLI is tool that analyses your code and generates a custom environment for your needs You can use if to local development, CI/CD or even production.
159 lines (133 loc) • 3.73 kB
JavaScript
/**
* Standalone worker script for monitoring podman container events.
* This runs as a detached Node.js process, independent of the CLI.
* It spawns `podman events` and writes container state to ~/.bit-ship/containers.json
*/
import { spawn } from 'child_process'
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'
import { homedir } from 'os'
import { join } from 'path'
const BIT_SHIP_DIR = join(homedir(), '.bit-ship')
const CONTAINERS_FILE = join(BIT_SHIP_DIR, 'containers.json')
function ensureDirectoryExists() {
if (!existsSync(BIT_SHIP_DIR)) {
mkdirSync(BIT_SHIP_DIR, { recursive: true })
}
}
function readContainersState() {
try {
if (existsSync(CONTAINERS_FILE)) {
const content = readFileSync(CONTAINERS_FILE, 'utf8')
return JSON.parse(content)
}
} catch (_err) {
// File doesn't exist or is corrupted, start fresh
}
return {}
}
function writeContainersState(state) {
ensureDirectoryExists()
writeFileSync(CONTAINERS_FILE, JSON.stringify(state, null, 2), 'utf8')
}
function processEvent(event, state) {
const now = Date.now()
const eventTime = event.time ? event.time * 1000 : now
// Extract container info from different event formats
const containerName = event.Name || event.Actor?.Attributes?.name
const containerId = event.ID || event.Actor?.ID
const image = event.Image || event.Actor?.Attributes?.image
const action = event.Status || event.Action
if (!containerName || !containerId) {
return state
}
const existing = state[containerName]
switch (action) {
case 'start':
state[containerName] = {
id: containerId,
image: image || existing?.image || 'unknown',
status: 'running',
startTime: eventTime,
endTime: null,
lastUpdated: now
}
break
case 'stop':
if (existing) {
state[containerName] = {
...existing,
status: 'stopped',
endTime: eventTime,
lastUpdated: now
}
}
break
case 'die':
if (existing) {
state[containerName] = {
...existing,
status: 'exited',
endTime: eventTime,
lastUpdated: now
}
}
break
case 'remove':
if (existing) {
state[containerName] = {
...existing,
status: 'removed',
endTime: eventTime,
lastUpdated: now
}
}
break
case 'create':
state[containerName] = {
id: containerId,
image: image || 'unknown',
status: 'stopped',
startTime: eventTime,
endTime: null,
lastUpdated: now
}
break
}
return state
}
// Main execution
function main() {
ensureDirectoryExists()
const child = spawn('podman', ['events', '--format', 'json', '--filter', 'type=container'], {
shell: true,
stdio: ['ignore', 'pipe', 'ignore']
})
let buffer = ''
child.stdout?.on('data', (data) => {
buffer += data.toString()
// Process complete JSON lines
const lines = buffer.split('\n')
buffer = lines.pop() || '' // Keep incomplete line in buffer
for (const line of lines) {
if (!line.trim()) continue
try {
const event = JSON.parse(line)
let state = readContainersState()
state = processEvent(event, state)
writeContainersState(state)
} catch (_err) {
// Skip malformed JSON lines
}
}
})
child.on('error', (_err) => {
// Silently handle errors
process.exit(1)
})
child.on('close', (_code) => {
// podman events exited (machine stopped?), exit worker
process.exit(0)
})
}
main()