diff --git a/cmd/actions.go b/cmd/actions.go index 0b58455..172831c 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -87,18 +87,34 @@ var actionsCmd = &cobra.Command{ Long: "View and manage workflows, runs, secrets, and variables for Forgejo Actions in your repositories.", } -// Run commands (compatible with gh run) -var runCmd = &cobra.Command{ - Use: "run", - Short: "View and manage workflow runs", - Long: "List, view, and manage workflow runs.", +// Run and Workflow command trees are built via factory functions +// (newRunCmd / newWorkflowCmd) so cmd/aliases.go can build an identical +// top-level tree under rootCmd without duplicating Use/Short/Long/Example/ +// flag declarations. Single source of truth — drift impossible. + +// newRunCmd builds the `run` subtree. parentLabel is interpolated into the +// parent's Short/Long so the alias-tree variant can advertise itself as +// "alias for 'actions run'" without diverging on the children. +func newRunCmd(parentLabel string) *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "View and manage workflow runs" + parentLabel, + Long: "List, view, and manage workflow runs." + parentLabel, + } + cmd.AddCommand(newRunListCmd()) + cmd.AddCommand(newRunViewCmd()) + cmd.AddCommand(newRunWatchCmd()) + cmd.AddCommand(newRunRerunCmd()) + cmd.AddCommand(newRunCancelCmd()) + return cmd } -var runListCmd = &cobra.Command{ - Use: "list", - Short: "List recent workflow runs", - Long: "List recent workflow runs for a repository.", - Example: ` # List recent workflow runs +func newRunListCmd() *cobra.Command { + c := &cobra.Command{ + Use: "list", + Short: "List recent workflow runs", + Long: "List recent workflow runs for a repository.", + Example: ` # List recent workflow runs fj actions run list # List runs with a custom limit @@ -106,14 +122,20 @@ var runListCmd = &cobra.Command{ # Output as JSON fj actions run list --json`, - RunE: runRunList, + RunE: runRunList, + } + addRepoFlags(c) + c.Flags().IntP("limit", "L", 20, "Maximum number of runs to list") + addJSONFlags(c, "Output workflow runs as JSON") + return c } -var runViewCmd = &cobra.Command{ - Use: "view ", - Short: "View a workflow run", - Long: "View details about a specific workflow run.", - Example: ` # View a workflow run +func newRunViewCmd() *cobra.Command { + c := &cobra.Command{ + Use: "view ", + Short: "View a workflow run", + Long: "View details about a specific workflow run.", + Example: ` # View a workflow run fj actions run view 123 # View with job details @@ -124,55 +146,86 @@ var runViewCmd = &cobra.Command{ # View only failed logs fj actions run view 123 --log-failed`, - Args: cobra.ExactArgs(1), - RunE: runRunView, + Args: cobra.ExactArgs(1), + RunE: runRunView, + } + addRepoFlags(c) + c.Flags().BoolP("verbose", "v", false, "Show job steps") + c.Flags().BoolP("log", "", false, "View full log for either a run or specific job") + c.Flags().StringP("job", "j", "", "View a specific job ID from a run") + c.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job") + addJSONFlags(c, "Output workflow run as JSON") + return c } -var runWatchCmd = &cobra.Command{ - Use: "watch ", - Short: "Watch a workflow run", - Long: "Poll a workflow run until it completes.", - Example: ` # Watch a run until it completes +func newRunWatchCmd() *cobra.Command { + c := &cobra.Command{ + Use: "watch ", + Short: "Watch a workflow run", + Long: "Poll a workflow run until it completes.", + Example: ` # Watch a run until it completes fj actions run watch 123 # Watch with a custom polling interval fj actions run watch 123 -i 10s`, - Args: cobra.ExactArgs(1), - RunE: runRunWatch, + Args: cobra.ExactArgs(1), + RunE: runRunWatch, + } + addRepoFlags(c) + c.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval") + return c } -var runRerunCmd = &cobra.Command{ - Use: "rerun ", - Short: "Rerun a workflow run", - Long: "Trigger a rerun for a specific workflow run.", - Example: ` # Rerun a failed workflow run +func newRunRerunCmd() *cobra.Command { + c := &cobra.Command{ + Use: "rerun ", + Short: "Rerun a workflow run", + Long: "Trigger a rerun for a specific workflow run.", + Example: ` # Rerun a failed workflow run fj actions run rerun 123`, - Args: cobra.ExactArgs(1), - RunE: runRunRerun, + Args: cobra.ExactArgs(1), + RunE: runRunRerun, + } + addRepoFlags(c) + return c } -var runCancelCmd = &cobra.Command{ - Use: "cancel ", - Short: "Cancel a workflow run", - Long: "Cancel a running workflow run.", - Example: ` # Cancel a running workflow +func newRunCancelCmd() *cobra.Command { + c := &cobra.Command{ + Use: "cancel ", + Short: "Cancel a workflow run", + Long: "Cancel a running workflow run.", + Example: ` # Cancel a running workflow fj actions run cancel 123`, - Args: cobra.ExactArgs(1), - RunE: runRunCancel, + Args: cobra.ExactArgs(1), + RunE: runRunCancel, + } + addRepoFlags(c) + return c } -// Workflow commands -var workflowCmd = &cobra.Command{ - Use: "workflow", - Short: "Manage workflows", - Long: "List, view, and run workflows.", +// newWorkflowCmd builds the `workflow` subtree. parentLabel is interpolated +// the same way as newRunCmd's, so the alias variant can self-identify. +func newWorkflowCmd(parentLabel string) *cobra.Command { + cmd := &cobra.Command{ + Use: "workflow", + Short: "Manage workflows" + parentLabel, + Long: "List, view, and run workflows." + parentLabel, + } + cmd.AddCommand(newWorkflowListCmd()) + cmd.AddCommand(newWorkflowViewCmd()) + cmd.AddCommand(newWorkflowRunCmd()) + cmd.AddCommand(newWorkflowEnableCmd()) + cmd.AddCommand(newWorkflowDisableCmd()) + return cmd } -var workflowListCmd = &cobra.Command{ - Use: "list", - Short: "List workflows", - Long: "List all workflows in a repository.", - Example: ` # List all workflows +func newWorkflowListCmd() *cobra.Command { + c := &cobra.Command{ + Use: "list", + Short: "List workflows", + Long: "List all workflows in a repository.", + Example: ` # List all workflows fj actions workflow list # List workflows as JSON @@ -180,53 +233,78 @@ var workflowListCmd = &cobra.Command{ # List workflows for a specific repo fj actions workflow list -R owner/repo`, - RunE: runWorkflowList, + RunE: runWorkflowList, + } + addRepoFlags(c) + c.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list") + addJSONFlags(c, "Output workflows as JSON") + return c } -var workflowViewCmd = &cobra.Command{ - Use: "view ", - Short: "View a workflow", - Long: "View details about a specific workflow. You can specify the workflow by name or filename.", - Example: ` # View a workflow by filename +func newWorkflowViewCmd() *cobra.Command { + c := &cobra.Command{ + Use: "view ", + Short: "View a workflow", + Long: "View details about a specific workflow. You can specify the workflow by name or filename.", + Example: ` # View a workflow by filename fj actions workflow view ci.yml # View as JSON fj actions workflow view ci.yml --json`, - Args: cobra.ExactArgs(1), - RunE: runWorkflowView, + Args: cobra.ExactArgs(1), + RunE: runWorkflowView, + } + addRepoFlags(c) + addJSONFlags(c, "Output workflow as JSON") + return c } -var workflowRunCmd = &cobra.Command{ - Use: "run ", - Short: "Run a workflow", - Long: "Trigger a workflow_dispatch event for a workflow. The workflow must support the workflow_dispatch trigger.", - Example: ` # Trigger a workflow on the default branch +func newWorkflowRunCmd() *cobra.Command { + c := &cobra.Command{ + Use: "run ", + Short: "Run a workflow", + Long: "Trigger a workflow_dispatch event for a workflow. The workflow must support the workflow_dispatch trigger.", + Example: ` # Trigger a workflow on the default branch fj actions workflow run deploy.yml # Trigger on a specific branch with input parameters fj actions workflow run deploy.yml -r staging -f environment=staging -f version=1.2.3`, - Args: cobra.ExactArgs(1), - RunE: runWorkflowRun, + Args: cobra.ExactArgs(1), + RunE: runWorkflowRun, + } + addRepoFlags(c) + c.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)") + c.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)") + c.Flags().StringSliceP("raw-field", "F", nil, "Add a string parameter in key=value format, reading from file if value starts with @ (can be used multiple times)") + return c } -var workflowEnableCmd = &cobra.Command{ - Use: "enable ", - Short: "Enable a workflow", - Long: "Enable a workflow so it can be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to enable workflows.", - Example: ` # Enable a workflow +func newWorkflowEnableCmd() *cobra.Command { + c := &cobra.Command{ + Use: "enable ", + Short: "Enable a workflow", + Long: "Enable a workflow so it can be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to enable workflows.", + Example: ` # Enable a workflow fj actions workflow enable ci.yml`, - Args: cobra.ExactArgs(1), - RunE: runWorkflowEnable, + Args: cobra.ExactArgs(1), + RunE: runWorkflowEnable, + } + addRepoFlags(c) + return c } -var workflowDisableCmd = &cobra.Command{ - Use: "disable ", - Short: "Disable a workflow", - Long: "Disable a workflow so it cannot be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to disable workflows.", - Example: ` # Disable a workflow +func newWorkflowDisableCmd() *cobra.Command { + c := &cobra.Command{ + Use: "disable ", + Short: "Disable a workflow", + Long: "Disable a workflow so it cannot be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to disable workflows.", + Example: ` # Disable a workflow fj actions workflow disable ci.yml`, - Args: cobra.ExactArgs(1), - RunE: runWorkflowDisable, + Args: cobra.ExactArgs(1), + RunE: runWorkflowDisable, + } + addRepoFlags(c) + return c } // Secret commands @@ -336,21 +414,10 @@ var actionsVariableDeleteCmd = &cobra.Command{ func init() { rootCmd.AddCommand(actionsCmd) - // Add run commands (gh run compatible) - actionsCmd.AddCommand(runCmd) - runCmd.AddCommand(runListCmd) - runCmd.AddCommand(runViewCmd) - runCmd.AddCommand(runWatchCmd) - runCmd.AddCommand(runRerunCmd) - runCmd.AddCommand(runCancelCmd) - - // Add workflow commands (gh workflow compatible) - actionsCmd.AddCommand(workflowCmd) - workflowCmd.AddCommand(workflowListCmd) - workflowCmd.AddCommand(workflowViewCmd) - workflowCmd.AddCommand(workflowRunCmd) - workflowCmd.AddCommand(workflowEnableCmd) - workflowCmd.AddCommand(workflowDisableCmd) + // Run and Workflow trees come from the factory functions defined above + // so cmd/aliases.go can build identical top-level trees under rootCmd. + actionsCmd.AddCommand(newRunCmd("")) + actionsCmd.AddCommand(newWorkflowCmd("")) // Add secret commands actionsCmd.AddCommand(actionsSecretCmd) @@ -366,34 +433,6 @@ func init() { actionsVariableCmd.AddCommand(actionsVariableUpdateCmd) actionsVariableCmd.AddCommand(actionsVariableDeleteCmd) - // Add flags for run commands - addRepoFlags(runListCmd) - runListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list") - addJSONFlags(runListCmd, "Output workflow runs as JSON") - addRepoFlags(runViewCmd) - runViewCmd.Flags().BoolP("verbose", "v", false, "Show job steps") - runViewCmd.Flags().BoolP("log", "", false, "View full log for either a run or specific job") - runViewCmd.Flags().StringP("job", "j", "", "View a specific job ID from a run") - runViewCmd.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job") - addJSONFlags(runViewCmd, "Output workflow run as JSON") - addRepoFlags(runWatchCmd) - runWatchCmd.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval") - addRepoFlags(runRerunCmd) - addRepoFlags(runCancelCmd) - - // Add flags for workflow commands - addRepoFlags(workflowListCmd) - workflowListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list") - addJSONFlags(workflowListCmd, "Output workflows as JSON") - addRepoFlags(workflowViewCmd) - addJSONFlags(workflowViewCmd, "Output workflow as JSON") - addRepoFlags(workflowRunCmd) - addRepoFlags(workflowEnableCmd) - addRepoFlags(workflowDisableCmd) - workflowRunCmd.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)") - workflowRunCmd.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)") - workflowRunCmd.Flags().StringSliceP("raw-field", "F", nil, "Add a string parameter in key=value format, reading from file if value starts with @ (can be used multiple times)") - // Add flags for secret commands addRepoFlags(actionsSecretListCmd) addRepoFlags(actionsSecretCreateCmd) diff --git a/cmd/aliases.go b/cmd/aliases.go index 401c040..46f0a24 100644 --- a/cmd/aliases.go +++ b/cmd/aliases.go @@ -1,142 +1,16 @@ package cmd -import ( - "time" - - "github.com/spf13/cobra" -) - -// Top-level aliases for "actions run" and "actions workflow" commands, -// matching gh CLI's command structure (e.g., "fj run list" instead of "fj actions run list"). +// Top-level aliases for "actions run" and "actions workflow" — matches gh +// CLI's ergonomics so users can type `fj run list` and `fj workflow list` +// instead of `fj actions run list`. +// +// Both trees are built from the same factory functions defined in +// `cmd/actions.go` (newRunCmd / newWorkflowCmd), which means flags and +// help text are guaranteed identical between the two paths. Previously +// this file rebuilt parallel trees by hand and silently drifted (the +// `--json` Bool/string mismatch was the symptom that surfaced). func init() { - // --- run alias --- - runAliasCmd := &cobra.Command{ - Use: "run", - Short: "View and manage workflow runs (alias for 'actions run')", - Long: "List, view, and manage workflow runs.\n\nThis is a top-level alias for 'actions run'.", - } - - runAliasListCmd := &cobra.Command{ - Use: "list", - Short: "List recent workflow runs", - Long: "List recent workflow runs for a repository.", - RunE: runRunList, - } - addRepoFlags(runAliasListCmd) - runAliasListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list") - addJSONFlags(runAliasListCmd, "Output workflow runs as JSON") - - runAliasViewCmd := &cobra.Command{ - Use: "view ", - Short: "View a workflow run", - Long: "View details about a specific workflow run.", - Args: cobra.ExactArgs(1), - RunE: runRunView, - } - addRepoFlags(runAliasViewCmd) - runAliasViewCmd.Flags().BoolP("verbose", "v", false, "Show job steps") - runAliasViewCmd.Flags().BoolP("log", "", false, "View full log for either a run or specific job") - runAliasViewCmd.Flags().StringP("job", "j", "", "View a specific job ID from a run") - runAliasViewCmd.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job") - addJSONFlags(runAliasViewCmd, "Output workflow run as JSON") - - runAliasWatchCmd := &cobra.Command{ - Use: "watch ", - Short: "Watch a workflow run", - Long: "Poll a workflow run until it completes.", - Args: cobra.ExactArgs(1), - RunE: runRunWatch, - } - addRepoFlags(runAliasWatchCmd) - runAliasWatchCmd.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval") - - runAliasRerunCmd := &cobra.Command{ - Use: "rerun ", - Short: "Rerun a workflow run", - Long: "Trigger a rerun for a specific workflow run.", - Args: cobra.ExactArgs(1), - RunE: runRunRerun, - } - addRepoFlags(runAliasRerunCmd) - - runAliasCancelCmd := &cobra.Command{ - Use: "cancel ", - Short: "Cancel a workflow run", - Long: "Cancel a running workflow run.", - Args: cobra.ExactArgs(1), - RunE: runRunCancel, - } - addRepoFlags(runAliasCancelCmd) - - runAliasCmd.AddCommand(runAliasListCmd) - runAliasCmd.AddCommand(runAliasViewCmd) - runAliasCmd.AddCommand(runAliasWatchCmd) - runAliasCmd.AddCommand(runAliasRerunCmd) - runAliasCmd.AddCommand(runAliasCancelCmd) - rootCmd.AddCommand(runAliasCmd) - - // --- workflow alias --- - workflowAliasCmd := &cobra.Command{ - Use: "workflow", - Short: "Manage workflows (alias for 'actions workflow')", - Long: "List, view, and run workflows.\n\nThis is a top-level alias for 'actions workflow'.", - } - - workflowAliasListCmd := &cobra.Command{ - Use: "list", - Short: "List workflows", - Long: "List all workflows in a repository.", - RunE: runWorkflowList, - } - addRepoFlags(workflowAliasListCmd) - workflowAliasListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list") - addJSONFlags(workflowAliasListCmd, "Output workflows as JSON") - - workflowAliasViewCmd := &cobra.Command{ - Use: "view ", - Short: "View a workflow", - Long: "View details about a specific workflow. You can specify the workflow by name or filename.", - Args: cobra.ExactArgs(1), - RunE: runWorkflowView, - } - addRepoFlags(workflowAliasViewCmd) - addJSONFlags(workflowAliasViewCmd, "Output workflow as JSON") - - workflowAliasRunCmd := &cobra.Command{ - Use: "run ", - Short: "Run a workflow", - Long: "Trigger a workflow_dispatch event for a workflow. The workflow must support the workflow_dispatch trigger.", - Args: cobra.ExactArgs(1), - RunE: runWorkflowRun, - } - addRepoFlags(workflowAliasRunCmd) - workflowAliasRunCmd.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)") - workflowAliasRunCmd.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)") - workflowAliasRunCmd.Flags().StringSliceP("raw-field", "F", nil, "Add a string parameter in key=value format, reading from file if value starts with @ (can be used multiple times)") - - workflowAliasEnableCmd := &cobra.Command{ - Use: "enable ", - Short: "Enable a workflow", - Long: "Enable a workflow so it can be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to enable workflows.", - Args: cobra.ExactArgs(1), - RunE: runWorkflowEnable, - } - addRepoFlags(workflowAliasEnableCmd) - - workflowAliasDisableCmd := &cobra.Command{ - Use: "disable ", - Short: "Disable a workflow", - Long: "Disable a workflow so it cannot be triggered.\n\nNote: This feature requires Forgejo 15.0+ or Gitea 1.24+.\nFor older versions, use the web UI to disable workflows.", - Args: cobra.ExactArgs(1), - RunE: runWorkflowDisable, - } - addRepoFlags(workflowAliasDisableCmd) - - workflowAliasCmd.AddCommand(workflowAliasListCmd) - workflowAliasCmd.AddCommand(workflowAliasViewCmd) - workflowAliasCmd.AddCommand(workflowAliasRunCmd) - workflowAliasCmd.AddCommand(workflowAliasEnableCmd) - workflowAliasCmd.AddCommand(workflowAliasDisableCmd) - rootCmd.AddCommand(workflowAliasCmd) + rootCmd.AddCommand(newRunCmd(" (alias for 'actions run')")) + rootCmd.AddCommand(newWorkflowCmd(" (alias for 'actions workflow')")) }