diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 3cee6754470..f5ac8ed4e8f 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -5,8 +5,8 @@ # # Resolved workflow manifest: # Includes: -# - shared/use-emojis.md # - shared/keep-it-short.md +# - shared/use-emojis.md name: "Dev" on: diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 952b86afee0..5c1d40af708 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "github.com/githubnext/gh-aw/pkg/console" @@ -626,6 +627,8 @@ func (c *Compiler) ParseWorkflowFile(markdownPath string) (*WorkflowData, error) for file := range allIncludedFilesMap { allIncludedFiles = append(allIncludedFiles, file) } + // Sort files alphabetically to ensure consistent ordering in lock files + sort.Strings(allIncludedFiles) // Extract workflow name workflowName, err := parser.ExtractWorkflowNameFromMarkdown(markdownPath) diff --git a/pkg/workflow/manifest_test.go b/pkg/workflow/manifest_test.go index 3f2850fecd5..8a1032a7254 100644 --- a/pkg/workflow/manifest_test.go +++ b/pkg/workflow/manifest_test.go @@ -188,3 +188,120 @@ Handle the issue.`, }) } } + +// TestManifestIncludeOrdering tests that included files are rendered in alphabetical order +func TestManifestIncludeOrdering(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "manifest-order-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // Create shared directory + sharedDir := filepath.Join(tmpDir, "shared") + if err := os.Mkdir(sharedDir, 0755); err != nil { + t.Fatal(err) + } + + // Create multiple include files with names that would be out of order if not sorted + includeFiles := []string{ + "zebra.md", + "apple.md", + "middle.md", + "banana.md", + } + + for _, filename := range includeFiles { + content := "# " + filename + "\n\nSome content." + filePath := filepath.Join(sharedDir, filename) + if err := os.WriteFile(filePath, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + // Create workflow that includes all files in non-alphabetical order + workflowContent := `--- +on: issues +engine: claude +--- + +# Test Workflow + +@include shared/zebra.md +@include shared/apple.md +@include shared/middle.md +@include shared/banana.md + +Handle the issue.` + + compiler := NewCompiler(false, "", "test") + testFile := filepath.Join(tmpDir, "test-workflow.md") + if err := os.WriteFile(testFile, []byte(workflowContent), 0644); err != nil { + t.Fatal(err) + } + + // Compile the workflow + err = compiler.CompileWorkflow(testFile) + if err != nil { + t.Fatalf("Unexpected error compiling workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml" + content, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read generated lock file: %v", err) + } + + lockContent := string(content) + + // Verify manifest section exists + if !strings.Contains(lockContent, "# Resolved workflow manifest:") { + t.Fatal("Expected manifest section but none found") + } + + // Verify includes section exists + if !strings.Contains(lockContent, "# Includes:") { + t.Fatal("Expected Includes section but none found") + } + + // Extract the includes section and verify alphabetical order + lines := strings.Split(lockContent, "\n") + var includeLines []string + inIncludesSection := false + + for _, line := range lines { + if strings.Contains(line, "# Includes:") { + inIncludesSection = true + continue + } + if inIncludesSection { + if strings.HasPrefix(line, "# - ") { + includeLines = append(includeLines, line) + } else if !strings.HasPrefix(line, "#") { + // End of includes section + break + } + } + } + + // Verify we found all includes + expectedCount := len(includeFiles) + if len(includeLines) != expectedCount { + t.Fatalf("Expected %d include lines, found %d", expectedCount, len(includeLines)) + } + + // Expected order is alphabetical + expectedOrder := []string{ + "# - shared/apple.md", + "# - shared/banana.md", + "# - shared/middle.md", + "# - shared/zebra.md", + } + + for i, expected := range expectedOrder { + if includeLines[i] != expected { + t.Errorf("Include line %d: expected %q, got %q", i, expected, includeLines[i]) + } + } +}