diff --git a/pkg/cli/upgrade_command.go b/pkg/cli/upgrade_command.go index 867aeff49b4..175f03849fc 100644 --- a/pkg/cli/upgrade_command.go +++ b/pkg/cli/upgrade_command.go @@ -34,6 +34,7 @@ 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) The upgrade process ensures: - GitHub Copilot instructions are up-to-date (.github/aw/github-agentic-workflows.md) @@ -41,16 +42,14 @@ 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 +- 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) - ` + string(constants.CLIExtensionPrefix) + ` upgrade --dir custom/workflows # Upgrade workflows in custom directory - -After upgrading, compile workflows manually with: - ` + string(constants.CLIExtensionPrefix) + ` compile`, + ` + string(constants.CLIExtensionPrefix) + ` upgrade --no-fix # Update agent files only (skip codemods and compilation) + ` + string(constants.CLIExtensionPrefix) + ` upgrade --dir custom/workflows # Upgrade workflows in custom directory`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { verbose, _ := cmd.Flags().GetBool("verbose") @@ -62,7 +61,7 @@ After upgrading, compile workflows manually with: } cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") - cmd.Flags().Bool("no-fix", false, "Skip applying codemods to workflows (only update agent files)") + cmd.Flags().Bool("no-fix", false, "Skip applying codemods and compiling workflows (only update agent files)") // Register completions RegisterDirFlagCompletion(cmd, "dir") @@ -112,12 +111,43 @@ func runUpgradeCommand(verbose bool, workflowDir string, noFix bool, noCompile b } } - // Step 3: Compile workflows (only if explicitly requested - compilation is optional) - // Skipping compilation by default avoids issues when workflows might have other errors - if !noCompile && !noFix { - upgradeLog.Print("Skipping compilation (can be enabled with future --compile flag)") - // Note: We skip compilation by default to avoid crashing on workflows that might have - // validation errors. Users can compile manually after upgrade with: gh aw compile + // Step 3: Compile all workflows (unless --no-fix is specified) + if !noFix { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Compiling all workflows...")) + upgradeLog.Print("Compiling all workflows") + + // Create and configure compiler + compiler := createAndConfigureCompiler(CompileConfig{ + Verbose: verbose, + WorkflowDir: workflowDir, + }) + + // Determine workflow directory + workflowsDir := workflowDir + if workflowsDir == "" { + workflowsDir = ".github/workflows" + } + + // Compile all workflow files + stats, compileErr := compileAllWorkflowFiles(compiler, workflowsDir, verbose) + if compileErr != nil { + upgradeLog.Printf("Failed to compile workflows: %v", compileErr) + // Don't fail the upgrade if compilation fails - this is non-critical + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: Failed to compile workflows: %v", compileErr))) + } else if stats != nil { + // Print compilation summary + if verbose { + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("✓ Compiled %d workflow(s)", stats.Total-stats.Errors))) + } + if stats.Errors > 0 { + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: %d workflow(s) failed to compile", stats.Errors))) + } + } + } else { + upgradeLog.Print("Skipping compilation (--no-fix specified)") + if verbose { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping compilation (--no-fix specified)")) + } } // Print success message diff --git a/pkg/cli/upgrade_command_test.go b/pkg/cli/upgrade_command_test.go index 98e7109d627..64be8ef6926 100644 --- a/pkg/cli/upgrade_command_test.go +++ b/pkg/cli/upgrade_command_test.go @@ -222,3 +222,107 @@ func TestUpgradeCommand_NonGitRepo(t *testing.T) { require.Error(t, err, "Upgrade should fail in non-git repository") assert.Contains(t, strings.ToLower(err.Error()), "git", "Error message should mention git") } + +func TestUpgradeCommand_CompilesWorkflows(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 a simple workflow that should compile successfully + workflowFile := filepath.Join(workflowsDir, "test-workflow.md") + content := `--- +on: + workflow_dispatch: + +permissions: + contents: read +--- + +# Test Workflow + +This is a test workflow that should be compiled during upgrade. +` + err = os.WriteFile(workflowFile, []byte(content), 0644) + require.NoError(t, err, "Failed to create test workflow file") + + // Run upgrade command (should compile workflows) + config := UpgradeConfig{ + Verbose: false, + NoFix: false, // Apply codemods and compile + WorkflowDir: "", + } + + err = RunUpgrade(config) + require.NoError(t, err, "Upgrade command should succeed") + + // Verify that the lock file was created + lockFile := filepath.Join(workflowsDir, "test-workflow.lock.yml") + assert.FileExists(t, lockFile, "Lock file should be created after upgrade") + + // Read lock file content and verify it's valid YAML + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Failed to read lock file") + assert.NotEmpty(t, lockContent, "Lock file should not be empty") + assert.Contains(t, string(lockContent), "name:", "Lock file should contain workflow name") +} + +func TestUpgradeCommand_NoFixSkipsCompilation(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 a simple workflow + workflowFile := filepath.Join(workflowsDir, "test-workflow.md") + content := `--- +on: + workflow_dispatch: + +permissions: + contents: read +--- + +# Test Workflow + +This workflow should not be compiled with --no-fix. +` + err = os.WriteFile(workflowFile, []byte(content), 0644) + require.NoError(t, err, "Failed to create test workflow file") + + // Run upgrade command with --no-fix + config := UpgradeConfig{ + Verbose: false, + NoFix: true, // Skip codemods and compilation + WorkflowDir: "", + } + + err = RunUpgrade(config) + require.NoError(t, err, "Upgrade command should succeed") + + // Verify that the lock file was NOT created + lockFile := filepath.Join(workflowsDir, "test-workflow.lock.yml") + assert.NoFileExists(t, lockFile, "Lock file should not be created with --no-fix") +}