package service import ( "fmt" "regexp" "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 // and updates build-complete's depends_on to include the new deploy step. 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 result := strings.Replace(existing, marker, marker+"\n\n"+strings.TrimRight(sb.String(), "\n"), 1) // Extract deploy step name from the step YAML (e.g., "deploy-studio-api:") deployStep := extractDeployStepName(stepYaml) if deployStep != "" { result = addBuildCompleteDep(result, deployStep) } return result } // extractDeployStepName finds the deploy-{name}: key in a step YAML block. func extractDeployStepName(stepYaml string) string { re := regexp.MustCompile(`(?m)^deploy-([a-zA-Z0-9_-]+):`) match := re.FindStringSubmatch(stepYaml) if len(match) >= 2 { return "deploy-" + match[1] } return "" } // addBuildCompleteDep appends a step name to build-complete's depends_on line // identified by the BUILD_COMPLETE_DEPS marker. func addBuildCompleteDep(yml, stepName string) string { const depsMarker = "# BUILD_COMPLETE_DEPS" lines := strings.Split(yml, "\n") for i, line := range lines { if !strings.Contains(line, depsMarker) { continue } // Parse existing depends_on array from the line // Format: " depends_on: [preflight, deploy-foo] # BUILD_COMPLETE_DEPS" re := regexp.MustCompile(`depends_on:\s*\[([^\]]*)\]`) match := re.FindStringSubmatch(line) if len(match) < 2 { break } // Parse existing deps existing := strings.TrimSpace(match[1]) var deps []string for _, d := range strings.Split(existing, ",") { d = strings.TrimSpace(d) if d != "" { deps = append(deps, d) } } // Append new dep if not already present for _, d := range deps { if d == stepName { return yml } } deps = append(deps, stepName) // Reconstruct the line preserving indentation indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))] newLine := fmt.Sprintf("%sdepends_on: [%s] %s", indent, strings.Join(deps, ", "), depsMarker) lines[i] = newLine return strings.Join(lines, "\n") } return yml } // 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 := "" 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") }