From 124167335e678af901e1d4be653cd81a16e7a1a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:01:21 +0000 Subject: [PATCH 1/5] Initial plan From 71d9e8902ef764d8bcea97580ae0ac03c3977f6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:20:26 +0000 Subject: [PATCH 2/5] feat: add --engine and --repo to upgrade, --approve to update (F1, F2, F4) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- docs/src/content/docs/setup/cli.md | 6 ++-- pkg/cli/add_workflow_compilation.go | 7 ++-- pkg/cli/update_actions.go | 8 ++--- pkg/cli/update_actions_test.go | 4 +-- pkg/cli/update_command.go | 11 +++--- pkg/cli/update_command_test.go | 13 +++++-- pkg/cli/update_manifest.go | 4 +-- pkg/cli/update_workflows.go | 3 +- pkg/cli/upgrade_command.go | 53 +++++++++++++++++++++-------- pkg/cli/upgrade_command_test.go | 28 +++++++++++++++ 10 files changed, 102 insertions(+), 35 deletions(-) diff --git a/docs/src/content/docs/setup/cli.md b/docs/src/content/docs/setup/cli.md index adeeaa31269..d37ea2b1c52 100644 --- a/docs/src/content/docs/setup/cli.md +++ b/docs/src/content/docs/setup/cli.md @@ -706,7 +706,7 @@ gh aw update --create-pull-request # Update and open a pull request gh aw update --org my-org --create-issue --yes # Auto-accept per-repo confirmations (required in CI) ``` -**Options:** `--dir/-d`, `--no-merge`, `--major`, `--force/-f`, `--engine/-e`, `--no-stop-after`, `--stop-after`, `--no-release-bump`, `--no-security-scanner`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--no-compile`, `--no-redirect`, `--cool-down`, `--repo/-r` +**Options:** `--dir/-d`, `--no-merge`, `--major`, `--force/-f`, `--engine/-e`, `--no-stop-after`, `--stop-after`, `--no-release-bump`, `--no-security-scanner`, `--approve`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--no-compile`, `--no-redirect`, `--cool-down`, `--repo/-r` Org mode (`--org`) previews or creates workflow update pull requests across every repository in an organization. Use `--repos` to limit org mode to repositories matching one or more glob patterns, `--create-issue` to open an issue in each repository that has pending updates (requires `--org`), and `--yes/-y` to auto-accept per-repository confirmations (required in CI). @@ -739,12 +739,14 @@ Upgrade repository with latest agent files and apply codemods to all workflows. gh aw upgrade # Upgrade repository agent files and all workflows gh aw upgrade --no-fix # Update agent files only (skip codemods, actions, and compilation) gh aw upgrade --create-pull-request # Upgrade and open a pull request +gh aw upgrade --engine claude # Override AI engine for compilation +gh aw upgrade --repo owner/repo # Upgrade workflows in another repository gh aw upgrade --audit # Run dependency health audit gh aw upgrade --audit --json # Dependency audit in JSON format gh aw upgrade --org my-org --create-issue --yes # Auto-accept per-repo confirmations (required in CI) ``` -**Options:** `--dir/-d`, `--no-fix`, `--no-actions`, `--no-compile`, `--disable-codemod`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--audit`, `--json/-j`, `--approve`, `--pre-releases` +**Options:** `--dir/-d`, `--engine/-e`, `--repo/-r`, `--no-fix`, `--no-actions`, `--no-compile`, `--disable-codemod`, `--create-pull-request`, `--create-issue`, `--org`, `--repos`, `--yes/-y`, `--audit`, `--json/-j`, `--approve`, `--pre-releases` Org mode (`--org`) previews or creates upgrade pull requests across every repository in an organization. Use `--repos` to limit org mode to repositories matching one or more glob patterns, `--create-issue` to open an issue in each org repository with agentic workflows (requires `--org`), and `--yes/-y` to auto-accept org-mode upgrade confirmations (required in CI). diff --git a/pkg/cli/add_workflow_compilation.go b/pkg/cli/add_workflow_compilation.go index ed52c347f80..6fdcca8d191 100644 --- a/pkg/cli/add_workflow_compilation.go +++ b/pkg/cli/add_workflow_compilation.go @@ -19,13 +19,13 @@ var addWorkflowCompilationLog = logger.New("cli:add_workflow_compilation") // compileWorkflow compiles a workflow file without refreshing stop time. // This is a convenience wrapper around compileWorkflowWithRefresh. func compileWorkflow(ctx context.Context, filePath string, verbose bool, quiet bool, engineOverride string) error { - return compileWorkflowWithRefresh(ctx, filePath, verbose, quiet, engineOverride, false) + return compileWorkflowWithRefresh(ctx, filePath, verbose, quiet, engineOverride, false, false) } // compileWorkflowWithRefresh compiles a workflow file with optional stop time refresh. // This function handles the compilation process and ensures .gitattributes is updated. -func compileWorkflowWithRefresh(ctx context.Context, filePath string, verbose bool, quiet bool, engineOverride string, refreshStopTime bool) error { - addWorkflowCompilationLog.Printf("Compiling workflow: file=%s, refresh_stop_time=%v, engine=%s", filePath, refreshStopTime, engineOverride) +func compileWorkflowWithRefresh(ctx context.Context, filePath string, verbose bool, quiet bool, engineOverride string, refreshStopTime bool, approve bool) error { + addWorkflowCompilationLog.Printf("Compiling workflow: file=%s, refresh_stop_time=%v, engine=%s, approve=%v", filePath, refreshStopTime, engineOverride, approve) // Create compiler with auto-detected version and action mode compiler := workflow.NewCompiler( @@ -34,6 +34,7 @@ func compileWorkflowWithRefresh(ctx context.Context, filePath string, verbose bo ) compiler.SetRefreshStopTime(refreshStopTime) + compiler.SetApprove(approve) compiler.SetQuiet(quiet) if err := CompileWorkflowWithValidation(ctx, compiler, filePath, CompileValidationOptions{Verbose: verbose}); err != nil { addWorkflowCompilationLog.Printf("Compilation failed: %v", err) diff --git a/pkg/cli/update_actions.go b/pkg/cli/update_actions.go index 5a537a68bd8..d779d4b0d9e 100644 --- a/pkg/cli/update_actions.go +++ b/pkg/cli/update_actions.go @@ -599,11 +599,11 @@ type latestReleaseResult struct { // major version. Updated files are recompiled. By default all actions are updated to // the latest major version; pass disableReleaseBump=true to only update core // (actions/*) references. -func UpdateActionsInWorkflowFiles(ctx context.Context, workflowsDir, engineOverride string, verbose, disableReleaseBump bool, noCompile bool, coolDown time.Duration) error { - return updateActionsInWorkflowFiles(ctx, defaultActionUpdateDeps(), workflowsDir, engineOverride, verbose, disableReleaseBump, noCompile, coolDown) +func UpdateActionsInWorkflowFiles(ctx context.Context, workflowsDir, engineOverride string, verbose, disableReleaseBump bool, noCompile bool, coolDown time.Duration, approve bool) error { + return updateActionsInWorkflowFiles(ctx, defaultActionUpdateDeps(), workflowsDir, engineOverride, verbose, disableReleaseBump, noCompile, coolDown, approve) } -func updateActionsInWorkflowFiles(ctx context.Context, deps actionUpdateDeps, workflowsDir, engineOverride string, verbose, disableReleaseBump bool, noCompile bool, coolDown time.Duration) error { +func updateActionsInWorkflowFiles(ctx context.Context, deps actionUpdateDeps, workflowsDir, engineOverride string, verbose, disableReleaseBump bool, noCompile bool, coolDown time.Duration, approve bool) error { if workflowsDir == "" { workflowsDir = getWorkflowsDir() } @@ -664,7 +664,7 @@ func updateActionsInWorkflowFiles(ctx context.Context, deps actionUpdateDeps, wo // Recompile the updated workflow (unless --no-compile is set) if !noCompile { - if err := compileWorkflowWithRefresh(ctx, path, verbose, false, engineOverride, false); err != nil { + if err := compileWorkflowWithRefresh(ctx, path, verbose, false, engineOverride, false, approve); err != nil { if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to recompile %s: %v", path, err))) } diff --git a/pkg/cli/update_actions_test.go b/pkg/cli/update_actions_test.go index 2ab80fe3b03..897511a7f02 100644 --- a/pkg/cli/update_actions_test.go +++ b/pkg/cli/update_actions_test.go @@ -839,7 +839,7 @@ func TestUpdateActionsInWorkflowFiles_UpdatesUsesReferences(t *testing.T) { t.Fatalf("failed to write workflow file: %v", err) } - if err := updateActionsInWorkflowFiles(context.Background(), deps, workflowsDir, "", false, false, true, 0); err != nil { + if err := updateActionsInWorkflowFiles(context.Background(), deps, workflowsDir, "", false, false, true, 0, false); err != nil { t.Fatalf("UpdateActionsInWorkflowFiles() error = %v", err) } @@ -872,7 +872,7 @@ func TestUpdateActionsInWorkflowFiles_NeverDowngrades(t *testing.T) { t.Fatalf("failed to write workflow file: %v", err) } - if err := updateActionsInWorkflowFiles(context.Background(), deps, workflowsDir, "", false, false, true, 0); err != nil { + if err := updateActionsInWorkflowFiles(context.Background(), deps, workflowsDir, "", false, false, true, 0, false); err != nil { t.Fatalf("UpdateActionsInWorkflowFiles() error = %v", err) } diff --git a/pkg/cli/update_command.go b/pkg/cli/update_command.go index 1fc0a457d82..626b74e5528 100644 --- a/pkg/cli/update_command.go +++ b/pkg/cli/update_command.go @@ -83,6 +83,7 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr disableSecurityScanner, _ := cmd.Flags().GetBool("no-security-scanner") disableSecurityScannerLegacy, _ := cmd.Flags().GetBool("disable-security-scanner") disableSecurityScanner = disableSecurityScanner || disableSecurityScannerLegacy + approveFlag, _ := cmd.Flags().GetBool("approve") createPRFlag, _ := cmd.Flags().GetBool("create-pull-request") prFlagAlias, _ := cmd.Flags().GetBool("pr") createPR := createPRFlag || prFlagAlias @@ -136,6 +137,7 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr NoRedirect: noRedirect, DisableSecurityScanner: disableSecurityScanner, CoolDown: coolDown, + Approve: approveFlag, } if targetRepo != "" { @@ -173,6 +175,7 @@ Note: In GitHub Enterprise repos, shorthand source specs resolve on your enterpr cmd.Flags().Bool("no-security-scanner", false, "Skip security scanning of workflow markdown content") cmd.Flags().Bool("disable-security-scanner", false, "Skip security scanning of workflow markdown content") _ = cmd.Flags().MarkDeprecated("disable-security-scanner", "use --no-security-scanner instead") + cmd.Flags().Bool("approve", false, "Approve all safe update changes. When strict mode is active (the default), the compiler emits warnings for new restricted secrets or unapproved action additions/removals not present in the existing gh-aw-manifest. Use this flag to approve and skip safe update enforcement") cmd.Flags().Bool("no-compile", false, "Skip recompiling workflows during update (do not modify lock files)") cmd.Flags().Bool("no-redirect", false, "Refuse updates when redirect frontmatter is present") cmd.Flags().String("org", "", "Preview or create workflow update pull requests across an organization") @@ -216,7 +219,7 @@ func RunUpdateWorkflows(ctx context.Context, opts UpdateWorkflowsOptions) error // Update action references in user-provided steps within workflow .md files. // By default all org/repo@version references are updated to the latest major version. updateLog.Print("Updating action references in workflow .md files") - if err := UpdateActionsInWorkflowFiles(ctx, opts.WorkflowsDir, opts.EngineOverride, opts.Verbose, opts.DisableReleaseBump, opts.NoCompile, opts.CoolDown); err != nil { + if err := UpdateActionsInWorkflowFiles(ctx, opts.WorkflowsDir, opts.EngineOverride, opts.Verbose, opts.DisableReleaseBump, opts.NoCompile, opts.CoolDown, opts.Approve); err != nil { // Non-fatal: warn but don't fail the update fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: Failed to update action references in workflow files: %v", err))) } @@ -237,7 +240,7 @@ func RunUpdateWorkflows(ctx context.Context, opts UpdateWorkflowsOptions) error if newContainerPins && !opts.NoCompile { updateLog.Print("Recompiling workflows to embed new container digest pins") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recompiling workflows to embed container digest pins...")) - recompileErr := recompileAllWorkflows(ctx, opts.WorkflowsDir, opts.EngineOverride, opts.Verbose) + recompileErr := recompileAllWorkflows(ctx, opts.WorkflowsDir, opts.EngineOverride, opts.Verbose, opts.Approve) if recompileErr != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: Failed to recompile workflows after container pin update: %v", recompileErr))) } @@ -250,7 +253,7 @@ func RunUpdateWorkflows(ctx context.Context, opts UpdateWorkflowsOptions) error // recompileAllWorkflows recompiles all .md workflow files in the given directory. // This is used after container pin updates to embed digest-pinned image references // in the generated lock files. -func recompileAllWorkflows(ctx context.Context, workflowsDir, engineOverride string, verbose bool) error { +func recompileAllWorkflows(ctx context.Context, workflowsDir, engineOverride string, verbose bool, approve bool) error { if workflowsDir == "" { workflowsDir = getWorkflowsDir() } @@ -265,7 +268,7 @@ func recompileAllWorkflows(ctx context.Context, workflowsDir, engineOverride str continue } path := filepath.Join(workflowsDir, entry.Name()) - if err := compileWorkflowWithRefresh(ctx, path, verbose, true, engineOverride, false); err != nil { + if err := compileWorkflowWithRefresh(ctx, path, verbose, true, engineOverride, false, approve); err != nil { if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to recompile %s: %v", entry.Name(), err))) } diff --git a/pkg/cli/update_command_test.go b/pkg/cli/update_command_test.go index 2b6abcc3436..7a60dc45628 100644 --- a/pkg/cli/update_command_test.go +++ b/pkg/cli/update_command_test.go @@ -123,6 +123,15 @@ func TestNewUpdateCommand_MentionsEnterpriseSourceResolution(t *testing.T) { assert.Contains(t, cmd.Long, "Use full https://github.com/... source URLs for other public github.com workflows.") } +func TestNewUpdateCommand_HasApproveFlag(t *testing.T) { + cmd := NewUpdateCommand(func(string) error { return nil }) + require.NotNil(t, cmd, "update command should be created") + + flag := cmd.Flags().Lookup("approve") + require.NotNil(t, flag, "update command should register --approve flag") + assert.Contains(t, flag.Usage, "When strict mode is active", "--approve description should match compile/upgrade semantics") +} + // TestMergeWorkflowContent_WithConflicts tests a merge with conflicts func TestMergeWorkflowContent_WithConflicts(t *testing.T) { base := `--- @@ -760,7 +769,7 @@ This is a test workflow. // Test with refreshStopTime=false (should preserve existing stop time if lock exists) t.Run("compileWorkflowWithRefresh false", func(t *testing.T) { - err := compileWorkflowWithRefresh(context.Background(), workflowFile, false, false, "", false) + err := compileWorkflowWithRefresh(context.Background(), workflowFile, false, false, "", false, false) if err != nil { t.Logf("Compilation failed (expected in test environment): %v", err) // In a test environment without full setup, compilation may fail, @@ -770,7 +779,7 @@ This is a test workflow. // Test with refreshStopTime=true (should regenerate stop time) t.Run("compileWorkflowWithRefresh true", func(t *testing.T) { - err := compileWorkflowWithRefresh(context.Background(), workflowFile, false, false, "", true) + err := compileWorkflowWithRefresh(context.Background(), workflowFile, false, false, "", true, false) if err != nil { t.Logf("Compilation failed (expected in test environment): %v", err) // In a test environment without full setup, compilation may fail, diff --git a/pkg/cli/update_manifest.go b/pkg/cli/update_manifest.go index 6e35878bcb2..2ef306cf2c6 100644 --- a/pkg/cli/update_manifest.go +++ b/pkg/cli/update_manifest.go @@ -277,7 +277,7 @@ func updateManifestManagedWorkflow(ctx context.Context, update manifestManagedWo } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Updated %s from %s to %s", update.wf.Name, shortRef(update.currentRef), shortRef(update.latestRef)))) if !opts.NoCompile { - if err := compileWorkflowWithRefresh(ctx, update.wf.Path, opts.Verbose, false, opts.EngineOverride, true); err != nil { + if err := compileWorkflowWithRefresh(ctx, update.wf.Path, opts.Verbose, false, opts.EngineOverride, true, opts.Approve); err != nil { return fmt.Errorf("failed to compile updated workflow: %w", err) } } @@ -318,7 +318,7 @@ func addManifestManagedWorkflow(ctx context.Context, targetDir, name, repo, late } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Added new workflow from manifest: "+filepath.Base(destPath))) if !opts.NoCompile { - if err := compileWorkflowWithRefresh(ctx, destPath, opts.Verbose, false, opts.EngineOverride, true); err != nil { + if err := compileWorkflowWithRefresh(ctx, destPath, opts.Verbose, false, opts.EngineOverride, true, opts.Approve); err != nil { return fmt.Errorf("failed to compile new manifest workflow: %w", err) } } diff --git a/pkg/cli/update_workflows.go b/pkg/cli/update_workflows.go index 7be166979c3..d1a2abc2bdb 100644 --- a/pkg/cli/update_workflows.go +++ b/pkg/cli/update_workflows.go @@ -68,6 +68,7 @@ type UpdateWorkflowsOptions struct { NoCompile bool NoRedirect bool CoolDown time.Duration + Approve bool } // UpdateWorkflows updates workflows from their source repositories @@ -818,7 +819,7 @@ func updateWorkflow(ctx context.Context, wf *workflowWithSource, opts UpdateWork // Compile the updated workflow with refreshStopTime enabled (unless --no-compile is set) if !opts.NoCompile { updateLog.Printf("Compiling updated workflow: %s", wf.Name) - if err := compileWorkflowWithRefresh(ctx, wf.Path, opts.Verbose, false, opts.EngineOverride, true); err != nil { + if err := compileWorkflowWithRefresh(ctx, wf.Path, opts.Verbose, false, opts.EngineOverride, true, opts.Approve); err != nil { updateLog.Printf("Compilation failed for workflow %s: %v", wf.Name, err) return fmt.Errorf("failed to compile updated workflow: %w", err) } diff --git a/pkg/cli/upgrade_command.go b/pkg/cli/upgrade_command.go index 2ecf6da8cd5..9218b72bc75 100644 --- a/pkg/cli/upgrade_command.go +++ b/pkg/cli/upgrade_command.go @@ -57,21 +57,23 @@ Use --audit to check dependency health without performing upgrades. This include The --audit flag skips the normal upgrade process. This command always upgrades all Markdown files in .github/workflows.`, - Example: ` ` + string(constants.CLIExtensionPrefix) + ` upgrade # Upgrade all workflows - ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-fix # Update agent files only (skip codemods, actions, and compilation) - ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-actions # Skip updating GitHub Actions versions - ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-compile # Skip recompiling workflows (do not modify lock files) - ` + string(constants.CLIExtensionPrefix) + ` upgrade --create-pull-request # Upgrade and open a pull request - ` + string(constants.CLIExtensionPrefix) + ` upgrade --dir custom/workflows # Upgrade workflows in custom directory - ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org # Preview upgrade pull requests across an organization + Example: ` ` + string(constants.CLIExtensionPrefix) + ` upgrade # Upgrade all workflows + ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-fix # Update agent files only (skip codemods, actions, and compilation) + ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-actions # Skip updating GitHub Actions versions + ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-compile # Skip recompiling workflows (do not modify lock files) + ` + string(constants.CLIExtensionPrefix) + ` upgrade --create-pull-request # Upgrade and open a pull request + ` + string(constants.CLIExtensionPrefix) + ` upgrade --dir custom/workflows # Upgrade workflows in custom directory + ` + string(constants.CLIExtensionPrefix) + ` upgrade --engine claude # Override AI engine for compilation + ` + string(constants.CLIExtensionPrefix) + ` upgrade --repo owner/repo # Upgrade workflows in another repository + ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org # Preview upgrade pull requests across an organization ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org --repos '*-service' # Limit org mode to matching repositories ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org --create-pull-request # Open upgrade pull requests in org repositories ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org --create-pull-request --yes # Auto-accept per-repo confirmations for PR creation (required in CI) ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org --create-issue # Open issues in org repos with agentic workflows ` + string(constants.CLIExtensionPrefix) + ` upgrade --org my-org --create-issue --yes # Auto-accept per-repo confirmations (required in CI) - ` + string(constants.CLIExtensionPrefix) + ` upgrade --audit # Check dependency health without upgrading - ` + string(constants.CLIExtensionPrefix) + ` upgrade --audit --json # Output audit results in JSON format - ` + string(constants.CLIExtensionPrefix) + ` upgrade --pre-releases # Include pre-release versions when upgrading the extension (stable releases are the default)`, + ` + string(constants.CLIExtensionPrefix) + ` upgrade --audit # Check dependency health without upgrading + ` + string(constants.CLIExtensionPrefix) + ` upgrade --audit --json # Output audit results in JSON format + ` + string(constants.CLIExtensionPrefix) + ` upgrade --pre-releases # Include pre-release versions when upgrading the extension (stable releases are the default)`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { verbose, _ := cmd.Flags().GetBool("verbose") @@ -90,9 +92,15 @@ This command always upgrades all Markdown files in .github/workflows.`, skipExtensionUpgrade, _ := cmd.Flags().GetBool("skip-extension-upgrade") approveUpgrade, _ := cmd.Flags().GetBool("approve") preReleases, _ := cmd.Flags().GetBool("pre-releases") + engineOverride, _ := cmd.Flags().GetString("engine") + targetRepo, _ := cmd.Flags().GetString("repo") targetOrg, _ := cmd.Flags().GetString("org") repoGlobs, _ := cmd.Flags().GetStringSlice("repos") + if targetRepo != "" && targetOrg != "" { + return errors.New("cannot specify both --repo and --org flags; use --repo for a single repository or --org for organization-wide upgrades") + } + if len(repoGlobs) > 0 && targetOrg == "" { return errors.New("--repos requires --org to be specified") } @@ -122,6 +130,16 @@ This command always upgrades all Markdown files in .github/workflows.`, approve: approveUpgrade, preReleases: preReleases, yes: yes, + engineOverride: engineOverride, + } + + if targetRepo != "" { + if createPR { + if err := PreflightCheckForCreatePR(verbose); err != nil { + return err + } + } + return runUpgradeForTargetRepo(cmd.Context(), targetRepo, opts, verbose) } if targetOrg != "" { @@ -149,6 +167,8 @@ This command always upgrades all Markdown files in .github/workflows.`, }, } + addEngineFlag(cmd) + addRepoFlag(cmd) cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: $GH_AW_WORKFLOWS_DIR or .github/workflows)") cmd.Flags().Bool("no-fix", false, "Skip codemods, action version updates, and workflow compilation (only update agent files)") cmd.Flags().Bool("no-actions", false, "Skip updating GitHub Actions versions (ignored when --no-fix is set)") @@ -169,6 +189,7 @@ This command always upgrades all Markdown files in .github/workflows.`, addJSONFlag(cmd) // Register completions + RegisterEngineFlagCompletion(cmd) RegisterDirFlagCompletion(cmd, "dir") return cmd @@ -206,6 +227,7 @@ type upgradeOptions struct { approve bool preReleases bool yes bool + engineOverride string } // runUpgradeCommand executes the upgrade process @@ -295,7 +317,7 @@ func runUpgradeCommand(opts upgradeOptions) error { // was successfully updated, so both files stay in sync. Compilation is // deferred to Step 4. upgradeLog.Print("Updating action references in workflow .md files") - if err := UpdateActionsInWorkflowFiles(opts.ctx, opts.workflowDir, "", opts.verbose, false, true, 0); err != nil { + if err := UpdateActionsInWorkflowFiles(opts.ctx, opts.workflowDir, opts.engineOverride, opts.verbose, false, true, 0, false); err != nil { msg := fmt.Sprintf("Failed to update action references in workflow files: %v", err) upgradeLog.Print(msg) // Non-critical: warn but don't fail the upgrade @@ -323,9 +345,10 @@ func runUpgradeCommand(opts upgradeOptions) error { // Create and configure compiler compiler := createAndConfigureCompiler(CompileConfig{ - Verbose: opts.verbose, - WorkflowDir: opts.workflowDir, - Approve: opts.approve, + Verbose: opts.verbose, + WorkflowDir: opts.workflowDir, + Approve: opts.approve, + EngineOverride: opts.engineOverride, }) // Determine workflow directory @@ -383,7 +406,7 @@ func runUpgradeCommand(opts upgradeOptions) error { if newPins && !opts.noCompile { upgradeLog.Print("Recompiling workflows to embed new container digest pins") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recompiling workflows to embed container digest pins...")) - if recompileErr := recompileAllWorkflows(opts.ctx, opts.workflowDir, "", opts.verbose); recompileErr != nil { + if recompileErr := recompileAllWorkflows(opts.ctx, opts.workflowDir, opts.engineOverride, opts.verbose, opts.approve); recompileErr != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: Failed to recompile after container pin update: %v", recompileErr))) } } diff --git a/pkg/cli/upgrade_command_test.go b/pkg/cli/upgrade_command_test.go index 1690caaf6ad..cb09e3a7165 100644 --- a/pkg/cli/upgrade_command_test.go +++ b/pkg/cli/upgrade_command_test.go @@ -30,3 +30,31 @@ func TestUpgradeCommandHelpTextConsistency(t *testing.T) { assert.Equal(t, "stringSlice", disableCodemodFlag.Value.Type()) assert.Contains(t, disableCodemodFlag.Usage, "Disable specific codemod IDs", "--disable-codemod usage should describe codemod exclusion") } + +func TestUpgradeCommandNewFlags(t *testing.T) { + cmd := NewUpgradeCommand() + require.NotNil(t, cmd, "upgrade command should be created") + + // F1: --engine flag + engineFlag := cmd.Flags().Lookup("engine") + require.NotNil(t, engineFlag, "--engine/-e flag should exist on upgrade command") + assert.Equal(t, "e", engineFlag.Shorthand, "--engine flag should have -e shorthand") + assert.Contains(t, engineFlag.Usage, "Override AI engine", "--engine description should describe engine override") + assert.Contains(t, cmd.Example, "--engine", "upgrade examples should show --engine usage") + + // F4: --repo flag + repoFlag := cmd.Flags().Lookup("repo") + require.NotNil(t, repoFlag, "--repo/-r flag should exist on upgrade command") + assert.Equal(t, "r", repoFlag.Shorthand, "--repo flag should have -r shorthand") + assert.Contains(t, repoFlag.Usage, "Target repository", "--repo description should describe target repository") + assert.Contains(t, cmd.Example, "--repo", "upgrade examples should show --repo usage") +} + +func TestUpgradeCommandRepoOrgMutualExclusion(t *testing.T) { + cmd := NewUpgradeCommand() + cmd.SetArgs([]string{"--repo", "owner/repo", "--org", "my-org"}) + err := cmd.Execute() + require.Error(t, err, "should error when both --repo and --org are specified") + assert.Contains(t, err.Error(), "--repo", "error should mention --repo flag") + assert.Contains(t, err.Error(), "--org", "error should mention --org flag") +} From 0654ec87645a70b17527f08dbfd7ec35b60ea3ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:22:17 +0000 Subject: [PATCH 3/5] test: expand upgrade command flag registration and mutual exclusion tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/upgrade_command_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/cli/upgrade_command_test.go b/pkg/cli/upgrade_command_test.go index cb09e3a7165..27c4518ac96 100644 --- a/pkg/cli/upgrade_command_test.go +++ b/pkg/cli/upgrade_command_test.go @@ -58,3 +58,27 @@ func TestUpgradeCommandRepoOrgMutualExclusion(t *testing.T) { assert.Contains(t, err.Error(), "--repo", "error should mention --repo flag") assert.Contains(t, err.Error(), "--org", "error should mention --org flag") } + +func TestUpgradeCommandFlagRegistration(t *testing.T) { + cmd := NewUpgradeCommand() + require.NotNil(t, cmd, "upgrade command should be created") + + // --repo alone should be accepted at flag parse time (RunE will proceed) + repoFlag := cmd.Flags().Lookup("repo") + require.NotNil(t, repoFlag, "--repo flag should be registered") + + // --org alone should be accepted at flag parse time (RunE will proceed) + orgFlag := cmd.Flags().Lookup("org") + require.NotNil(t, orgFlag, "--org flag should be registered") + + // --engine alone should be accepted + engineFlag := cmd.Flags().Lookup("engine") + require.NotNil(t, engineFlag, "--engine flag should be registered") + + // --repos without --org should fail at RunE + cmd2 := NewUpgradeCommand() + cmd2.SetArgs([]string{"--repos", "foo-*"}) + err := cmd2.Execute() + require.Error(t, err, "should error when --repos is specified without --org") + assert.Contains(t, err.Error(), "--repos", "error should mention --repos flag") +} From 2b84357fbe77bdc041921570fd6136dff3ea357c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 03:34:11 +0000 Subject: [PATCH 4/5] docs(adr): add draft ADR-45573 for CLI flag consistency across upgrade/update commands Co-Authored-By: Claude Sonnet 4.6 --- ...573-cli-flag-consistency-upgrade-update.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/adr/45573-cli-flag-consistency-upgrade-update.md diff --git a/docs/adr/45573-cli-flag-consistency-upgrade-update.md b/docs/adr/45573-cli-flag-consistency-upgrade-update.md new file mode 100644 index 00000000000..d27497ea317 --- /dev/null +++ b/docs/adr/45573-cli-flag-consistency-upgrade-update.md @@ -0,0 +1,57 @@ +# ADR-45573: Add --engine, --repo, and --approve Flags to upgrade and update Commands for CLI Consistency + +**Date**: 2026-07-15 +**Status**: Draft +**Deciders**: Unknown + +--- + +### Context + +The `gh aw` CLI exposes several workflow management commands — `compile`, `validate`, `update`, and `upgrade` — that share an underlying compilation pipeline. A CLI consistency audit identified four high-severity flag gaps: the `upgrade` command was missing `--engine/-e` (AI engine override) and `--repo/-r` (target repository), while the `update` command was missing `--approve` (bypass strict-mode enforcement). Sibling commands already supported these flags, creating asymmetry that forced users to use different commands for equivalent operations or to work around missing controls. + +The `--approve` flag controls whether the compiler allows action additions/removals and restricted secrets not already present in the `gh-aw-manifest` during strict-mode compilation. The `--engine` flag allows overriding the AI engine used during workflow compilation. The `--repo` flag dispatches compilation work to a remote repository. + +### Decision + +We will add `--engine/-e` and `--repo/-r` to `upgrade` and `--approve` to `update`, threading the new values through to the shared compilation pipeline (`compileWorkflowWithRefresh`, `UpdateActionsInWorkflowFiles`, `recompileAllWorkflows`). We will also enforce `--repo`/`--org` mutual exclusion on `upgrade`, matching the existing guard on `update`. The `--no-security-scanner` gap on `compile`/`validate` (F3) is intentionally skipped because those commands operate exclusively on local files where the scanner is a no-op. + +### Alternatives Considered + +#### Alternative 1: Document the Asymmetry and Provide Workarounds + +Accept that `upgrade` and `update` expose fewer controls than `compile`/`validate`, and add documentation explaining which flags are available on which commands. Users needing `--engine` during upgrade would run a separate `compile` pass afterward. + +This was rejected because it degrades usability: users running upgrade in CI cannot override the engine mid-pipeline without invoking an extra command, and the asymmetry is not obvious from `--help` output alone. + +#### Alternative 2: Add Flag Declarations Without Wiring Them to the Pipeline + +Register the new flags on `upgrade`/`update` to satisfy the consistency checker but silently ignore their values, deferring the pipeline integration to a later PR. + +This was rejected because it would introduce misleading no-op flags — a worse UX than missing flags — and any CI automation that relied on the flags would silently get wrong behavior. + +#### Alternative 3: Unify Commands Behind a Single Orchestration Layer + +Refactor `upgrade` and `update` to delegate to `compile` internally, so any flag supported by `compile` is automatically available to callers. + +This was considered but rejected as out of scope for a consistency fix. It would require a larger architectural refactor that risks regressions across the entire compilation pipeline. + +### Consequences + +#### Positive +- `upgrade` and `update` now accept the same engine, repo, and approval controls as their sibling commands, removing user-visible asymmetry. +- Users can override the AI engine during upgrade without a separate `compile` pass — important in CI pipelines where compilation must use a specific engine. +- The `--repo`/`--org` mutual exclusion guard on `upgrade` matches `update` behavior, preventing ambiguous invocations. +- Flag coverage is verified by new unit tests for registration, description consistency, and mutual exclusion. + +#### Negative +- `compileWorkflowWithRefresh` and several internal helpers now take an additional `approve bool` parameter, growing the function signatures. Seven call sites required updates. +- The `approve` value is threaded through multiple layers (`update_command.go` → `update_manifest.go`, `update_workflows.go`, `update_actions.go`), increasing the surface area for future parameter drift if the compilation interface changes. + +#### Neutral +- F3 (`--no-security-scanner` on `compile`/`validate`) is explicitly deferred and documented as a no-op case; no code or flag was added. +- Documentation in `docs/src/content/docs/setup/cli.md` for both `update` and `upgrade` is updated to reflect the new flags. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* From de153a14bb1533afe4c16fc6e83dd407263d2912 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 04:05:24 +0000 Subject: [PATCH 5/5] fix: address review feedback on upgrade --repo and --engine flags - Add validateEngine param to NewUpgradeCommand; validate early in RunE before any upgrade work (fixes review comment on upgrade_command.go:95) - Add createPR param to runUpgradeForTargetRepo; gate PreflightCheckForCreatePR and CreatePRWithChanges on the flag so plain upgrade --repo does not unconditionally push and open a PR (fixes review comment on upgrade_command.go:138-142) - Use runUpgradeForTargetRepoFn (injectable stub) in upgrade_command.go --repo dispatch path so tests can intercept it - Fix error message: --org requires -> --repo/--org requires (fixes review comment on upgrade_command.go:142) - Add TestUpgradeCommandEngineValidationRunsEarly, TestUpgradeCommandRepoDispatchNoPR, and TestUpgradeCommandRepoDispatchWithPR behavioral tests (fixes review comment on upgrade_command_test.go:66-68) - Update all callers and stubs of runUpgradeForTargetRepoFn to new signature across upgrade_org.go, upgrade_org_test.go, main.go Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- cmd/gh-aw/main.go | 2 +- pkg/cli/upgrade_command.go | 13 +++---- pkg/cli/upgrade_command_test.go | 66 ++++++++++++++++++++++++++++++--- pkg/cli/upgrade_org.go | 16 +++++--- pkg/cli/upgrade_org_test.go | 24 ++++++------ 5 files changed, 91 insertions(+), 30 deletions(-) diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 1bf340514ce..9302012abb1 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -824,7 +824,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all prCmd := cli.NewPRCommand() secretsCmd := cli.NewSecretsCommand() fixCmd := cli.NewFixCommand() - upgradeCmd := cli.NewUpgradeCommand() + upgradeCmd := cli.NewUpgradeCommand(validateEngine) completionCmd := cli.NewCompletionCommand() hashCmd := cli.NewHashCommand() projectCmd := cli.NewProjectCommand() diff --git a/pkg/cli/upgrade_command.go b/pkg/cli/upgrade_command.go index 9218b72bc75..348b7d4801c 100644 --- a/pkg/cli/upgrade_command.go +++ b/pkg/cli/upgrade_command.go @@ -31,7 +31,7 @@ type UpgradeConfig struct { } // NewUpgradeCommand creates the upgrade command -func NewUpgradeCommand() *cobra.Command { +func NewUpgradeCommand(validateEngine func(string) error) *cobra.Command { cmd := &cobra.Command{ Use: "upgrade", Short: "Upgrade local agent files and workflows (codemods, action updates, and compilation)", @@ -97,6 +97,10 @@ This command always upgrades all Markdown files in .github/workflows.`, targetOrg, _ := cmd.Flags().GetString("org") repoGlobs, _ := cmd.Flags().GetStringSlice("repos") + if err := validateEngine(engineOverride); err != nil { + return err + } + if targetRepo != "" && targetOrg != "" { return errors.New("cannot specify both --repo and --org flags; use --repo for a single repository or --org for organization-wide upgrades") } @@ -134,12 +138,7 @@ This command always upgrades all Markdown files in .github/workflows.`, } if targetRepo != "" { - if createPR { - if err := PreflightCheckForCreatePR(verbose); err != nil { - return err - } - } - return runUpgradeForTargetRepo(cmd.Context(), targetRepo, opts, verbose) + return runUpgradeForTargetRepoFn(cmd.Context(), targetRepo, opts, createPR, verbose) } if targetOrg != "" { diff --git a/pkg/cli/upgrade_command_test.go b/pkg/cli/upgrade_command_test.go index 27c4518ac96..377ae90b1db 100644 --- a/pkg/cli/upgrade_command_test.go +++ b/pkg/cli/upgrade_command_test.go @@ -3,14 +3,18 @@ package cli import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// upgradeValidateEngineStub is a no-op engine validator for upgrade command tests. +func upgradeValidateEngineStub(engine string) error { return nil } + func TestUpgradeCommandHelpTextConsistency(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(upgradeValidateEngineStub) require.NotNil(t, cmd, "upgrade command should be created") assert.Contains(t, cmd.Long, "Upgrade the repository to the latest version of agentic workflows.", "long description should use correct grammar") @@ -32,7 +36,7 @@ func TestUpgradeCommandHelpTextConsistency(t *testing.T) { } func TestUpgradeCommandNewFlags(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(upgradeValidateEngineStub) require.NotNil(t, cmd, "upgrade command should be created") // F1: --engine flag @@ -50,8 +54,22 @@ func TestUpgradeCommandNewFlags(t *testing.T) { assert.Contains(t, cmd.Example, "--repo", "upgrade examples should show --repo usage") } +func TestUpgradeCommandEngineValidationRunsEarly(t *testing.T) { + validated := false + validate := func(engine string) error { + validated = true + assert.Equal(t, "bad-engine", engine) + return assert.AnError + } + cmd := NewUpgradeCommand(validate) + cmd.SetArgs([]string{"--engine", "bad-engine"}) + err := cmd.Execute() + require.Error(t, err, "invalid engine should fail early") + assert.True(t, validated, "engine validator should have been called") +} + func TestUpgradeCommandRepoOrgMutualExclusion(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(upgradeValidateEngineStub) cmd.SetArgs([]string{"--repo", "owner/repo", "--org", "my-org"}) err := cmd.Execute() require.Error(t, err, "should error when both --repo and --org are specified") @@ -60,7 +78,7 @@ func TestUpgradeCommandRepoOrgMutualExclusion(t *testing.T) { } func TestUpgradeCommandFlagRegistration(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(upgradeValidateEngineStub) require.NotNil(t, cmd, "upgrade command should be created") // --repo alone should be accepted at flag parse time (RunE will proceed) @@ -76,9 +94,47 @@ func TestUpgradeCommandFlagRegistration(t *testing.T) { require.NotNil(t, engineFlag, "--engine flag should be registered") // --repos without --org should fail at RunE - cmd2 := NewUpgradeCommand() + cmd2 := NewUpgradeCommand(upgradeValidateEngineStub) cmd2.SetArgs([]string{"--repos", "foo-*"}) err := cmd2.Execute() require.Error(t, err, "should error when --repos is specified without --org") assert.Contains(t, err.Error(), "--repos", "error should mention --repos flag") } + +// TestUpgradeCommandRepoDispatchNoPR verifies that plain `upgrade --repo` +// dispatches to the target-repo runner without requesting PR creation. +func TestUpgradeCommandRepoDispatchNoPR(t *testing.T) { + origFn := runUpgradeForTargetRepoFn + defer func() { runUpgradeForTargetRepoFn = origFn }() + + var capturedCreatePR bool + runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, createPR bool, _ bool) error { + capturedCreatePR = createPR + return nil + } + + cmd := NewUpgradeCommand(upgradeValidateEngineStub) + cmd.SetArgs([]string{"--repo", "owner/repo"}) + err := cmd.Execute() + require.NoError(t, err) + assert.False(t, capturedCreatePR, "plain --repo must not request PR creation") +} + +// TestUpgradeCommandRepoDispatchWithPR verifies that `upgrade --repo --create-pull-request` +// dispatches to the target-repo runner with PR creation requested. +func TestUpgradeCommandRepoDispatchWithPR(t *testing.T) { + origFn := runUpgradeForTargetRepoFn + defer func() { runUpgradeForTargetRepoFn = origFn }() + + var capturedCreatePR bool + runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, createPR bool, _ bool) error { + capturedCreatePR = createPR + return nil + } + + cmd := NewUpgradeCommand(upgradeValidateEngineStub) + cmd.SetArgs([]string{"--repo", "owner/repo", "--create-pull-request"}) + err := cmd.Execute() + require.NoError(t, err) + assert.True(t, capturedCreatePR, "--repo --create-pull-request must request PR creation") +} diff --git a/pkg/cli/upgrade_org.go b/pkg/cli/upgrade_org.go index d3a0b1f196f..ff08198a4db 100644 --- a/pkg/cli/upgrade_org.go +++ b/pkg/cli/upgrade_org.go @@ -43,7 +43,7 @@ func runUpgradeForOrg(ctx context.Context, org string, repoGlobs []string, opts }, ReportFn: renderOrgUpgradeReport, ApplyFn: func(ctx context.Context, preview orgRepoPreview, v bool) error { - return runUpgradeForTargetRepoFn(ctx, preview.Repo, opts, v) + return runUpgradeForTargetRepoFn(ctx, preview.Repo, opts, true, v) }, IssueFn: func(ctx context.Context, preview orgRepoPreview, v bool) error { return createIssueForUpgradeOrgRepoFn(ctx, preview.Repo, v) @@ -209,10 +209,10 @@ func normalizeDisplayVersion(version string) string { // runUpgradeForTargetRepo checks out repo to a temporary directory, runs the // upgrade command inside it, and opens a pull request with the resulting changes. -func runUpgradeForTargetRepo(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { +func runUpgradeForTargetRepo(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { gitRoot, err := gitutil.FindGitRoot() if err != nil { - return fmt.Errorf("--org requires running inside a git repository: %w", err) + return fmt.Errorf("--repo/--org requires running inside a git repository: %w", err) } updatesDir, err := ensureUpdateTargetRepoGitignore(gitRoot) @@ -252,8 +252,10 @@ func runUpgradeForTargetRepo(ctx context.Context, repo string, opts upgradeOptio return fmt.Errorf("failed to change directory to checkout %s: %w", checkoutDir, err) } - if err := PreflightCheckForCreatePR(verbose); err != nil { - return err + if createPR { + if err := PreflightCheckForCreatePR(verbose); err != nil { + return err + } } // Override fields that must be adjusted for a remote-repo upgrade. @@ -268,6 +270,10 @@ func runUpgradeForTargetRepo(ctx context.Context, repo string, opts upgradeOptio return err } + if !createPR { + return nil + } + // Skip PR creation when the upgrade produced no changes (e.g. repo is already up to date). changed, err := hasPendingChanges() if err != nil { diff --git a/pkg/cli/upgrade_org_test.go b/pkg/cli/upgrade_org_test.go index 4ba82896aab..be1eb752ca2 100644 --- a/pkg/cli/upgrade_org_test.go +++ b/pkg/cli/upgrade_org_test.go @@ -20,7 +20,7 @@ func mockScanUpgradeRepo(_ context.Context, repo string, _ bool) (orgRepoPreview } func TestNewUpgradeCommandOrgFlags(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(func(string) error { return nil }) require.NotNil(t, cmd.Flags().Lookup("org")) require.NotNil(t, cmd.Flags().Lookup("repos")) @@ -151,7 +151,7 @@ func TestRunUpgradeForOrgDryRun(t *testing.T) { return []string{"octo/api", "octo/web"}, nil } scanUpgradeRepoFn = mockScanUpgradeRepo - runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { + runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { t.Fatalf("unexpected upgrade call for %s", repo) return nil } @@ -184,7 +184,7 @@ func TestRunUpgradeForOrgDryRunShowsVersion(t *testing.T) { scanUpgradeRepoFn = func(_ context.Context, repo string, _ bool) (orgRepoPreview, bool, error) { return orgRepoPreview{Repo: repo, TotalWorkflows: 2, CurrentVersion: "v1.2.3"}, true, nil } - runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { + runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { t.Fatalf("unexpected upgrade call for %s", repo) return nil } @@ -216,7 +216,7 @@ func TestRunUpgradeForOrgCreatePR(t *testing.T) { } scanUpgradeRepoFn = mockScanUpgradeRepo var upgraded []string - runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { + runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { upgraded = append(upgraded, repo) return nil } @@ -243,7 +243,7 @@ func TestRunUpgradeForOrgRepoFilter(t *testing.T) { } scanUpgradeRepoFn = mockScanUpgradeRepo var upgraded []string - runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { + runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { upgraded = append(upgraded, repo) return nil } @@ -270,7 +270,7 @@ func TestRunUpgradeForOrgCreateIssue(t *testing.T) { return []string{"octo/api", "octo/web"}, nil } scanUpgradeRepoFn = mockScanUpgradeRepo - runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, verbose bool) error { + runUpgradeForTargetRepoFn = func(ctx context.Context, repo string, opts upgradeOptions, createPR bool, verbose bool) error { t.Fatalf("unexpected upgrade call for %s", repo) return nil } @@ -294,7 +294,7 @@ func TestRunUpgradeForOrgCreateIssue(t *testing.T) { } func TestRunUpgradeCommandCreateIssueRequiresOrg(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(func(string) error { return nil }) cmd.SetArgs([]string{"--create-issue"}) err := cmd.Execute() require.Error(t, err) @@ -302,7 +302,7 @@ func TestRunUpgradeCommandCreateIssueRequiresOrg(t *testing.T) { } func TestRunUpgradeCommandCreateIssueAndPRMutuallyExclusive(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(func(string) error { return nil }) cmd.SetArgs([]string{"--org", "octo", "--create-issue", "--create-pull-request"}) err := cmd.Execute() require.Error(t, err) @@ -310,7 +310,7 @@ func TestRunUpgradeCommandCreateIssueAndPRMutuallyExclusive(t *testing.T) { } func TestRunUpgradeCommandReposRequiresOrg(t *testing.T) { - cmd := NewUpgradeCommand() + cmd := NewUpgradeCommand(func(string) error { return nil }) cmd.SetArgs([]string{"--repos", "*-svc"}) err := cmd.Execute() require.Error(t, err) @@ -328,7 +328,7 @@ func TestRunUpgradeForOrgSkipsFailedRepos(t *testing.T) { scanUpgradeRepoFn = mockScanUpgradeRepo boom := errors.New("upgrade failed") var called []string - runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool) error { + runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool, _ bool) error { called = append(called, repo) return boom } @@ -356,7 +356,7 @@ func TestRunUpgradeForOrgCreateIssueSkipsFailedRepos(t *testing.T) { return []string{"octo/api", "octo/web"}, nil } scanUpgradeRepoFn = mockScanUpgradeRepo - runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool) error { + runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool, _ bool) error { t.Fatalf("unexpected upgrade call for %s", repo) return nil } @@ -391,7 +391,7 @@ func TestRunUpgradeForOrgSortsAlphabetically(t *testing.T) { } scanUpgradeRepoFn = mockScanUpgradeRepo var called []string - runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool) error { + runUpgradeForTargetRepoFn = func(_ context.Context, repo string, _ upgradeOptions, _ bool, _ bool) error { called = append(called, repo) return nil }