← 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 })
  }
}