Skip to content

Commit 1ecbb88

Browse files
authored
Migrate interactive workflow creation from add to new command (#3940)
1 parent f5398be commit 1ecbb88

6 files changed

Lines changed: 175 additions & 25 deletions

File tree

.github/workflows/ci-doctor.lock.yml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/daily-team-status.lock.yml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/super-linter.lock.yml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/gh-aw/main.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,54 @@ This is different from the optional 'name' field in the workflow frontmatter, wh
4646
}
4747

4848
var newCmd = &cobra.Command{
49-
Use: "new <workflow-base-name>",
49+
Use: "new [workflow-base-name]",
5050
Short: "Create a new workflow Markdown file with example configuration",
5151
Long: `Create a new workflow Markdown file with commented examples and explanations of all available options.
5252
53-
The created file will include comprehensive examples of:
53+
When called without a workflow name (or with --interactive flag), launches an interactive wizard
54+
to guide you through creating a workflow with custom settings.
55+
56+
When called with a workflow name, creates a template file with comprehensive examples of:
5457
- All trigger types (on: events)
5558
- Permissions configuration
5659
- AI processor settings
5760
- Tools configuration (github, claude, mcps)
5861
- All frontmatter options with explanations
5962
6063
Examples:
61-
` + constants.CLIExtensionPrefix + ` new my-workflow
64+
` + constants.CLIExtensionPrefix + ` new # Interactive mode
65+
` + constants.CLIExtensionPrefix + ` new --interactive # Interactive mode (explicit)
66+
` + constants.CLIExtensionPrefix + ` new my-workflow # Create template file
6267
` + constants.CLIExtensionPrefix + ` new issue-handler --force`,
63-
Args: cobra.ExactArgs(1),
68+
Args: cobra.MaximumNArgs(1),
6469
Run: func(cmd *cobra.Command, args []string) {
65-
workflowName := args[0]
6670
forceFlag, _ := cmd.Flags().GetBool("force")
6771
verbose, _ := cmd.Flags().GetBool("verbose")
72+
interactiveFlag, _ := cmd.Flags().GetBool("interactive")
73+
74+
// If no arguments provided or interactive flag is set, use interactive mode
75+
if len(args) == 0 || interactiveFlag {
76+
// Check if running in CI environment
77+
if cli.IsRunningInCI() {
78+
fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Interactive mode cannot be used in CI environments. Please provide a workflow name."))
79+
os.Exit(1)
80+
}
81+
82+
// Use default workflow name for interactive mode
83+
workflowName := "my-workflow"
84+
if len(args) > 0 {
85+
workflowName = args[0]
86+
}
87+
88+
if err := cli.CreateWorkflowInteractively(workflowName, verbose, forceFlag); err != nil {
89+
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
90+
os.Exit(1)
91+
}
92+
return
93+
}
94+
95+
// Template mode with workflow name
96+
workflowName := args[0]
6897
if err := cli.NewWorkflow(workflowName, verbose, forceFlag); err != nil {
6998
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
7099
os.Exit(1)
@@ -351,8 +380,9 @@ Use "` + constants.CLIExtensionPrefix + ` help all" to show help for all command
351380
// Create and setup init command
352381
initCmd := cli.NewInitCommand()
353382

354-
// Add force flag to new command
383+
// Add flags to new command
355384
newCmd.Flags().Bool("force", false, "Overwrite existing workflow files")
385+
newCmd.Flags().BoolP("interactive", "i", false, "Launch interactive workflow creation wizard")
356386

357387
// Add AI flag to compile and add commands
358388
compileCmd.Flags().StringP("engine", "e", "", "Override AI engine (claude, codex, copilot, custom)")

pkg/cli/add_command.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ Workflow specifications:
4545
The -n flag allows you to specify a custom name for the workflow file (only applies to the first workflow when adding multiple).
4646
The --dir flag allows you to specify a subdirectory under .github/workflows/ where the workflow will be added.
4747
The --pr flag automatically creates a pull request with the workflow changes.
48-
The --force flag overwrites existing workflow files.`,
48+
The --force flag overwrites existing workflow files.
49+
50+
Note: To create a new workflow from scratch, use the 'new' command instead.`,
51+
Args: cobra.MinimumNArgs(1),
4952
Run: func(cmd *cobra.Command, args []string) {
5053
workflows := args
5154
numberFlag, _ := cmd.Flags().GetInt("number")
@@ -58,18 +61,6 @@ The --force flag overwrites existing workflow files.`,
5861
noGitattributes, _ := cmd.Flags().GetBool("no-gitattributes")
5962
workflowDir, _ := cmd.Flags().GetString("dir")
6063

61-
// If no arguments provided and not in CI, automatically use interactive mode
62-
if len(args) == 0 && !IsRunningInCI() {
63-
// Auto-enable interactive mode
64-
var workflowName = "my-workflow" // Default name
65-
if err := CreateWorkflowInteractively(workflowName, verbose, false); err != nil {
66-
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
67-
os.Exit(1)
68-
}
69-
// Exit successfully after interactive creation
70-
os.Exit(0)
71-
}
72-
7364
if err := validateEngine(engineOverride); err != nil {
7465
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
7566
os.Exit(1)

pkg/cli/add_no_args_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package cli
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"testing"
7+
)
8+
9+
// TestAddCommandRequiresArguments verifies that the add command requires at least one argument
10+
func TestAddCommandRequiresArguments(t *testing.T) {
11+
// Save current directory
12+
originalDir, err := os.Getwd()
13+
if err != nil {
14+
t.Fatalf("Failed to get current directory: %v", err)
15+
}
16+
defer func() {
17+
// Restore original directory
18+
if err := os.Chdir(originalDir); err != nil {
19+
t.Logf("Warning: Failed to restore directory: %v", err)
20+
}
21+
}()
22+
23+
// Create a temporary directory for testing
24+
tmpDir := t.TempDir()
25+
if err := os.Chdir(tmpDir); err != nil {
26+
t.Fatalf("Failed to change to temp dir: %v", err)
27+
}
28+
29+
// Initialize git repo
30+
if err := os.MkdirAll(".git", 0755); err != nil {
31+
t.Fatalf("Failed to create .git dir: %v", err)
32+
}
33+
34+
// Create add command
35+
validateEngine := func(engine string) error { return nil }
36+
cmd := NewAddCommand(validateEngine)
37+
38+
// Set up stderr capture
39+
stderr := &bytes.Buffer{}
40+
cmd.SetErr(stderr)
41+
42+
// Try to execute without arguments
43+
cmd.SetArgs([]string{})
44+
45+
// Execute and expect an error
46+
err = cmd.Execute()
47+
if err == nil {
48+
t.Error("Expected error when calling add without arguments, got nil")
49+
}
50+
51+
// Verify the error message mentions required arguments
52+
errMsg := err.Error()
53+
if errMsg != "requires at least 1 arg(s), only received 0" {
54+
t.Errorf("Expected 'requires at least 1 arg(s)' error, got: %s", errMsg)
55+
}
56+
}
57+
58+
// TestAddCommandWithWorkflow verifies that add command works with a workflow specification
59+
func TestAddCommandWithWorkflow(t *testing.T) {
60+
// This test verifies that the command accepts arguments correctly
61+
// We don't actually execute it since that would require network access
62+
63+
validateEngine := func(engine string) error { return nil }
64+
cmd := NewAddCommand(validateEngine)
65+
66+
// Verify command accepts arguments
67+
if cmd.Args == nil {
68+
t.Error("Add command should have Args validator")
69+
}
70+
71+
// Test Args validator directly
72+
argsValidator := cmd.Args
73+
if argsValidator != nil {
74+
// Should accept one argument
75+
if err := argsValidator(cmd, []string{"githubnext/agentics/ci-doctor"}); err != nil {
76+
t.Errorf("Command should accept one workflow argument, got error: %v", err)
77+
}
78+
79+
// Should accept multiple arguments
80+
if err := argsValidator(cmd, []string{"githubnext/agentics/ci-doctor", "githubnext/agentics/daily-plan"}); err != nil {
81+
t.Errorf("Command should accept multiple workflow arguments, got error: %v", err)
82+
}
83+
84+
// Should reject empty arguments
85+
if err := argsValidator(cmd, []string{}); err == nil {
86+
t.Error("Command should reject empty arguments, but no error was returned")
87+
}
88+
}
89+
}
90+
91+
// TestAddCommandHelpText verifies the help text mentions the new command for creating workflows
92+
func TestAddCommandHelpText(t *testing.T) {
93+
validateEngine := func(engine string) error { return nil }
94+
cmd := NewAddCommand(validateEngine)
95+
96+
// Check that the long description mentions the 'new' command
97+
longDesc := cmd.Long
98+
if longDesc == "" {
99+
t.Error("Add command should have a long description")
100+
}
101+
102+
// Verify the note about using 'new' command is present
103+
expectedNote := "Note: To create a new workflow from scratch, use the 'new' command instead."
104+
if len(longDesc) > 0 {
105+
found := false
106+
for i := 0; i <= len(longDesc)-len(expectedNote); i++ {
107+
if longDesc[i:i+len(expectedNote)] == expectedNote {
108+
found = true
109+
break
110+
}
111+
}
112+
if !found {
113+
t.Errorf("Add command help text should mention 'new' command")
114+
}
115+
}
116+
}
117+
118+
// TestNewCommandAcceptsOptionalArgument verifies that new command accepts optional workflow name
119+
func TestNewCommandAcceptsOptionalArgument(t *testing.T) {
120+
// Note: We can't easily test the actual command defined in main.go from here,
121+
// but we can document the expected behavior
122+
123+
// The new command should:
124+
// 1. Accept zero arguments (interactive mode)
125+
// 2. Accept one argument (template mode with workflow name)
126+
// 3. Reject more than one argument
127+
128+
// This is enforced by cobra.MaximumNArgs(1) in main.go
129+
}

0 commit comments

Comments
 (0)