Skip to content

Commit d8f3bf6

Browse files
Copilotpelikhan
andauthored
Add --dir flag to update command for custom workflow directories (#3925)
* Initial plan * Add --dir flag to update command for custom workflow directories Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Final verification - all tests and validations pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux <pelikhan@users.noreply.github.com>
1 parent ee43d44 commit d8f3bf6

2 files changed

Lines changed: 121 additions & 9 deletions

File tree

pkg/cli/update_command.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,22 @@ Examples:
3737
` + constants.CLIExtensionPrefix + ` update ci-doctor # Check gh-aw updates and update specific workflow
3838
` + constants.CLIExtensionPrefix + ` update ci-doctor --major # Allow major version updates
3939
` + constants.CLIExtensionPrefix + ` update --pr # Create PR with changes
40-
` + constants.CLIExtensionPrefix + ` update --force # Force update even if no changes`,
40+
` + constants.CLIExtensionPrefix + ` update --force # Force update even if no changes
41+
` + constants.CLIExtensionPrefix + ` update --dir custom/workflows # Update workflows in custom directory`,
4142
Run: func(cmd *cobra.Command, args []string) {
4243
majorFlag, _ := cmd.Flags().GetBool("major")
4344
forceFlag, _ := cmd.Flags().GetBool("force")
4445
engineOverride, _ := cmd.Flags().GetString("engine")
4546
verbose, _ := cmd.Flags().GetBool("verbose")
4647
prFlag, _ := cmd.Flags().GetBool("pr")
48+
workflowDir, _ := cmd.Flags().GetString("dir")
4749

4850
if err := validateEngine(engineOverride); err != nil {
4951
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
5052
os.Exit(1)
5153
}
5254

53-
if err := UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag); err != nil {
55+
if err := UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag, workflowDir); err != nil {
5456
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
5557
os.Exit(1)
5658
}
@@ -61,6 +63,7 @@ Examples:
6163
cmd.Flags().Bool("force", false, "Force update even if no changes detected")
6264
cmd.Flags().StringP("engine", "e", "", "Override AI engine (claude, codex, copilot, custom)")
6365
cmd.Flags().Bool("pr", false, "Create a pull request with the workflow changes")
66+
cmd.Flags().String("dir", "", "Relative directory containing workflows (default: .github/workflows)")
6467

6568
return cmd
6669
}
@@ -107,16 +110,20 @@ func checkExtensionUpdate(verbose bool) error {
107110
}
108111

