rdev/internal/service/component_updates.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

174 lines
5.2 KiB
Go

package service
import (
"fmt"
"strings"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/logging"
)
// updateProcfile adds an entry for the component.
func (s *ComponentService) updateProcfile(existing string, componentType domain.ComponentType, componentName, componentPath string, _ int) string {
var entry string
switch componentType {
case domain.ComponentTypeService:
entry = fmt.Sprintf("%s: cd %s && make run", componentName, componentPath)
case domain.ComponentTypeWorker:
entry = fmt.Sprintf("%s: cd %s && make run", componentName, componentPath)
case domain.ComponentTypeAppAstro, domain.ComponentTypeAppReact:
entry = fmt.Sprintf("%s: cd %s && npm run dev", componentName, componentPath)
case domain.ComponentTypeCLI:
// CLIs don't run as processes
return existing
}
// Add the entry before any empty line at the end, or at the end
lines := strings.Split(strings.TrimRight(existing, "\n"), "\n")
lines = append(lines, entry)
return strings.Join(lines, "\n") + "\n"
}
// updateGoWork adds a use directive for Go components.
func (s *ComponentService) updateGoWork(existing, componentPath string) string {
useLine := fmt.Sprintf("use ./%s", componentPath)
// Check if already present
if strings.Contains(existing, useLine) {
return existing
}
// Find where to insert: after the last 'use' line or after 'go X.XX'
lines := strings.Split(existing, "\n")
insertIdx := len(lines) - 1
// Find the last use statement
for i := len(lines) - 1; i >= 0; i-- {
trimmed := strings.TrimSpace(lines[i])
if strings.HasPrefix(trimmed, "use ") {
insertIdx = i + 1
break
}
if strings.HasPrefix(trimmed, "go ") {
insertIdx = i + 1
}
}
// Insert the new use line
newLines := make([]string, 0, len(lines)+1)
newLines = append(newLines, lines[:insertIdx]...)
newLines = append(newLines, useLine)
newLines = append(newLines, lines[insertIdx:]...)
return strings.Join(newLines, "\n")
}
// updateWoodpeckerYml inserts the component step at the COMPONENT_STEPS_BELOW marker.
func (s *ComponentService) updateWoodpeckerYml(existing, stepYaml string) string {
marker := "# COMPONENT_STEPS_BELOW"
if !strings.Contains(existing, marker) {
log := logging.Default().WithService("component")
log.Warn("COMPONENT_STEPS_BELOW marker not found in .woodpecker.yml")
return existing
}
// Indent the step YAML properly (2 spaces for YAML steps)
var sb strings.Builder
lines := strings.Split(strings.TrimSpace(stepYaml), "\n")
for _, line := range lines {
sb.WriteString(" ")
sb.WriteString(line)
sb.WriteString("\n")
}
// Insert after the marker
return strings.Replace(existing, marker, marker+"\n\n"+strings.TrimRight(sb.String(), "\n"), 1)
}
// updateClaudeMd adds the component to the routing table.
func (s *ComponentService) updateClaudeMd(existing string, componentType domain.ComponentType, componentName, componentPath string) string {
// Find the "## Components" section and add entry
marker := "<!-- Components will be listed here as they're added -->"
var description string
switch componentType {
case domain.ComponentTypeService:
description = "API service"
case domain.ComponentTypeWorker:
description = "Background worker"
case domain.ComponentTypeAppAstro:
description = "Astro app"
case domain.ComponentTypeAppReact:
description = "React app"
case domain.ComponentTypeCLI:
description = "CLI tool"
}
entry := fmt.Sprintf("| **%s** | %s | `%s/` |", componentName, description, componentPath)
if strings.Contains(existing, marker) {
// First component - replace the marker with a table
table := fmt.Sprintf(`| Component | Type | Path |
|-----------|------|------|
%s
`, entry)
return strings.Replace(existing, marker, table, 1)
}
// Add to existing table - find the last table row in ## Components section
lines := strings.Split(existing, "\n")
inComponents := false
insertIdx := -1
for i, line := range lines {
if strings.HasPrefix(line, "## Components") {
inComponents = true
continue
}
if inComponents && strings.HasPrefix(line, "## ") {
// End of Components section
insertIdx = i
break
}
if inComponents && strings.HasPrefix(line, "|") {
insertIdx = i + 1
}
}
if insertIdx > 0 {
// Insert the new entry
newLines := make([]string, 0, len(lines)+1)
newLines = append(newLines, lines[:insertIdx]...)
newLines = append(newLines, entry)
newLines = append(newLines, lines[insertIdx:]...)
return strings.Join(newLines, "\n")
}
return existing
}
// removeProcfileEntry removes a component entry from the Procfile.
func (s *ComponentService) removeProcfileEntry(procfile, componentName string) string {
var lines []string
for _, line := range strings.Split(procfile, "\n") {
if !strings.HasPrefix(strings.TrimSpace(line), componentName+":") {
lines = append(lines, line)
}
}
return strings.Join(lines, "\n")
}
// removeGoWorkEntry removes a use directive from go.work.
func (s *ComponentService) removeGoWorkEntry(goWork, componentPath string) string {
useLine := "use ./" + componentPath
var lines []string
for _, line := range strings.Split(goWork, "\n") {
if strings.TrimSpace(line) != useLine {
lines = append(lines, line)
}
}
return strings.Join(lines, "\n")
}