feat: v0.3.0d — add PR checks, iostreams, aliases, and broad enhancements

Add PR checks command, iostreams/text packages for colored table output,
top-level run/workflow aliases matching gh CLI structure. Enhance actions,
issues, PRs, releases, repos, labels, milestones, and wiki commands with
improved flags, JSON output, and error handling.
This commit is contained in:
sid 2026-03-23 11:42:44 -06:00
parent 7c0dcc8696
commit 113505de95
29 changed files with 3131 additions and 542 deletions

View file

@ -3,10 +3,8 @@ package cmd
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
"code.gitea.io/sdk/gitea"
@ -100,39 +98,67 @@ var runListCmd = &cobra.Command{
Use: "list",
Short: "List recent workflow runs",
Long: "List recent workflow runs for a repository.",
RunE: runRunList,
Example: ` # List recent workflow runs
fgj actions run list
# List runs with a custom limit
fgj actions run list -L 50
# Output as JSON
fgj actions run list --json`,
RunE: runRunList,
}
var runViewCmd = &cobra.Command{
Use: "view <run-id>",
Short: "View a workflow run",
Long: "View details about a specific workflow run.",
Args: cobra.ExactArgs(1),
RunE: runRunView,
Example: ` # View a workflow run
fgj actions run view 123
# View with job details
fgj actions run view 123 -v
# View logs for a specific job
fgj actions run view 123 --job 456 --log
# View only failed logs
fgj actions run view 123 --log-failed`,
Args: cobra.ExactArgs(1),
RunE: runRunView,
}
var runWatchCmd = &cobra.Command{
Use: "watch <run-id>",
Short: "Watch a workflow run",
Long: "Poll a workflow run until it completes.",
Args: cobra.ExactArgs(1),
RunE: runRunWatch,
Example: ` # Watch a run until it completes
fgj actions run watch 123
# Watch with a custom polling interval
fgj actions run watch 123 -i 10s`,
Args: cobra.ExactArgs(1),
RunE: runRunWatch,
}
var runRerunCmd = &cobra.Command{
Use: "rerun <run-id>",
Short: "Rerun a workflow run",
Long: "Trigger a rerun for a specific workflow run.",
Args: cobra.ExactArgs(1),
RunE: runRunRerun,
Example: ` # Rerun a failed workflow run
fgj actions run rerun 123`,
Args: cobra.ExactArgs(1),
RunE: runRunRerun,
}
var runCancelCmd = &cobra.Command{
Use: "cancel <run-id>",
Short: "Cancel a workflow run",
Long: "Cancel a running workflow run.",
Args: cobra.ExactArgs(1),
RunE: runRunCancel,
Example: ` # Cancel a running workflow
fgj actions run cancel 123`,
Args: cobra.ExactArgs(1),
RunE: runRunCancel,
}
// Workflow commands
@ -146,39 +172,61 @@ var workflowListCmd = &cobra.Command{
Use: "list",
Short: "List workflows",
Long: "List all workflows in a repository.",
RunE: runWorkflowList,
Example: ` # List all workflows
fgj actions workflow list
# List workflows as JSON
fgj actions workflow list --json
# List workflows for a specific repo
fgj actions workflow list -R owner/repo`,
RunE: runWorkflowList,
}
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.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowView,
Example: ` # View a workflow by filename
fgj actions workflow view ci.yml
# View as JSON
fgj actions workflow view ci.yml --json`,
Args: cobra.ExactArgs(1),
RunE: runWorkflowView,
}
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.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowRun,
Example: ` # Trigger a workflow on the default branch
fgj actions workflow run deploy.yml
# Trigger on a specific branch with input parameters
fgj actions workflow run deploy.yml -r staging -f environment=staging -f version=1.2.3`,
Args: cobra.ExactArgs(1),
RunE: runWorkflowRun,
}
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.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowEnable,
Example: ` # Enable a workflow
fgj actions workflow enable ci.yml`,
Args: cobra.ExactArgs(1),
RunE: runWorkflowEnable,
}
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.",
Args: cobra.ExactArgs(1),
RunE: runWorkflowDisable,
Example: ` # Disable a workflow
fgj actions workflow disable ci.yml`,
Args: cobra.ExactArgs(1),
RunE: runWorkflowDisable,
}
// Secret commands
@ -192,23 +240,35 @@ var actionsSecretListCmd = &cobra.Command{
Use: "list",
Short: "List repository secrets",
Long: "List all secrets for a repository.",
RunE: runActionsSecretList,
Example: ` # List all secrets
fgj actions secret list
# List secrets for a specific repo
fgj actions secret list -R owner/repo`,
RunE: runActionsSecretList,
}
var actionsSecretCreateCmd = &cobra.Command{
Use: "create <name>",
Short: "Create or update a repository secret",
Long: "Create or update a secret for Forgejo Actions. The secret value will be read from stdin.",
Args: cobra.ExactArgs(1),
RunE: runActionsSecretCreate,
Example: ` # Create a secret (will prompt for value)
fgj actions secret create DEPLOY_TOKEN
# Create a secret for a specific repo
fgj actions secret create API_KEY -R owner/repo`,
Args: cobra.ExactArgs(1),
RunE: runActionsSecretCreate,
}
var actionsSecretDeleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "Delete a repository secret",
Long: "Delete a secret from Forgejo Actions.",
Args: cobra.ExactArgs(1),
RunE: runActionsSecretDelete,
Example: ` # Delete a secret
fgj actions secret delete DEPLOY_TOKEN`,
Args: cobra.ExactArgs(1),
RunE: runActionsSecretDelete,
}
// Variable commands
@ -222,39 +282,55 @@ var actionsVariableListCmd = &cobra.Command{
Use: "list",
Short: "List repository variables",
Long: "List all variables for a repository.",
RunE: runActionsVariableList,
Example: ` # List all variables
fgj actions variable list
# List variables for a specific repo
fgj actions variable list -R owner/repo`,
RunE: runActionsVariableList,
}
var actionsVariableGetCmd = &cobra.Command{
Use: "get <name>",
Short: "Get a repository variable",
Long: "Get the value of a specific repository variable.",
Args: cobra.ExactArgs(1),
RunE: runActionsVariableGet,
Example: ` # Get a variable value
fgj actions variable get ENVIRONMENT`,
Args: cobra.ExactArgs(1),
RunE: runActionsVariableGet,
}
var actionsVariableCreateCmd = &cobra.Command{
Use: "create <name> <value>",
Short: "Create a repository variable",
Long: "Create a new variable for Forgejo Actions.",
Args: cobra.ExactArgs(2),
RunE: runActionsVariableCreate,
Example: ` # Create a variable
fgj actions variable create ENVIRONMENT production
# Create a variable for a specific repo
fgj actions variable create NODE_VERSION 20 -R owner/repo`,
Args: cobra.ExactArgs(2),
RunE: runActionsVariableCreate,
}
var actionsVariableUpdateCmd = &cobra.Command{
Use: "update <name> <value>",
Short: "Update a repository variable",
Long: "Update an existing variable for Forgejo Actions.",
Args: cobra.ExactArgs(2),
RunE: runActionsVariableUpdate,
Example: ` # Update a variable
fgj actions variable update ENVIRONMENT staging`,
Args: cobra.ExactArgs(2),
RunE: runActionsVariableUpdate,
}
var actionsVariableDeleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "Delete a repository variable",
Long: "Delete a variable from Forgejo Actions.",
Args: cobra.ExactArgs(1),
RunE: runActionsVariableDelete,
Example: ` # Delete a variable
fgj actions variable delete ENVIRONMENT`,
Args: cobra.ExactArgs(1),
RunE: runActionsVariableDelete,
}
func init() {
@ -293,13 +369,13 @@ func init() {
// Add flags for run commands
addRepoFlags(runListCmd)
runListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list")
runListCmd.Flags().Bool("json", false, "Output workflow runs as JSON")
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")
runViewCmd.Flags().Bool("json", false, "Output workflow run as JSON")
addJSONFlags(runViewCmd, "Output workflow run as JSON")
addRepoFlags(runWatchCmd)
runWatchCmd.Flags().DurationP("interval", "i", 5*time.Second, "Polling interval")
addRepoFlags(runRerunCmd)
@ -308,9 +384,9 @@ func init() {
// Add flags for workflow commands
addRepoFlags(workflowListCmd)
workflowListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list")
workflowListCmd.Flags().Bool("json", false, "Output workflows as JSON")
addJSONFlags(workflowListCmd, "Output workflows as JSON")
addRepoFlags(workflowViewCmd)
workflowViewCmd.Flags().Bool("json", false, "Output workflow as JSON")
addJSONFlags(workflowViewCmd, "Output workflow as JSON")
addRepoFlags(workflowRunCmd)
addRepoFlags(workflowEnableCmd)
addRepoFlags(workflowDisableCmd)
@ -325,6 +401,7 @@ func init() {
// Add flags for variable commands
addRepoFlags(actionsVariableListCmd)
addJSONFlags(actionsVariableListCmd, "Output variables as JSON")
addRepoFlags(actionsVariableGetCmd)
addRepoFlags(actionsVariableCreateCmd)
addRepoFlags(actionsVariableUpdateCmd)
@ -364,39 +441,26 @@ func runRunList(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to list runs: %w", err)
}
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
return writeJSON(runList.WorkflowRuns)
if wantJSON(cmd) {
return outputJSON(cmd, runList.WorkflowRuns)
}
if len(runList.WorkflowRuns) == 0 {
fmt.Println("No workflow runs found")
fmt.Fprintln(ios.Out, "No workflow runs found")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if _, err := fmt.Fprintln(w, "STATUS\tTITLE\tWORKFLOW\tEVENT\tID\tCREATED"); err != nil {
return fmt.Errorf("failed to write header: %w", err)
}
tp := ios.NewTablePrinter()
tp.AddHeader("STATUS", "TITLE", "WORKFLOW", "EVENT", "ID", "CREATED")
for _, run := range runList.WorkflowRuns {
createdTime, err := time.Parse(time.RFC3339, run.Created)
if err != nil {
createdTime = time.Now()
}
timeStr := formatTimeSince(createdTime)
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%s\n",
formatStatus(run.Status), run.Title, run.WorkflowID, run.Event, run.ID, timeStr); err != nil {
return fmt.Errorf("failed to write run: %w", err)
}
tp.AddRow(formatStatus(run.Status), run.Title, run.WorkflowID, run.Event, fmt.Sprintf("%d", run.ID), timeStr)
}
if err := w.Flush(); err != nil {
return fmt.Errorf("failed to flush output: %w", err)
}
return nil
return tp.Render()
}
func runRunView(cmd *cobra.Command, args []string) error {
@ -425,7 +489,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
showLog, _ := cmd.Flags().GetBool("log")
jobIDStr, _ := cmd.Flags().GetString("job")
showLogFailed, _ := cmd.Flags().GetBool("log-failed")
jsonOutput, _ := cmd.Flags().GetBool("json")
jsonRequested := wantJSON(cmd)
var jobID int64
if jobIDStr != "" {
@ -436,7 +500,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
}
}
if jsonOutput && (showLog || showLogFailed) {
if jsonRequested && (showLog || showLogFailed) {
return fmt.Errorf("--json cannot be used with --log or --log-failed")
}
@ -450,7 +514,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
needsJobs := verbose || showLog || showLogFailed || jobID > 0
if jsonOutput {
if jsonRequested {
var runTasks []ActionTask
if needsJobs {
tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
@ -487,33 +551,33 @@ func runRunView(cmd *cobra.Command, args []string) error {
Run: run,
Tasks: runTasks,
}
return writeJSON(payload)
return outputJSON(cmd, payload)
}
// Display run information
fmt.Printf("Title: %s\n", run.Title)
fmt.Printf("Workflow: %s\n", run.WorkflowID)
fmt.Printf("Run: #%d\n", run.IndexInRepo)
fmt.Printf("Status: %s\n", formatStatus(run.Status))
fmt.Printf("Event: %s\n", run.Event)
fmt.Printf("Ref: %s\n", run.PrettyRef)
fmt.Fprintf(ios.Out, "Title: %s\n", run.Title)
fmt.Fprintf(ios.Out, "Workflow: %s\n", run.WorkflowID)
fmt.Fprintf(ios.Out, "Run: #%d\n", run.IndexInRepo)
fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(run.Status))
fmt.Fprintf(ios.Out, "Event: %s\n", run.Event)
fmt.Fprintf(ios.Out, "Ref: %s\n", run.PrettyRef)
commit := run.CommitSHA
if len(commit) > 8 {
commit = commit[:8]
}
fmt.Printf("Commit: %s\n", commit)
fmt.Fprintf(ios.Out, "Commit: %s\n", commit)
if createdTime, err := time.Parse(time.RFC3339, run.Created); err == nil {
fmt.Printf("Created: %s\n", createdTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, "Created: %s\n", createdTime.Format("2006-01-02 15:04:05"))
}
if run.Started != "" {
if startedTime, err := time.Parse(time.RFC3339, run.Started); err == nil {
fmt.Printf("Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, "Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
}
}
if updatedTime, err := time.Parse(time.RFC3339, run.Updated); err == nil {
fmt.Printf("Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, "Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
}
// Fetch jobs if needed for verbose, log, or job-specific views
@ -536,7 +600,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
}
if len(runTasks) == 0 {
fmt.Println("\nNo jobs found for this run")
fmt.Fprintln(ios.Out, "\nNo jobs found for this run")
return nil
}
@ -557,14 +621,14 @@ func runRunView(cmd *cobra.Command, args []string) error {
// Case 1: --verbose (show job steps/details without logs)
if verbose && !showLog && !showLogFailed {
fmt.Println("\nJobs:")
fmt.Fprintln(ios.Out, "\nJobs:")
for _, task := range runTasks {
fmt.Printf("\n %s - %s (ID: %d)\n", formatStatus(task.Status), task.Name, task.ID)
fmt.Fprintf(ios.Out, "\n %s - %s (ID: %d)\n", formatStatus(task.Status), task.Name, task.ID)
if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
fmt.Printf(" Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, " Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
}
if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
fmt.Printf(" Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, " Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
}
}
return nil
@ -574,7 +638,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
if showLog || showLogFailed {
for _, task := range runTasks {
if err := showJobLog(client, owner, name, task, showLogFailed); err != nil {
fmt.Printf("\nError fetching log for job %s: %v\n", task.Name, err)
fmt.Fprintf(ios.Out, "\nError fetching log for job %s: %v\n", task.Name, err)
}
}
return nil
@ -583,15 +647,15 @@ func runRunView(cmd *cobra.Command, args []string) error {
// Case 3: --job without --log or --verbose (show job details only)
if jobID > 0 {
task := runTasks[0]
fmt.Println("\nJob Details:")
fmt.Printf(" Name: %s\n", task.Name)
fmt.Printf(" ID: %d\n", task.ID)
fmt.Printf(" Status: %s\n", formatStatus(task.Status))
fmt.Fprintln(ios.Out, "\nJob Details:")
fmt.Fprintf(ios.Out, " Name: %s\n", task.Name)
fmt.Fprintf(ios.Out, " ID: %d\n", task.ID)
fmt.Fprintf(ios.Out, " Status: %s\n", formatStatus(task.Status))
if startedTime, err := time.Parse(time.RFC3339, task.RunStartedAt); err == nil {
fmt.Printf(" Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, " Started: %s\n", startedTime.Format("2006-01-02 15:04:05"))
}
if updatedTime, err := time.Parse(time.RFC3339, task.UpdatedAt); err == nil {
fmt.Printf(" Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
fmt.Fprintf(ios.Out, " Updated: %s\n", updatedTime.Format("2006-01-02 15:04:05"))
}
}
@ -635,12 +699,12 @@ func runRunWatch(cmd *cobra.Command, args []string) error {
}
if run.Status != lastStatus {
fmt.Printf("Status: %s\n", formatStatus(run.Status))
fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(run.Status))
lastStatus = run.Status
}
if isRunComplete(run.Status) {
fmt.Printf("Run #%d completed with status %s\n", run.IndexInRepo, formatStatus(run.Status))
fmt.Fprintf(ios.Out, "Run #%d completed with status %s\n", run.IndexInRepo, formatStatus(run.Status))
return nil
}
@ -675,7 +739,7 @@ func runRunRerun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to rerun workflow: %w", err)
}
fmt.Printf("✓ Rerun requested for run %d\n", runID)
fmt.Fprintf(ios.Out, "✓ Rerun requested for run %d\n", runID)
return nil
}
@ -706,7 +770,7 @@ func runRunCancel(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to cancel workflow run: %w", err)
}
fmt.Printf("✓ Cancel requested for run %d\n", runID)
fmt.Fprintf(ios.Out, "✓ Cancel requested for run %d\n", runID)
return nil
}
@ -715,10 +779,10 @@ func showJobLog(client *api.Client, owner, name string, task ActionTask, logFail
logURL := fmt.Sprintf("https://%s/api/v1/repos/%s/%s/actions/jobs/%d/logs",
client.Hostname(), owner, name, task.ID)
fmt.Printf("\n========================================\n")
fmt.Printf("Job: %s (ID: %d)\n", task.Name, task.ID)
fmt.Printf("Status: %s\n", formatStatus(task.Status))
fmt.Printf("========================================\n\n")
fmt.Fprintf(ios.Out, "\n========================================\n")
fmt.Fprintf(ios.Out, "Job: %s (ID: %d)\n", task.Name, task.ID)
fmt.Fprintf(ios.Out, "Status: %s\n", formatStatus(task.Status))
fmt.Fprintf(ios.Out, "========================================\n\n")
// Use GetRawLog helper
logContent, err := client.GetRawLog(logURL)
@ -731,11 +795,11 @@ func showJobLog(client *api.Client, owner, name string, task ActionTask, logFail
if logFailed {
// TODO: Implement filtering for failed steps only
// This would require parsing the log format and identifying failed step markers
fmt.Println("Note: --log-failed filtering not yet implemented, showing all logs")
fmt.Fprintln(ios.Out, "Note: --log-failed filtering not yet implemented, showing all logs")
}
fmt.Print(logContent)
fmt.Println()
fmt.Fprint(ios.Out, logContent)
fmt.Fprintln(ios.Out)
return nil
}
@ -848,34 +912,25 @@ func runWorkflowList(cmd *cobra.Command, args []string) error {
}
if len(allWorkflows) == 0 {
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
return writeJSON(allWorkflows)
if wantJSON(cmd) {
return outputJSON(cmd, allWorkflows)
}
fmt.Println("No workflows found")
fmt.Fprintln(ios.Out, "No workflows found")
return nil
}
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
return writeJSON(allWorkflows)
if wantJSON(cmd) {
return outputJSON(cmd, allWorkflows)
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if _, err := fmt.Fprintln(w, "NAME\tSTATE\tPATH"); err != nil {
return fmt.Errorf("failed to write header: %w", err)
}
tp := ios.NewTablePrinter()
tp.AddHeader("NAME", "STATE", "PATH")
for _, workflow := range allWorkflows {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n",
workflow.Name, workflow.State, workflow.Path); err != nil {
return fmt.Errorf("failed to write workflow: %w", err)
}
tp.AddRow(workflow.Name, workflow.State, workflow.Path)
}
if err := w.Flush(); err != nil {
return fmt.Errorf("failed to flush output: %w", err)
}
return nil
return tp.Render()
}
func runWorkflowView(cmd *cobra.Command, args []string) error {
@ -901,8 +956,6 @@ func runWorkflowView(cmd *cobra.Command, args []string) error {
return err
}
jsonOutput, _ := cmd.Flags().GetBool("json")
var latestRun *ActionRun
// Get the latest run for this workflow
@ -912,7 +965,7 @@ func runWorkflowView(cmd *cobra.Command, args []string) error {
latestRun = &runList.WorkflowRuns[0]
}
if jsonOutput {
if wantJSON(cmd) {
payload := struct {
Workflow *Workflow `json:"workflow"`
LatestRun *ActionRun `json:"latest_run,omitempty"`
@ -920,21 +973,21 @@ func runWorkflowView(cmd *cobra.Command, args []string) error {
Workflow: workflow,
LatestRun: latestRun,
}
return writeJSON(payload)
return outputJSON(cmd, payload)
}
// Display workflow information
fmt.Printf("Name: %s\n", workflow.Name)
fmt.Printf("Path: %s\n", workflow.Path)
fmt.Printf("State: %s\n", workflow.State)
fmt.Fprintf(ios.Out, "Name: %s\n", workflow.Name)
fmt.Fprintf(ios.Out, "Path: %s\n", workflow.Path)
fmt.Fprintf(ios.Out, "State: %s\n", workflow.State)
if latestRun != nil {
fmt.Printf("\nLatest run:\n")
fmt.Printf(" Status: %s\n", formatStatus(latestRun.Status))
fmt.Printf(" Event: %s\n", latestRun.Event)
fmt.Printf(" Ref: %s\n", latestRun.PrettyRef)
fmt.Fprintf(ios.Out, "\nLatest run:\n")
fmt.Fprintf(ios.Out, " Status: %s\n", formatStatus(latestRun.Status))
fmt.Fprintf(ios.Out, " Event: %s\n", latestRun.Event)
fmt.Fprintf(ios.Out, " Ref: %s\n", latestRun.PrettyRef)
if createdTime, err := time.Parse(time.RFC3339, latestRun.Created); err == nil {
fmt.Printf(" Created: %s\n", formatTimeSince(createdTime))
fmt.Fprintf(ios.Out, " Created: %s\n", formatTimeSince(createdTime))
}
}
@ -1006,12 +1059,12 @@ func runWorkflowRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to trigger workflow: %w", err)
}
fmt.Printf("✓ Workflow '%s' triggered successfully\n", workflowIdentifier)
fmt.Printf(" Branch/Tag: %s\n", ref)
fmt.Fprintf(ios.Out, "✓ Workflow '%s' triggered successfully\n", workflowIdentifier)
fmt.Fprintf(ios.Out, " Branch/Tag: %s\n", ref)
if len(inputs) > 0 {
fmt.Println(" Inputs:")
fmt.Fprintln(ios.Out, " Inputs:")
for key, value := range inputs {
fmt.Printf(" %s: %s\n", key, value)
fmt.Fprintf(ios.Out, " %s: %s\n", key, value)
}
}
@ -1059,7 +1112,7 @@ func runWorkflowEnable(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to enable workflow: %w", err)
}
fmt.Printf("✓ Workflow '%s' enabled\n", workflow.Name)
fmt.Fprintf(ios.Out, "✓ Workflow '%s' enabled\n", workflow.Name)
return nil
}
@ -1104,7 +1157,7 @@ func runWorkflowDisable(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to disable workflow: %w", err)
}
fmt.Printf("✓ Workflow '%s' disabled\n", workflow.Name)
fmt.Fprintf(ios.Out, "✓ Workflow '%s' disabled\n", workflow.Name)
return nil
}
@ -1170,24 +1223,16 @@ func runActionsSecretList(cmd *cobra.Command, args []string) error {
}
if len(secrets) == 0 {
fmt.Println("No secrets found")
fmt.Fprintln(ios.Out, "No secrets found")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if _, err := fmt.Fprintln(w, "NAME\tCREATED"); err != nil {
return fmt.Errorf("failed to write header: %w", err)
}
tp := ios.NewTablePrinter()
tp.AddHeader("NAME", "CREATED")
for _, secret := range secrets {
if _, err := fmt.Fprintf(w, "%s\t%s\n", secret.Name, secret.Created.Format("2006-01-02 15:04:05")); err != nil {
return fmt.Errorf("failed to write secret: %w", err)
}
tp.AddRow(secret.Name, secret.Created.Format("2006-01-02 15:04:05"))
}
if err := w.Flush(); err != nil {
return fmt.Errorf("failed to flush output: %w", err)
}
return nil
return tp.Render()
}
func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
@ -1210,7 +1255,7 @@ func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
secretName := args[0]
// Read secret value from stdin
fmt.Print("Enter secret value: ")
fmt.Fprint(ios.ErrOut, "Enter secret value: ")
var secretValue string
_, err = fmt.Scanln(&secretValue)
if err != nil {
@ -1227,7 +1272,7 @@ func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create secret: %w", err)
}
fmt.Printf("Secret '%s' created successfully\n", secretName)
fmt.Fprintf(ios.Out, "Secret '%s' created successfully\n", secretName)
return nil
}
@ -1255,15 +1300,57 @@ func runActionsSecretDelete(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to delete secret: %w", err)
}
fmt.Printf("Secret '%s' deleted successfully\n", secretName)
fmt.Fprintf(ios.Out, "Secret '%s' deleted successfully\n", secretName)
return nil
}
// Variable command implementations
// ActionVariable represents a repository action variable from the API
type ActionVariable struct {
Name string `json:"name"`
Value string `json:"data"`
}
func runActionsVariableList(cmd *cobra.Command, args []string) error {
// Note: The SDK doesn't have a ListRepoActionVariable method yet
return fmt.Errorf("listing variables is not yet supported in the SDK")
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
repo, _ := cmd.Flags().GetString("repo")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, name)
var variables []ActionVariable
if err := client.GetJSON(endpoint, &variables); err != nil {
return fmt.Errorf("failed to list variables: %w", err)
}
if wantJSON(cmd) {
return outputJSON(cmd, variables)
}
if len(variables) == 0 {
fmt.Fprintln(ios.Out, "No variables found")
return nil
}
tp := ios.NewTablePrinter()
tp.AddHeader("NAME", "VALUE")
for _, v := range variables {
tp.AddRow(v.Name, v.Value)
}
return tp.Render()
}
func runActionsVariableGet(cmd *cobra.Command, args []string) error {
@ -1290,7 +1377,7 @@ func runActionsVariableGet(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to get variable: %w", err)
}
fmt.Printf("%s=%s\n", variable.Name, variable.Value)
fmt.Fprintf(ios.Out, "%s=%s\n", variable.Name, variable.Value)
return nil
}
@ -1319,7 +1406,7 @@ func runActionsVariableCreate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create variable: %w", err)
}
fmt.Printf("Variable '%s' created successfully\n", variableName)
fmt.Fprintf(ios.Out, "Variable '%s' created successfully\n", variableName)
return nil
}
@ -1348,7 +1435,7 @@ func runActionsVariableUpdate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to update variable: %w", err)
}
fmt.Printf("Variable '%s' updated successfully\n", variableName)
fmt.Fprintf(ios.Out, "Variable '%s' updated successfully\n", variableName)
return nil
}
@ -1376,6 +1463,6 @@ func runActionsVariableDelete(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to delete variable: %w", err)
}
fmt.Printf("Variable '%s' deleted successfully\n", variableName)
fmt.Fprintf(ios.Out, "Variable '%s' deleted successfully\n", variableName)
return nil
}