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
1 change: 1 addition & 0 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ Examples:
` + string(constants.CLIExtensionPrefix) + ` compile ci-doctor # Compile a specific workflow
` + string(constants.CLIExtensionPrefix) + ` compile ci-doctor daily-plan # Compile multiple workflows
` + string(constants.CLIExtensionPrefix) + ` compile workflow.md # Compile by file path
` + string(constants.CLIExtensionPrefix) + ` compile .github/workflows # Compile all workflows in a directory
` + string(constants.CLIExtensionPrefix) + ` compile --dir custom/workflows # Compile from custom directory
` + string(constants.CLIExtensionPrefix) + ` compile --watch ci-doctor # Watch and auto-compile
` + string(constants.CLIExtensionPrefix) + ` compile --trial --logical-repo owner/repo # Compile for trial mode
Expand Down
84 changes: 84 additions & 0 deletions pkg/cli/compile_args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// This file provides argument preprocessing for the compile command.
//
// It handles expansion of local directory paths into their constituent
// workflow .md files so that the rest of the compilation pipeline only
// needs to deal with concrete file paths.
//
// # Key Functions
//
// - resolveCompileArgs() - Expand a list of compile arguments
// - expandCompileArg() - Expand a single argument (directory or file)
// - expandDirectoryArg() - Return all .md workflow files inside a directory

package cli

import (
"fmt"
"os"

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

var compileArgsLog = logger.New("cli:compile_args")

// resolveCompileArgs preprocesses compile command arguments to handle
// local directory paths. When an argument is a directory it is expanded
// to all .md workflow files in that directory.
func resolveCompileArgs(args []string, verbose bool) ([]string, error) {
if len(args) == 0 {
return args, nil
}

var result []string
for _, arg := range args {
expanded, err := expandCompileArg(arg, verbose)
if err != nil {
return nil, err
}
result = append(result, expanded...)
}
return result, nil
}

// expandCompileArg expands a single compile argument:
// - Local directory paths: expand to all .md files in that directory
// - Everything else: return as-is for the existing resolver to handle
func expandCompileArg(arg string, verbose bool) ([]string, error) {
compileArgsLog.Printf("Processing compile argument: %s", arg)

// Handle local directory paths
info, err := os.Stat(arg)
if err == nil && info.IsDir() {
return expandDirectoryArg(arg, verbose)
}

// Return as-is (regular file path or workflow name)
return []string{arg}, nil
}

// expandDirectoryArg expands a directory path to all .md workflow files in it.
func expandDirectoryArg(dirPath string, verbose bool) ([]string, error) {
compileArgsLog.Printf("Expanding directory argument: %s", dirPath)

if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Compiling all workflows in directory: "+dirPath))
}

mdFiles, err := getMarkdownWorkflowFiles(dirPath)
if err != nil {
return nil, fmt.Errorf("failed to find workflow files in %s: %w", dirPath, err)
}

mdFiles, err = filterMarkdownFilesWithFrontmatter(mdFiles)
if err != nil {
return nil, fmt.Errorf("failed to filter workflow files in %s: %w", dirPath, err)
}

if len(mdFiles) == 0 {
return nil, fmt.Errorf("no workflow markdown files found in %s (workflow files must start with a frontmatter opener on the first line)", dirPath)
}

compileArgsLog.Printf("Found %d workflow files in directory %s", len(mdFiles), dirPath)
return mdFiles, nil
}
84 changes: 84 additions & 0 deletions pkg/cli/compile_args_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//go:build !integration

package cli

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestExpandCompileArg_RegularFile(t *testing.T) {
// A plain workflow name should be returned as-is
result, err := expandCompileArg("my-workflow", false)
require.NoError(t, err, "plain workflow name should not error")
assert.Equal(t, []string{"my-workflow"}, result, "plain workflow name should be returned unchanged")
}

func TestExpandCompileArg_FilePath(t *testing.T) {
// A non-existent file path should be returned as-is
result, err := expandCompileArg(".github/workflows/my-workflow.md", false)
require.NoError(t, err, "non-existent file path should not error")
assert.Equal(t, []string{".github/workflows/my-workflow.md"}, result, "file path should be returned unchanged")
}

func TestExpandCompileArg_LocalDirectory(t *testing.T) {
// Create a temp directory with workflow files
tmpDir := t.TempDir()
writeWorkflowFile(t, tmpDir, "workflow-a.md")
writeWorkflowFile(t, tmpDir, "workflow-b.md")

result, err := expandCompileArg(tmpDir, false)
require.NoError(t, err, "local directory should expand without error")
assert.Len(t, result, 2, "should return all .md files in the directory")
}

func TestExpandCompileArg_LocalDirectory_Empty(t *testing.T) {
// Directory with no .md files should error
tmpDir := t.TempDir()
_, err := expandCompileArg(tmpDir, false)
assert.Error(t, err, "empty directory should return an error")
assert.Contains(t, err.Error(), "no workflow markdown files found", "error should mention no workflow files")
}

func TestExpandCompileArg_URLPassthrough(t *testing.T) {
// URLs should be returned as-is (not processed)
url := "https://github.com/org/repo/tree/main/.github/workflows"
result, err := expandCompileArg(url, false)
require.NoError(t, err, "URL should not error")
assert.Equal(t, []string{url}, result, "URL should be returned unchanged")
}

func TestResolveCompileArgs_Empty(t *testing.T) {
result, err := resolveCompileArgs(nil, false)
require.NoError(t, err)
assert.Empty(t, result)
}

func TestResolveCompileArgs_Mixed(t *testing.T) {
// Create a temp directory with a workflow file
tmpDir := t.TempDir()
writeWorkflowFile(t, tmpDir, "workflow-a.md")

// Mix: a plain workflow name + a directory
result, err := resolveCompileArgs([]string{"plain-workflow", tmpDir}, false)
require.NoError(t, err, "mixed args should expand without error")
require.Len(t, result, 2, "should have one plain name + one expanded file")

// The plain workflow name should be unchanged
assert.Equal(t, "plain-workflow", result[0], "first arg should be unchanged")
// The directory arg should be the single .md file in tmpDir
assert.True(t, strings.HasSuffix(result[1], "workflow-a.md"), "second arg should be the expanded .md file")
}

// writeWorkflowFile creates a minimal workflow .md file in dir with the given name.
func writeWorkflowFile(t *testing.T, dir, name string) {
t.Helper()
content := "---\non: push\n---\n\n# Test Workflow\n"
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(content), 0644),
"write workflow file %s", name)
}
9 changes: 9 additions & 0 deletions pkg/cli/compile_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ func CompileWorkflows(ctx context.Context, config CompileConfig) ([]*workflow.Wo
compileOrchestratorLog.Printf("Using custom workflow directory: %s", workflowDir)
}

// Preprocess args: expand directory paths and GitHub URLs to constituent workflow files
if len(config.MarkdownFiles) > 0 {
expandedFiles, err := resolveCompileArgs(config.MarkdownFiles, config.Verbose)
if err != nil {
return nil, err
}
config.MarkdownFiles = expandedFiles
}

// Create and configure compiler
compiler := createAndConfigureCompiler(config)

Expand Down