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: 6 additions & 11 deletions pkg/cli/run_workflow_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,15 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, enable bo
}
}

// Determine the lock file name based on the workflow source
var lockFileName string
var lockFilePath string

// Normalize workflow ID to handle both \"workflow-name\" and \".github/workflows/workflow-name.md\" formats
normalizedID := normalizeWorkflowID(workflowIdOrName)

if repoOverride != "" {
// For remote repositories, construct lock file name from normalized ID
lockFileName = normalizedID + ".lock.yml"
} else {
// Construct lock file name from normalized ID (same for both local and remote)
lockFileName := normalizedID + ".lock.yml"

// For local workflows, validate the workflow exists and check for lock file
var lockFilePath string
if repoOverride == "" {
// For local workflows, validate the workflow exists locally
workflowsDir := getWorkflowsDir()

Expand All @@ -168,9 +166,6 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, enable bo
return fmt.Errorf("failed to find workflow in local .github/workflows: %w", err)
}

// For local workflows, construct lock file name from normalized ID
lockFileName = normalizedID + ".lock.yml"

// Check if the lock file exists in .github/workflows
lockFilePath = filepath.Join(".github/workflows", lockFileName)
if _, err := os.Stat(lockFilePath); os.IsNotExist(err) {
Expand Down
19 changes: 5 additions & 14 deletions pkg/cli/workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"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/sliceutil"
"github.com/githubnext/gh-aw/pkg/workflow"
)

Expand Down Expand Up @@ -207,13 +208,9 @@ func getAvailableWorkflowNames() []string {
return nil
}

var names []string
for _, file := range mdFiles {
base := filepath.Base(file)
name := strings.TrimSuffix(base, ".md")
names = append(names, name)
}
return names
return sliceutil.Map(mdFiles, func(file string) string {
return strings.TrimSuffix(filepath.Base(file), ".md")
})
}

// suggestWorkflowNames returns up to 3 similar workflow names using fuzzy matching
Expand All @@ -240,11 +237,5 @@ func isWorkflowFile(filename string) bool {

// filterWorkflowFiles filters out non-workflow files from a list of markdown files.
func filterWorkflowFiles(files []string) []string {
var filtered []string
for _, file := range files {
if isWorkflowFile(file) {
filtered = append(filtered, file)
}
}
return filtered
return sliceutil.Filter(files, isWorkflowFile)
}
41 changes: 41 additions & 0 deletions pkg/sliceutil/sliceutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,44 @@ func MapToSlice[K comparable, V any](m map[K]V) []K {
}
return result
}

// FilterMap filters and transforms elements in a single pass.
// Only elements where the predicate returns true are transformed and included in the result.
// This is a pure function that does not modify the input slice.
func FilterMap[T, U any](slice []T, predicate func(T) bool, transform func(T) U) []U {
result := make([]U, 0, len(slice))
for _, item := range slice {
if predicate(item) {
result = append(result, transform(item))
}
}
return result
}

// FilterMapKeys returns map keys that match the given predicate.
// The order of elements is not guaranteed as map iteration order is undefined.
// This is a pure function that does not modify the input map.
func FilterMapKeys[K comparable, V any](m map[K]V, predicate func(K, V) bool) []K {
result := make([]K, 0, len(m))
for key, value := range m {
if predicate(key, value) {
result = append(result, key)
}
}
return result
}

// Deduplicate returns a new slice with duplicate elements removed.
// The order of first occurrence is preserved.
// This is a pure function that does not modify the input slice.
func Deduplicate[T comparable](slice []T) []T {
seen := make(map[T]bool, len(slice))
result := make([]T, 0, len(slice))
for _, item := range slice {
if !seen[item] {
seen[item] = true
result = append(result, item)
}
}
return result
}
30 changes: 12 additions & 18 deletions pkg/workflow/compiler_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"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/sliceutil"
"github.com/goccy/go-yaml"
)

Expand Down Expand Up @@ -92,15 +93,12 @@ func jobDependsOnAgent(jobConfig map[string]any) bool {
// getCustomJobsDependingOnPreActivation returns custom job names that explicitly depend on pre_activation.
// These jobs run after pre_activation but before activation, and activation should depend on them.
func (c *Compiler) getCustomJobsDependingOnPreActivation(customJobs map[string]any) []string {
var jobNames []string
for jobName, jobConfig := range customJobs {
return sliceutil.FilterMapKeys(customJobs, func(jobName string, jobConfig any) bool {
if configMap, ok := jobConfig.(map[string]any); ok {
if jobDependsOnPreActivation(configMap) {
jobNames = append(jobNames, jobName)
}
return jobDependsOnPreActivation(configMap)
}
}
return jobNames
return false
})
}

// getReferencedCustomJobs returns custom job names that are referenced in the given content.
Expand All @@ -109,17 +107,13 @@ func (c *Compiler) getReferencedCustomJobs(content string, customJobs map[string
if content == "" || customJobs == nil {
return nil
}
var referencedJobs []string
for jobName := range customJobs {
// Check for patterns like "needs.job_name." which covers:
// - needs.job_name.outputs.X
// - ${{ needs.job_name.outputs.X }}
// - needs.job_name.result
if strings.Contains(content, fmt.Sprintf("needs.%s.", jobName)) {
referencedJobs = append(referencedJobs, jobName)
}
}
return referencedJobs
// Check for patterns like "needs.job_name." which covers:
// - needs.job_name.outputs.X
// - ${{ needs.job_name.outputs.X }}
// - needs.job_name.result
return sliceutil.FilterMapKeys(customJobs, func(jobName string, _ any) bool {
return strings.Contains(content, fmt.Sprintf("needs.%s.", jobName))
})
}

// buildJobs creates all jobs for the workflow and adds them to the job manager
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/compiler_safe_outputs_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type handlerConfigBuilder struct {
// newHandlerConfigBuilder creates a new handler config builder
func newHandlerConfigBuilder() *handlerConfigBuilder {
return &handlerConfigBuilder{
config: make(map[string]any),
config: map[string]any{},
}
}

Expand Down
Loading