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
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Testing update command to ensure it runs without issues..."
# Run update with verbose flag to check for updates without making changes
# The command checks for gh-aw updates, action updates, and workflow updates
./gh-aw update --verbose --no-actions
echo "Testing update command to ensure it runs successfully..."
# Run update with verbose flag to check for and apply workflow updates from source repositories
# The command may modify workflow files if upstream updates are available
./gh-aw update --verbose
echo "✅ Update command executed successfully" >> $GITHUB_STEP_SUMMARY

build:
Expand Down Expand Up @@ -1143,7 +1143,7 @@ jobs:
run: |
echo "## Dependency Health Audit" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
./gh-aw update --audit 2>&1 | tee audit_output.txt || true
./gh-aw upgrade --audit 2>&1 | tee audit_output.txt || true
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
head -100 audit_output.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
Expand All @@ -1152,7 +1152,7 @@ jobs:
id: audit_json
run: |
# Run audit with JSON output for agent-friendly parsing
./gh-aw update --audit --json > audit.json 2>&1
./gh-aw upgrade --audit --json > audit.json 2>&1

# Display summary in GitHub Actions
echo "✅ Dependency audit completed" >> $GITHUB_STEP_SUMMARY
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/guides/packaging-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ gh aw update ci-doctor # update specific workflow
gh aw update ci-doctor issue-triage # update multiple
```

Use `--major`, `--force`, `--engine`, or `--verbose` flags to control update behavior. Semantic versions (e.g., `v1.2.3`) update to latest compatible release within same major version. Branch references update to latest commit. Updates use 3-way merge; when conflicts occur, manually resolve conflict markers and run `gh aw compile`.
Use `--major`, `--force`, `--no-merge`, `--engine`, or `--verbose` flags to control update behavior. Semantic versions (e.g., `v1.2.3`) update to latest compatible release within same major version. Branch references update to latest commit. SHA references update to the latest commit on the default branch. Updates use 3-way merge by default to preserve local changes; use `--no-merge` to replace with the upstream version. When merge conflicts occur, manually resolve conflict markers and run `gh aw compile`.

## Imports

Expand Down
11 changes: 7 additions & 4 deletions docs/src/content/docs/setup/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,15 +385,16 @@ gh aw remove my-workflow

#### `update`

Update workflows based on `source` field (`owner/repo/path@ref`). Default replaces local file; `--merge` performs 3-way merge. Semantic versions update within same major version.
Update workflows based on `source` field (`owner/repo/path@ref`). By default, performs a 3-way merge to preserve local changes; use `--no-merge` to override with upstream. Semantic versions update within same major version.

```bash wrap
gh aw update # Update all with source field
gh aw update ci-doctor --merge # Update with 3-way merge
gh aw update ci-doctor # Update specific workflow (3-way merge)
gh aw update ci-doctor --no-merge # Override local changes with upstream
gh aw update ci-doctor --major --force # Allow major version updates
```

**Options:** `--dir`, `--merge`, `--major`, `--force`
**Options:** `--dir`, `--no-merge`, `--major`, `--force`, `--engine`, `--no-stop-after`, `--stop-after`

#### `upgrade`

Expand All @@ -404,9 +405,11 @@ gh aw upgrade # Upgrade repository agent files and
gh aw upgrade --no-fix # Update agent files only (skip codemods)
gh aw upgrade --push # Upgrade and automatically commit/push
gh aw upgrade --push --no-fix # Update agent files and push
gh aw upgrade --audit # Run dependency health audit
gh aw upgrade --audit --json # Dependency audit in JSON format
```