109112
// runCompileWorkflows runs the compile command to recompile all workflows
110-
func runCompileWorkflows(verbose bool, engineOverride string) error {
113+
func runCompileWorkflows(verbose bool, engineOverride string, workflowsDir string) error {
111114
if verbose {
112115
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Compiling workflows..."))
113116
}
114117

115118
// Create a compiler instance similar to how compile command does it
116119
compiler := workflow.NewCompiler(verbose, engineOverride, GetVersion())
117120

121+
// Use provided workflows directory or default
122+
if workflowsDir == "" {
123+
workflowsDir = getWorkflowsDir()
124+
}
125+
118126
// Compile all workflows in the workflows directory
119-
workflowsDir := getWorkflowsDir()
120127
_, err := compileAllWorkflowFiles(compiler, workflowsDir, verbose)
121128
if err != nil {
122129
return fmt.Errorf("failed to compile workflows: %w", err)
@@ -133,19 +140,19 @@ func runCompileWorkflows(verbose bool, engineOverride string) error {
133140
// 2. Update workflows from source repositories
134141
// 3. Compile all workflows
135142
// 4. Optionally create a PR
136-
func UpdateWorkflowsWithExtensionCheck(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, createPR bool) error {
143+
func UpdateWorkflowsWithExtensionCheck(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, createPR bool, workflowsDir string) error {
137144
// Step 1: Check for gh-aw extension updates
138145
if err := checkExtensionUpdate(verbose); err != nil {
139146
return fmt.Errorf("extension update check failed: %w", err)
140147
}
141148

142149
// Step 2: Update workflows from source repositories
143-
if err := UpdateWorkflows(workflowNames, allowMajor, force, verbose, engineOverride); err != nil {
150+
if err := UpdateWorkflows(workflowNames, allowMajor, force, verbose, engineOverride, workflowsDir); err != nil {
144151
return fmt.Errorf("workflow update failed: %w", err)
145152
}
146153

147154
// Step 3: Compile all workflows
148-
if err := runCompileWorkflows(verbose, engineOverride); err != nil {
155+
if err := runCompileWorkflows(verbose, engineOverride, workflowsDir); err != nil {
149156
return fmt.Errorf("compile failed: %w", err)
150157
}
151158

@@ -243,8 +250,11 @@ func createUpdatePR(verbose bool) error {
243250
}
244251

245252
// UpdateWorkflows updates workflows from their source repositories
246-
func UpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, engineOverride string) error {
247-
workflowsDir := getWorkflowsDir()
253+
func UpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, workflowsDir string) error {
254+
// Use provided workflows directory or default
255+
if workflowsDir == "" {
256+
workflowsDir = getWorkflowsDir()
257+
}
248258

249259
// Find all workflows with source field
250260
workflows, err := findWorkflowsWithSource(workflowsDir, workflowNames, verbose)

pkg/cli/update_command_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,105 @@ Base content with upstream notes.`
355355
t.Error("Expected both local and upstream permission changes to be merged")
356356
}
357357
}
358+
359+
// TestFindWorkflowsWithSource_CustomDirectory tests that findWorkflowsWithSource works with custom directories
360+
func TestFindWorkflowsWithSource_CustomDirectory(t *testing.T) {
361+
// Create a temporary directory structure
362+
tmpDir := t.TempDir()
363+
customWorkflowDir := filepath.Join(tmpDir, "custom", "workflows")
364+
if err := os.MkdirAll(customWorkflowDir, 0755); err != nil {
365+
t.Fatalf("Failed to create custom workflow directory: %v", err)
366+
}
367+
368+
// Create a workflow file with source field
369+
workflowContent := `---
370+
on: push
371+
engine: claude
372+
source: test/repo/workflow.md@v1.0.0
373+
---
374+
375+
# Test Workflow
376+
377+
Test content.`
378+
379+
workflowPath := filepath.Join(customWorkflowDir, "test-workflow.md")
380+
if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil {
381+
t.Fatalf("Failed to write workflow file: %v", err)
382+
}
383+
384+
// Create a workflow file without source field
385+
workflowWithoutSource := `---
386+
on: push
387+
engine: claude
388+
---
389+
390+
# Another Workflow
391+
392+
No source field.`
393+
394+
workflowPath2 := filepath.Join(customWorkflowDir, "no-source.md")
395+
if err := os.WriteFile(workflowPath2, []byte(workflowWithoutSource), 0644); err != nil {
396+
t.Fatalf("Failed to write workflow file: %v", err)
397+
}
398+
399+
// Test findWorkflowsWithSource with custom directory
400+
workflows, err := findWorkflowsWithSource(customWorkflowDir, nil, false)
401+
if err != nil {
402+
t.Fatalf("Expected no error, got: %v", err)
403+
}
404+
405+
// Should find only one workflow (the one with source field)
406+
if len(workflows) != 1 {
407+
t.Errorf("Expected to find 1 workflow with source field, got %d", len(workflows))
408+
}
409+
410+
if len(workflows) > 0 {
411+
if workflows[0].Name != "test-workflow" {
412+
t.Errorf("Expected workflow name 'test-workflow', got '%s'", workflows[0].Name)
413+
}
414+
if workflows[0].SourceSpec != "test/repo/workflow.md@v1.0.0" {
415+
t.Errorf("Expected source spec 'test/repo/workflow.md@v1.0.0', got '%s'", workflows[0].SourceSpec)
416+
}
417+
}
418+
}
419+
420+
// TestUpdateWorkflows_CustomDirectory tests that UpdateWorkflows respects custom directory parameter
421+
func TestUpdateWorkflows_CustomDirectory(t *testing.T) {
422+
// Create a temporary directory structure
423+
tmpDir := t.TempDir()
424+
customWorkflowDir := filepath.Join(tmpDir, "custom", "workflows")
425+
if err := os.MkdirAll(customWorkflowDir, 0755); err != nil {
426+
t.Fatalf("Failed to create custom workflow directory: %v", err)
427+
}
428+
429+
// Create a workflow file with source field
430+
workflowContent := `---
431+
on: push
432+
engine: claude
433+
source: test/repo/workflow.md@v1.0.0
434+
---
435+
436+
# Test Workflow
437+
438+
Test content.`
439+
440+
workflowPath := filepath.Join(customWorkflowDir, "test-workflow.md")
441+
if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil {
442+
t.Fatalf("Failed to write workflow file: %v", err)
443+
}
444+
445+
// Test that findWorkflowsWithSource can find workflows in custom directory
446+
workflows, err := findWorkflowsWithSource(customWorkflowDir, nil, false)
447+
if err != nil {
448+
t.Fatalf("Expected no error finding workflows, got: %v", err)
449+
}
450+
451+
if len(workflows) == 0 {
452+
t.Fatal("Expected to find at least one workflow")
453+
}
454+
455+
// Verify the workflow was found in the custom directory
456+
if !strings.Contains(workflows[0].Path, customWorkflowDir) {
457+
t.Errorf("Expected workflow path to contain custom directory '%s', got '%s'", customWorkflowDir, workflows[0].Path)
458+
}
459+
}

0 commit comments

Comments
 (0)