← back to Letsbegin
app/api/processes/details/route.ts
194 lines
import { NextRequest, NextResponse } from 'next/server'
import * as fs from 'fs'
import * as path from 'path'
const STATE_FILE = '/tmp/ralph-state.json'
// Detect port from project config files
function detectPort(projectPath: string): number | null {
// 1. Check ecosystem.config.js
const ecosystemPath = path.join(projectPath, 'ecosystem.config.js')
if (fs.existsSync(ecosystemPath)) {
try {
const content = fs.readFileSync(ecosystemPath, 'utf-8')
const portMatch = content.match(/PORT[:\s]*['"]?(\d+)['"]?/)
if (portMatch) return parseInt(portMatch[1])
} catch {}
}
// 2. Check package.json scripts
const packagePath = path.join(projectPath, 'package.json')
if (fs.existsSync(packagePath)) {
try {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
const scripts = pkg.scripts || {}
for (const script of [scripts.start, scripts.dev]) {
if (script) {
const portMatch = script.match(/-p\s*(\d+)|--port[=\s]*(\d+)|PORT[=\s]*(\d+)/)
if (portMatch) return parseInt(portMatch[1] || portMatch[2] || portMatch[3])
}
}
} catch {}
}
// 3. Check .env or .env.local
for (const envFile of ['.env.local', '.env']) {
const envPath = path.join(projectPath, envFile)
if (fs.existsSync(envPath)) {
try {
const content = fs.readFileSync(envPath, 'utf-8')
const portMatch = content.match(/^PORT[=\s]*(\d+)/m)
if (portMatch) return parseInt(portMatch[1])
} catch {}
}
}
return null
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const project = searchParams.get('project')
const projectPath = searchParams.get('path')
if (!project || !projectPath) {
return NextResponse.json({ error: 'Missing project or path parameter' }, { status: 400 })
}
const logs: string[] = []
const stories: { id: string; title: string; status: string; notes?: string }[] = []
// 1. Check ralph state file for logs
if (fs.existsSync(STATE_FILE)) {
try {
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'))
if (state.project === project || state.projectPath === projectPath) {
if (state.logs && Array.isArray(state.logs)) {
logs.push(...state.logs)
}
// Get stories from state
if (state.userStories && Array.isArray(state.userStories)) {
for (const story of state.userStories) {
stories.push({
id: story.id,
title: story.title || story.description || '',
status: state.storyResults?.[story.id] || 'PENDING',
notes: state.storyNotes?.[story.id] || ''
})
}
}
}
} catch (e) {
logs.push(`Error reading state file: ${e}`)
}
}
// 2. Read progress.txt for story statuses
const progressFile = path.join(projectPath, 'scripts', 'ralph', 'progress.txt')
if (fs.existsSync(progressFile)) {
try {
const content = fs.readFileSync(progressFile, 'utf-8')
logs.push('=== progress.txt ===')
logs.push(content)
logs.push('')
// Parse stories from progress file if not already loaded
if (stories.length === 0) {
const lines = content.split('\n')
for (const line of lines) {
const match = line.match(/(US-\d+):\s*(\w+)\s*-?\s*(.*)/)
if (match) {
stories.push({
id: match[1],
status: match[2],
title: match[3] || ''
})
}
}
}
} catch (e) {
logs.push(`Error reading progress.txt: ${e}`)
}
}
// 3. Read prd.json for story details if stories still empty
const prdFile = path.join(projectPath, 'scripts', 'ralph', 'prd.json')
if (stories.length === 0 && fs.existsSync(prdFile)) {
try {
const prdData = JSON.parse(fs.readFileSync(prdFile, 'utf-8'))
if (prdData.userStories && Array.isArray(prdData.userStories)) {
for (const story of prdData.userStories) {
stories.push({
id: story.id,
title: story.title || story.description || '',
status: 'PENDING',
notes: ''
})
}
}
} catch (e) {
logs.push(`Error reading prd.json: ${e}`)
}
}
// 4. Check for error logs
const errorLogPaths = [
path.join(projectPath, 'ralph-error.log'),
path.join(projectPath, 'scripts', 'ralph', 'error.log'),
path.join(projectPath, '.ralph', 'error.log')
]
for (const logPath of errorLogPaths) {
if (fs.existsSync(logPath)) {
try {
const content = fs.readFileSync(logPath, 'utf-8')
logs.push(`=== ${path.basename(logPath)} ===`)
const lines = content.split('\n').slice(-50) // Last 50 lines
logs.push(...lines)
logs.push('')
} catch (e) {
logs.push(`Error reading ${logPath}: ${e}`)
}
}
}
// 5. Check ralph output log
const outputLog = path.join(projectPath, 'scripts', 'ralph', 'output.log')
if (fs.existsSync(outputLog)) {
try {
const content = fs.readFileSync(outputLog, 'utf-8')
logs.push('=== output.log ===')
const lines = content.split('\n').slice(-100) // Last 100 lines
logs.push(...lines)
} catch (e) {
logs.push(`Error reading output.log: ${e}`)
}
}
// If no logs found, add a message
if (logs.length === 0) {
logs.push('No logs found for this process.')
logs.push(`Checked: ${progressFile}`)
logs.push(`Checked: ${STATE_FILE}`)
logs.push(`Project path: ${projectPath}`)
}
// Detect port for this project
const port = detectPort(projectPath)
return NextResponse.json({
logs,
stories,
project,
projectPath,
port
})
} catch (error: any) {
console.error('Process details error:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}