**Options:** `--dir`, `--no-fix`, `--push` (see [--push flag](#the---push-flag))
**Options:** `--dir`, `--no-fix`, `--no-actions`, `--push` (see [--push flag](#the---push-flag)), `--audit`, `--json`

### Advanced

Expand Down
148 changes: 25 additions & 123 deletions pkg/cli/update_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package cli

import (
"fmt"
"os"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/spf13/cobra"
Expand All @@ -16,97 +14,59 @@ var updateLog = logger.New("cli:update_command")
func NewUpdateCommand(validateEngine func(string) error) *cobra.Command {
cmd := &cobra.Command{
Use: "update [workflow]...",
Short: "Update agentic workflows from their source repositories and check for gh aw updates",
Long: `Update one or more workflows from their source repositories and check for gh aw updates.
Short: "Update agentic workflows from their source repositories",
Long: `Update one or more workflows from their source repositories.

The command:
1. Checks if a newer version of gh aw is available
2. Updates GitHub Actions versions in .github/aw/actions-lock.json (unless --no-actions is set)
3. Updates workflows using the 'source' field in the workflow frontmatter
4. Compiles each workflow immediately after update
5. Applies automatic fixes (codemods) to ensure workflows follow latest best practices
The update command fetches the latest version of each workflow from its source
repository, merges upstream changes with any local modifications, and recompiles.

By default, the update command replaces local workflow files with the latest version from the source
repository, overriding any local changes. Use the --merge flag to preserve local changes by performing
a 3-way merge between the base version, your local changes, and the latest upstream version.
If no workflow names are specified, all workflows with a 'source' field are updated.

By default, the update performs a 3-way merge to preserve your local changes.
Use --no-merge to override local changes with the upstream version.

For workflow updates, it fetches the latest version based on the current ref:
- If the ref is a tag, it updates to the latest release (use --major for major version updates)
- If the ref is a branch, it fetches the latest commit from that branch
- Otherwise, it fetches the latest commit from the default branch

For action updates, it checks each action in .github/aw/actions-lock.json for newer releases
and updates the SHA to pin to the latest version. Use --no-actions to skip action updates.

DEPENDENCY HEALTH AUDIT:
Use --audit to check dependency health without performing updates. This includes:
- Outdated Go dependencies with available updates
- Security advisories from GitHub Security Advisory API
- Dependency maturity analysis (v0.x vs stable versions)
- Comprehensive dependency health report
- If the ref is a commit SHA, it fetches the latest commit from the default branch

The --audit flag implies --dry-run (no updates performed).
For extension updates, action updates, agent files, and codemods, use 'gh aw upgrade'.

` + WorkflowIDExplanation + `

Examples:
` + string(constants.CLIExtensionPrefix) + ` update # Check gh aw updates, update actions, and update all workflows
` + string(constants.CLIExtensionPrefix) + ` update ci-doctor # Check gh aw updates, update actions, and update specific workflow
` + string(constants.CLIExtensionPrefix) + ` update --no-actions # Skip action updates, only update workflows
` + string(constants.CLIExtensionPrefix) + ` update ci-doctor.md # Check gh aw updates, update actions, and update specific workflow (alternative format)
` + string(constants.CLIExtensionPrefix) + ` update ci-doctor --major # Allow major version updates
` + string(constants.CLIExtensionPrefix) + ` update --merge # Update with 3-way merge to preserve local changes
` + string(constants.CLIExtensionPrefix) + ` update --pr # Create PR with changes
` + string(constants.CLIExtensionPrefix) + ` update --force # Force update even if no changes
` + string(constants.CLIExtensionPrefix) + ` update --dir custom/workflows # Update workflows in custom directory
` + string(constants.CLIExtensionPrefix) + ` update --audit # Check dependency health without updating
` + string(constants.CLIExtensionPrefix) + ` update --dry-run # Show what would be updated without making changes`,
` + string(constants.CLIExtensionPrefix) + ` update # Update all workflows from source
` + string(constants.CLIExtensionPrefix) + ` update repo-assist # Update a specific workflow
` + string(constants.CLIExtensionPrefix) + ` update repo-assist.md # Same (alternative format)
` + string(constants.CLIExtensionPrefix) + ` update --no-merge # Override local changes with upstream
` + string(constants.CLIExtensionPrefix) + ` update repo-assist --major # Allow major version updates
` + string(constants.CLIExtensionPrefix) + ` update --force # Force update even if no changes
` + string(constants.CLIExtensionPrefix) + ` update --dir custom/workflows # Update workflows in custom directory`,
RunE: func(cmd *cobra.Command, args []string) error {
majorFlag, _ := cmd.Flags().GetBool("major")
forceFlag, _ := cmd.Flags().GetBool("force")
engineOverride, _ := cmd.Flags().GetString("engine")
verbose, _ := cmd.Flags().GetBool("verbose")
prFlag, _ := cmd.Flags().GetBool("pr")
workflowDir, _ := cmd.Flags().GetString("dir")
noStopAfter, _ := cmd.Flags().GetBool("no-stop-after")
stopAfter, _ := cmd.Flags().GetString("stop-after")
mergeFlag, _ := cmd.Flags().GetBool("merge")
noActions, _ := cmd.Flags().GetBool("no-actions")
auditFlag, _ := cmd.Flags().GetBool("audit")
dryRunFlag, _ := cmd.Flags().GetBool("dry-run")
jsonOutput, _ := cmd.Flags().GetBool("json")
noMergeFlag, _ := cmd.Flags().GetBool("no-merge")

if err := validateEngine(engineOverride); err != nil {
return err
}

// Handle audit mode
if auditFlag {
return runDependencyAudit(verbose, jsonOutput)
}

// Handle dry-run mode
if dryRunFlag {
// TODO: Implement dry-run mode for workflow updates
return fmt.Errorf("--dry-run mode not yet implemented for workflow updates")
}

return UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag, workflowDir, noStopAfter, stopAfter, mergeFlag, noActions)
return RunUpdateWorkflows(args, majorFlag, forceFlag, verbose, engineOverride, workflowDir, noStopAfter, stopAfter, noMergeFlag)
},
}

cmd.Flags().Bool("major", false, "Allow major version updates when updating tagged releases")
cmd.Flags().BoolP("force", "f", false, "Force update even if no changes are detected")
addEngineFlag(cmd)
cmd.Flags().Bool("pr", false, "Create a pull request with the workflow changes")
cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)")
cmd.Flags().Bool("no-stop-after", false, "Remove any stop-after field from the workflow")
cmd.Flags().String("stop-after", "", "Override stop-after value in the workflow (e.g., '+48h', '2025-12-31 23:59:59')")
cmd.Flags().Bool("merge", false, "Merge local changes with upstream updates instead of overriding")
cmd.Flags().Bool("no-actions", false, "Skip updating GitHub Actions versions")
cmd.Flags().Bool("audit", false, "Check dependency health without performing updates (implies --dry-run)")
cmd.Flags().Bool("dry-run", false, "Show what would be updated without making changes")
cmd.Flags().BoolP("json", "j", false, "Output audit results in JSON format (only with --audit)")
cmd.Flags().Bool("no-merge", false, "Override local changes with upstream version instead of merging")

// Register completions for update command
cmd.ValidArgsFunction = CompleteWorkflowNames
Expand All @@ -116,72 +76,14 @@ Examples:
return cmd
}

// runDependencyAudit performs a dependency health audit
func runDependencyAudit(verbose bool, jsonOutput bool) error {
updateLog.Print("Running dependency health audit")

// Generate comprehensive report
report, err := GenerateDependencyReport(verbose)
if err != nil {
return fmt.Errorf("failed to generate dependency report: %w", err)
}

// Display the report
if jsonOutput {
return DisplayDependencyReportJSON(report)
}
DisplayDependencyReport(report)
// RunUpdateWorkflows updates workflows from their source repositories.
// Each workflow is compiled immediately after update.
func RunUpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, workflowsDir string, noStopAfter bool, stopAfter string, noMerge bool) error {
updateLog.Printf("Starting update process: workflows=%v, allowMajor=%v, force=%v, noMerge=%v", workflowNames, allowMajor, force, noMerge)

return nil
}

// UpdateWorkflowsWithExtensionCheck performs the complete update process:
// 1. Check for gh-aw extension updates
// 2. Update GitHub Actions versions (unless --no-actions flag is set)
// 3. Update workflows from source repositories (compiles each workflow after update)
// 4. Apply automatic fixes to updated workflows
// 5. Optionally create a PR
func UpdateWorkflowsWithExtensionCheck(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, createPR bool, workflowsDir string, noStopAfter bool, stopAfter string, merge bool, noActions bool) error {
updateLog.Printf("Starting update process: workflows=%v, allowMajor=%v, force=%v, createPR=%v, merge=%v, noActions=%v", workflowNames, allowMajor, force, createPR, merge, noActions)

// Step 1: Check for gh-aw extension updates
if err := checkExtensionUpdate(verbose); err != nil {
return fmt.Errorf("extension update check failed: %w", err)
}

// Step 2: Update GitHub Actions versions (unless disabled)
if !noActions {
if err := UpdateActions(allowMajor, verbose); err != nil {
return fmt.Errorf("action update failed: %w", err)
}
}

// Step 3: Update workflows from source repositories
// Note: Each workflow is compiled immediately after update
if err := UpdateWorkflows(workflowNames, allowMajor, force, verbose, engineOverride, workflowsDir, noStopAfter, stopAfter, merge); err != nil {
if err := UpdateWorkflows(workflowNames, allowMajor, force, verbose, engineOverride, workflowsDir, noStopAfter, stopAfter, noMerge); err != nil {
return fmt.Errorf("workflow update failed: %w", err)
}

// Step 4: Apply automatic fixes to updated workflows
fixConfig := FixConfig{
WorkflowIDs: workflowNames,
Write: true,
Verbose: verbose,
}
if err := RunFix(fixConfig); err != nil {
updateLog.Printf("Fix command failed (non-fatal): %v", err)
// Don't fail the update if fix fails - this is non-critical
if verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warning: automatic fixes failed: %v", err)))
}
}

// Step 5: Optionally create PR if flag is set
if createPR {
if err := createUpdatePR(verbose); err != nil {
return fmt.Errorf("failed to create PR: %w", err)
}
}

return nil
}
Loading
Loading