refactor(cmd): unify actions/aliases command trees via factory functions
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / functional (push) Blocked by required conditions

The remaining audit finding: cmd/aliases.go rebuilt parallel `run *` and
`workflow *` command subtrees by hand to expose them at top level
(matching gh CLI's `gh run list` ergonomics). That duplication is what
let the `--json` Bool/string mismatch fixed in 0c181df slip in — the
flag was registered correctly under `actions run list` but Bool-typed
under the top-level `run list`, and `wantJSON` silently swallowed the
type-error return.

Switch each `run *` and `workflow *` command from a package-level
`var xxxCmd = &cobra.Command{...}` declaration to a factory function
`newXxxCmd() *cobra.Command` that returns a fully-configured Command
(struct, examples, args, RunE, AND its own flag registrations).

Each parent factory (newRunCmd, newWorkflowCmd) takes a `parentLabel`
string that's appended to the parent's Short/Long, so the alias-tree
variant says "(alias for 'actions run')" without the children diverging.

actions.go init() now does:
  actionsCmd.AddCommand(newRunCmd(""))
  actionsCmd.AddCommand(newWorkflowCmd(""))

aliases.go shrinks from 142 lines to 17 lines:
  rootCmd.AddCommand(newRunCmd(" (alias for 'actions run')"))
  rootCmd.AddCommand(newWorkflowCmd(" (alias for 'actions workflow')"))

Verified: `diff` of `fj run list --help` flags vs `fj actions run list
--help` flags is empty. Both trees produce IDENTICAL surfaces. Future
flag changes touch one factory and propagate to both paths.

Note: secret/variable subcommands aren't aliased so they keep the
package-level var pattern. Only the run/workflow subtrees moved.
This commit is contained in:
sid 2026-05-02 15:56:58 -06:00
parent 155ddb97ba
commit 373c769d2c
2 changed files with 171 additions and 258 deletions

View file

@ -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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <run-id>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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 <workflow>",
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)