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
5 changes: 5 additions & 0 deletions .github/workflows/github-remote-mcp-auth-test.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 42 additions & 10 deletions pkg/cli/upgrade_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,21 +36,24 @@ 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)
- Dispatcher agent is current (.github/agents/agentic-workflows.agent.md)
- 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,
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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, "")
Expand Down
158 changes: 158 additions & 0 deletions pkg/cli/upgrade_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -439,10 +446,161 @@ 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)
// Should fail because no remote is configured
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")
}
Loading