diff --git a/.github/workflows/github-remote-mcp-auth-test.lock.yml b/.github/workflows/github-remote-mcp-auth-test.lock.yml index e68e97abe7..66e01c53b9 100644 --- a/.github/workflows/github-remote-mcp-auth-test.lock.yml +++ b/.github/workflows/github-remote-mcp-auth-test.lock.yml @@ -396,6 +396,11 @@ jobs: "X-MCP-Readonly": "true", "X-MCP-Toolsets": "repos,issues,discussions" }, + "tools": [ + "get_repository", + "list_issues", + "issue_read" + ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}" } diff --git a/pkg/cli/upgrade_command.go b/pkg/cli/upgrade_command.go index 9fe27358b0..bfb09f08e2 100644 --- a/pkg/cli/upgrade_command.go +++ b/pkg/cli/upgrade_command.go @@ -18,11 +18,12 @@ type UpgradeConfig struct { WorkflowDir string NoFix bool Push bool + NoActions bool } // RunUpgrade runs the upgrade command with the given configuration func RunUpgrade(config UpgradeConfig) error { - return runUpgradeCommand(config.Verbose, config.WorkflowDir, config.NoFix, false, config.Push) + return runUpgradeCommand(config.Verbose, config.WorkflowDir, config.NoFix, false, config.Push, config.NoActions) } // NewUpgradeCommand creates the upgrade command @@ -35,7 +36,8 @@ func NewUpgradeCommand() *cobra.Command { This command: 1. Updates all agent and prompt files to the latest templates (like 'init' command) 2. Applies automatic codemods to fix deprecated fields in all workflows (like 'fix --write') - 3. Compiles all workflows to generate lock files (like 'compile' command) + 3. Updates GitHub Actions versions in .github/aw/actions-lock.json (unless --no-actions is set) + 4. Compiles all workflows to generate lock files (like 'compile' command) The upgrade process ensures: - GitHub Copilot instructions are up-to-date (.github/aw/github-agentic-workflows.md) @@ -43,13 +45,15 @@ The upgrade process ensures: - All workflow prompts are updated (create, update, debug, upgrade) - All workflows use the latest syntax and configuration options - Deprecated fields are automatically migrated across all workflows +- GitHub Actions are pinned to the latest versions - All workflows are compiled and lock files are up-to-date This command always upgrades all Markdown files in .github/workflows. Examples: ` + string(constants.CLIExtensionPrefix) + ` upgrade # Upgrade all workflows - ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-fix # Update agent files only (skip codemods and compilation) + ` + 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 --push # Upgrade and automatically commit/push changes ` + string(constants.CLIExtensionPrefix) + ` upgrade --dir custom/workflows # Upgrade workflows in custom directory`, Args: cobra.NoArgs, @@ -58,13 +62,15 @@ Examples: dir, _ := cmd.Flags().GetString("dir") noFix, _ := cmd.Flags().GetBool("no-fix") push, _ := cmd.Flags().GetBool("push") + noActions, _ := cmd.Flags().GetBool("no-actions") - return runUpgradeCommand(verbose, dir, noFix, false, push) + return runUpgradeCommand(verbose, dir, noFix, false, push, noActions) }, } cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") - cmd.Flags().Bool("no-fix", false, "Skip applying codemods and compiling workflows (only update agent files)") + cmd.Flags().Bool("no-fix", false, "Skip applying codemods, action updates, and compiling workflows (only update agent files)") + cmd.Flags().Bool("no-actions", false, "Skip updating GitHub Actions versions") cmd.Flags().Bool("push", false, "Automatically commit and push changes after successful upgrade") // Register completions @@ -74,9 +80,9 @@ Examples: } // runUpgradeCommand executes the upgrade process -func runUpgradeCommand(verbose bool, workflowDir string, noFix bool, noCompile bool, push bool) error { - upgradeLog.Printf("Running upgrade command: verbose=%v, workflowDir=%s, noFix=%v, noCompile=%v, push=%v", - verbose, workflowDir, noFix, noCompile, push) +func runUpgradeCommand(verbose bool, workflowDir string, noFix bool, noCompile bool, push bool, noActions bool) error { + upgradeLog.Printf("Running upgrade command: verbose=%v, workflowDir=%s, noFix=%v, noCompile=%v, push=%v, noActions=%v", + verbose, workflowDir, noFix, noCompile, push, noActions) // Step 0a: If --push is enabled, ensure git status is clean before starting if push { @@ -135,7 +141,33 @@ func runUpgradeCommand(verbose bool, workflowDir string, noFix bool, noCompile b } } - // Step 3: Compile all workflows (unless --no-fix is specified) + // Step 3: Update GitHub Actions versions (unless --no-fix or --no-actions is specified) + if !noFix && !noActions { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Updating GitHub Actions versions...")) + upgradeLog.Print("Updating GitHub Actions versions") + + if err := UpdateActions(false, verbose); err != nil { + upgradeLog.Printf("Failed to update actions: %v", err) + // Don't fail the upgrade if action updates fail - this is non-critical + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: Failed to update actions: %v", err))) + } else if verbose { + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✓ Updated GitHub Actions versions")) + } + } else { + if noFix { + upgradeLog.Print("Skipping action updates (--no-fix specified)") + if verbose { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping action updates (--no-fix specified)")) + } + } else if noActions { + upgradeLog.Print("Skipping action updates (--no-actions specified)") + if verbose { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping action updates (--no-actions specified)")) + } + } + } + + // Step 4: Compile all workflows (unless --no-fix is specified) if !noFix { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Compiling all workflows...")) upgradeLog.Print("Compiling all workflows") @@ -178,7 +210,7 @@ func runUpgradeCommand(verbose bool, workflowDir string, noFix bool, noCompile b fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✓ Upgrade complete")) - // Step 4: If --push is enabled, commit and push changes + // Step 5: If --push is enabled, commit and push changes if push { upgradeLog.Print("Push enabled - preparing to commit and push changes") fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/upgrade_command_test.go b/pkg/cli/upgrade_command_test.go index 7046657472..bbbc1b3032 100644 --- a/pkg/cli/upgrade_command_test.go +++ b/pkg/cli/upgrade_command_test.go @@ -51,6 +51,7 @@ This is a test workflow. NoFix: true, // Skip codemods for this test WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -127,6 +128,7 @@ This workflow also has deprecated timeout_minutes field. NoFix: false, // Apply codemods WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -188,6 +190,7 @@ This workflow should not be modified when --no-fix is used. NoFix: true, // Skip codemods WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -219,6 +222,7 @@ func TestUpgradeCommand_NonGitRepo(t *testing.T) { NoFix: true, WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err := RunUpgrade(config) @@ -267,6 +271,7 @@ This is a test workflow that should be compiled during upgrade. NoFix: false, // Apply codemods and compile WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -323,6 +328,7 @@ This workflow should not be compiled with --no-fix. NoFix: true, // Skip codemods and compilation WorkflowDir: "", Push: false, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -382,6 +388,7 @@ This is a test workflow. NoFix: true, WorkflowDir: "", Push: true, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -439,6 +446,7 @@ This workflow is already up to date. NoFix: true, // Skip codemods to avoid changes WorkflowDir: "", Push: true, + NoActions: true, // Skip action updates for this test } err = RunUpgrade(config) @@ -446,3 +454,153 @@ This workflow is already up to date. require.Error(t, err, "Upgrade with --push should fail when no remote is configured") assert.Contains(t, err.Error(), "--push requires a remote repository to be configured") } + +func TestUpgradeCommand_UpdatesActionPins(t *testing.T) { + // Create a temporary directory for test files + tmpDir := t.TempDir() + originalDir, _ := os.Getwd() + defer os.Chdir(originalDir) + + // Initialize git repository + os.Chdir(tmpDir) + exec.Command("git", "init").Run() + exec.Command("git", "config", "user.email", "test@example.com").Run() + exec.Command("git", "config", "user.name", "Test User").Run() + + // Create .github/workflows directory + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + err := os.MkdirAll(workflowsDir, 0755) + require.NoError(t, err, "Failed to create workflows directory") + + // Create .github/aw directory + awDir := filepath.Join(tmpDir, ".github", "aw") + err = os.MkdirAll(awDir, 0755) + require.NoError(t, err, "Failed to create .github/aw directory") + + // Create a simple workflow + workflowFile := filepath.Join(workflowsDir, "test-workflow.md") + content := `--- +on: + workflow_dispatch: + +permissions: + contents: read +--- + +# Test Workflow + +This workflow should trigger action pin updates. +` + err = os.WriteFile(workflowFile, []byte(content), 0644) + require.NoError(t, err, "Failed to create test workflow file") + + // Create an actions-lock.json file with a test entry + actionsLockPath := filepath.Join(awDir, "actions-lock.json") + actionsLockContent := `{ + "entries": { + "actions/checkout@v4": { + "repo": "actions/checkout", + "version": "v4", + "sha": "b4ffde65f46336ab88eb53be808477a3936bae11" + } + } +} +` + err = os.WriteFile(actionsLockPath, []byte(actionsLockContent), 0644) + require.NoError(t, err, "Failed to create actions-lock.json file") + + // Run upgrade command (should update actions) + config := UpgradeConfig{ + Verbose: false, + NoFix: false, // Apply codemods and compile + WorkflowDir: "", + Push: false, + NoActions: false, // Enable action updates + } + + err = RunUpgrade(config) + require.NoError(t, err, "Upgrade command should succeed") + + // Verify that actions-lock.json still exists (it may or may not be updated depending on GitHub API availability) + // We just verify the upgrade doesn't break when action updates are enabled + _, statErr := os.Stat(actionsLockPath) + // Either file still exists or was removed (both are acceptable outcomes) + // Just verify no panic or crash occurred + assert.Condition(t, func() bool { + return statErr == nil || os.IsNotExist(statErr) + }, "Actions lock file should exist or be removed cleanly") +} + +func TestUpgradeCommand_NoActionsFlag(t *testing.T) { + // Create a temporary directory for test files + tmpDir := t.TempDir() + originalDir, _ := os.Getwd() + defer os.Chdir(originalDir) + + // Initialize git repository + os.Chdir(tmpDir) + exec.Command("git", "init").Run() + exec.Command("git", "config", "user.email", "test@example.com").Run() + exec.Command("git", "config", "user.name", "Test User").Run() + + // Create .github/workflows directory + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + err := os.MkdirAll(workflowsDir, 0755) + require.NoError(t, err, "Failed to create workflows directory") + + // Create .github/aw directory + awDir := filepath.Join(tmpDir, ".github", "aw") + err = os.MkdirAll(awDir, 0755) + require.NoError(t, err, "Failed to create .github/aw directory") + + // Create a simple workflow + workflowFile := filepath.Join(workflowsDir, "test-workflow.md") + content := `--- +on: + workflow_dispatch: + +permissions: + contents: read +--- + +# Test Workflow + +This workflow should not trigger action pin updates with --no-actions. +` + err = os.WriteFile(workflowFile, []byte(content), 0644) + require.NoError(t, err, "Failed to create test workflow file") + + // Create an actions-lock.json file with a test entry + actionsLockPath := filepath.Join(awDir, "actions-lock.json") + originalContent := `{ + "entries": { + "actions/checkout@v4": { + "repo": "actions/checkout", + "version": "v4", + "sha": "b4ffde65f46336ab88eb53be808477a3936bae11" + } + } +} +` + err = os.WriteFile(actionsLockPath, []byte(originalContent), 0644) + require.NoError(t, err, "Failed to create actions-lock.json file") + + // Run upgrade command with --no-actions + config := UpgradeConfig{ + Verbose: false, + NoFix: false, // Apply codemods and compile + WorkflowDir: "", + Push: false, + NoActions: true, // Skip action updates + } + + err = RunUpgrade(config) + require.NoError(t, err, "Upgrade command should succeed") + + // Verify that actions-lock.json was not modified + updatedContent, err := os.ReadFile(actionsLockPath) + require.NoError(t, err, "Failed to read actions-lock.json") + + // Content should be unchanged (actions should not be updated) + assert.Equal(t, originalContent, string(updatedContent), "Actions lock file should not be modified with --no-actions") +}