Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pkg/cli/file_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import (
"os/exec"
"path/filepath"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
)

var fileTrackerLog = logger.New("cli:file_tracker")

// FileTracker keeps track of files created or modified during workflow operations
// to enable proper staging and rollback functionality
type FileTracker struct {
Expand All @@ -19,10 +23,13 @@ type FileTracker struct {

// NewFileTracker creates a new file tracker
func NewFileTracker() (*FileTracker, error) {
fileTrackerLog.Print("Creating new file tracker")
gitRoot, err := findGitRoot()
if err != nil {
fileTrackerLog.Printf("Failed to find git root: %v", err)
return nil, fmt.Errorf("file tracker requires being in a git repository: %w", err)
}
fileTrackerLog.Printf("File tracker initialized with git root: %s", gitRoot)
return &FileTracker{
CreatedFiles: make([]string, 0),
ModifiedFiles: make([]string, 0),
Expand All @@ -37,6 +44,7 @@ func (ft *FileTracker) TrackCreated(filePath string) {
if err != nil {
absPath = filePath
}
fileTrackerLog.Printf("Tracking created file: %s", absPath)
ft.CreatedFiles = append(ft.CreatedFiles, absPath)
}

Expand All @@ -51,6 +59,9 @@ func (ft *FileTracker) TrackModified(filePath string) {
if _, exists := ft.OriginalContent[absPath]; !exists {
if content, err := os.ReadFile(absPath); err == nil {
ft.OriginalContent[absPath] = content
fileTrackerLog.Printf("Tracking modified file: %s (stored %d bytes)", absPath, len(content))
} else {
fileTrackerLog.Printf("Tracking modified file: %s (failed to store original: %v)", absPath, err)
}
}

Expand All @@ -68,6 +79,7 @@ func (ft *FileTracker) GetAllFiles() []string {
// StageAllFiles stages all tracked files using git add
func (ft *FileTracker) StageAllFiles(verbose bool) error {
allFiles := ft.GetAllFiles()
fileTrackerLog.Printf("Staging %d tracked files", len(allFiles))
if len(allFiles) == 0 {
if verbose {
fmt.Println("No files to stage")
Expand All @@ -87,9 +99,11 @@ func (ft *FileTracker) StageAllFiles(verbose bool) error {
cmd := exec.Command("git", args...)
cmd.Dir = ft.gitRoot
if err := cmd.Run(); err != nil {
fileTrackerLog.Printf("Failed to stage files: %v", err)
return fmt.Errorf("failed to stage files: %w", err)
}

fileTrackerLog.Printf("Successfully staged all files")
return nil
}

Expand All @@ -99,6 +113,7 @@ func (ft *FileTracker) RollbackCreatedFiles(verbose bool) error {
return nil
}

fileTrackerLog.Printf("Rolling back %d created files", len(ft.CreatedFiles))
if verbose {
fmt.Printf("Rolling back %d created files...\n", len(ft.CreatedFiles))
}
Expand All @@ -109,6 +124,7 @@ func (ft *FileTracker) RollbackCreatedFiles(verbose bool) error {
fmt.Printf(" - Deleting %s\n", file)
}
if err := os.Remove(file); err != nil && !os.IsNotExist(err) {
fileTrackerLog.Printf("Failed to delete %s: %v", file, err)
errors = append(errors, fmt.Sprintf("failed to delete %s: %v", file, err))
}
}
Expand All @@ -117,6 +133,7 @@ func (ft *FileTracker) RollbackCreatedFiles(verbose bool) error {
return fmt.Errorf("rollback errors: %s", strings.Join(errors, "; "))
}

fileTrackerLog.Print("Successfully rolled back created files")
return nil
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/cli/frontmatter_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@ import (
"strings"

"github.com/githubnext/gh-aw/pkg/constants"
"github.com/githubnext/gh-aw/pkg/logger"
"github.com/githubnext/gh-aw/pkg/parser"
"github.com/githubnext/gh-aw/pkg/workflow"
)

var frontmatterUtilsLog = logger.New("cli:frontmatter_utils")

// UpdateFieldInFrontmatter updates a field in the frontmatter while preserving the original formatting
// when possible. It tries to preserve whitespace, comments, and formatting by working with the raw
// frontmatter lines, similar to how addSourceToWorkflow works.
func UpdateFieldInFrontmatter(content, fieldName, fieldValue string) (string, error) {
frontmatterUtilsLog.Printf("Updating frontmatter field: %s = %s", fieldName, fieldValue)

// Parse frontmatter using parser package
result, err := parser.ExtractFrontmatterFromContent(content)
if err != nil {
frontmatterUtilsLog.Printf("Failed to parse frontmatter: %v", err)
return "", fmt.Errorf("failed to parse frontmatter: %w", err)
}

// Try to preserve original frontmatter formatting by manually updating the field
if len(result.FrontmatterLines) > 0 {
frontmatterUtilsLog.Printf("Using raw frontmatter lines for field update (%d lines)", len(result.FrontmatterLines))
// Look for existing field in the raw lines
fieldUpdated := false
frontmatterLines := make([]string, len(result.FrontmatterLines))
Expand Down Expand Up @@ -48,6 +55,7 @@ func UpdateFieldInFrontmatter(content, fieldName, fieldValue string) (string, er
frontmatterLines[i] = fmt.Sprintf("%s%s: %s", leadingSpace, fieldName, fieldValue)
}
fieldUpdated = true
frontmatterUtilsLog.Printf("Updated existing field %s in place (line %d)", fieldName, i+1)
break
}
}
Expand All @@ -56,6 +64,7 @@ func UpdateFieldInFrontmatter(content, fieldName, fieldValue string) (string, er
if !fieldUpdated {
newField := fmt.Sprintf("%s: %s", fieldName, fieldValue)
frontmatterLines = append(frontmatterLines, newField)
frontmatterUtilsLog.Printf("Added new field %s at end of frontmatter", fieldName)
}

// Reconstruct the file with preserved formatting
Expand Down
12 changes: 12 additions & 0 deletions pkg/cli/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,32 @@ import (

"github.com/githubnext/gh-aw/pkg/console"
"github.com/githubnext/gh-aw/pkg/constants"
"github.com/githubnext/gh-aw/pkg/logger"
"github.com/githubnext/gh-aw/pkg/parser"
"github.com/githubnext/gh-aw/pkg/workflow"
)

var importsLog = logger.New("cli:imports")

// processImportsWithWorkflowSpec processes imports field in frontmatter and replaces local file references
// with workflowspec format (owner/repo/path@sha) for all imports found
func processImportsWithWorkflowSpec(content string, workflow *WorkflowSpec, commitSHA string, verbose bool) (string, error) {
importsLog.Printf("Processing imports with workflowspec: repo=%s, sha=%s", workflow.RepoSlug, commitSHA)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Processing imports field to replace with workflowspec"))
}

// Extract frontmatter from content
result, err := parser.ExtractFrontmatterFromContent(content)
if err != nil {
importsLog.Printf("No frontmatter found, skipping imports processing")
return content, nil // Return original content if no frontmatter
}

// Check if imports field exists
importsField, exists := result.Frontmatter["imports"]
if !exists {
importsLog.Print("No imports field in frontmatter")
return content, nil // No imports field, return original content
}

Expand All @@ -44,14 +50,18 @@ func processImportsWithWorkflowSpec(content string, workflow *WorkflowSpec, comm
case []string:
imports = v
default:
importsLog.Print("Invalid imports field type, skipping")
return content, nil // Invalid imports field, skip processing
}

importsLog.Printf("Found %d imports to process", len(imports))

// Process each import and replace with workflowspec format
processedImports := make([]string, 0, len(imports))
for _, importPath := range imports {
// Skip if already a workflowspec
if isWorkflowSpecFormat(importPath) {
importsLog.Printf("Import already in workflowspec format: %s", importPath)
processedImports = append(processedImports, importPath)
continue
}
Expand All @@ -65,6 +75,7 @@ func processImportsWithWorkflowSpec(content string, workflow *WorkflowSpec, comm
workflowSpec += "@" + workflow.Version
}

importsLog.Printf("Converted import: %s -> %s", importPath, workflowSpec)
processedImports = append(processedImports, workflowSpec)
}

Expand Down Expand Up @@ -106,6 +117,7 @@ func reconstructWorkflowFileFromMap(frontmatter map[string]any, markdown string)
// processIncludesWithWorkflowSpec processes @include directives in content and replaces local file references
// with workflowspec format (owner/repo/path@sha) for all includes found in the package
func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, commitSHA, packagePath string, verbose bool) (string, error) {
importsLog.Printf("Processing @include directives: repo=%s, sha=%s, package=%s", workflow.RepoSlug, commitSHA, packagePath)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Processing @include directives to replace with workflowspec"))
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/cli/jq.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ import (
"fmt"
"os/exec"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
)

var jqLog = logger.New("cli:jq")

// ApplyJqFilter applies a jq filter to JSON input
func ApplyJqFilter(jsonInput string, jqFilter string) (string, error) {
jqLog.Printf("Applying jq filter: %s (input size: %d bytes)", jqFilter, len(jsonInput))

// Check if jq is available
jqPath, err := exec.LookPath("jq")
if err != nil {
jqLog.Printf("jq not found in PATH")
return "", fmt.Errorf("jq not found in PATH")
}
jqLog.Printf("Found jq at: %s", jqPath)

// Pipe through jq
cmd := exec.Command(jqPath, jqFilter)
Expand All @@ -23,8 +31,10 @@ func ApplyJqFilter(jsonInput string, jqFilter string) (string, error) {
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
jqLog.Printf("jq filter failed: %v, stderr: %s", err, stderr.String())
return "", fmt.Errorf("jq filter failed: %w, stderr: %s", err, stderr.String())
}

jqLog.Printf("jq filter succeeded (output size: %d bytes)", stdout.Len())
return stdout.String(), nil
}