package view
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra"
"gitcode.com/gitcode-cli/cli/api"
cmdutil "gitcode.com/gitcode-cli/cli/pkg/cmdutil"
"gitcode.com/gitcode-cli/cli/pkg/iostreams"
)
const msTimestampThreshold = 100_000_000_000
type ViewOptions struct {
IO *iostreams.IOStreams
HttpClient func() (*http.Client, error)
BaseRepo func() (string, error)
Repository string
ArtifactID string
JSON bool
}
func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command {
opts := &ViewOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
BaseRepo: f.BaseRepo,
}
cmd := &cobra.Command{
Use: "view <artifact-id>",
Short: "View an artifact",
Long: heredoc.Doc(`
View the detail of a single workflow artifact.
The artifact id is the ` + "`id`" + ` returned by ` + "`gc actions artifact list`" + `.
Use --json for a faithful, machine-readable copy of the API response.
`),
Example: heredoc.Doc(`
# View an artifact
$ gc actions artifact view <artifact-id> -R owner/repo
# Faithful JSON output
$ gc actions artifact view <artifact-id> -R owner/repo --json
`),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.ArtifactID = strings.TrimSpace(args[0])
if opts.ArtifactID == "" {
return cmdutil.NewUsageError("artifact id is required")
}
if runF != nil {
return runF(opts)
}
return viewRun(opts)
},
}
cmd.Flags().StringVarP(&opts.Repository, "repo", "R", "", "Repository (owner/repo)")
cmdutil.AddJSONFlag(cmd, &opts.JSON)
return cmd
}
func viewRun(opts *ViewOptions) error {
client, err := cmdutil.AuthenticatedClientFromFactory(opts.HttpClient)
if err != nil {
return err
}
repository, err := cmdutil.ResolveRepo(opts.Repository, opts.BaseRepo)
if err != nil {
return err
}
owner, repo, err := cmdutil.ParseRepo(repository)
if err != nil {
return err
}
artifact, raw, err := api.GetActionsArtifact(client, owner, repo, opts.ArtifactID)
if err != nil {
return fmt.Errorf("failed to get artifact: %w", err)
}
if opts.JSON {
if _, err := opts.IO.Out.Write(raw); err != nil {
return fmt.Errorf("failed to write JSON output: %w", err)
}
_, err := fmt.Fprintln(opts.IO.Out)
return err
}
return printArtifact(opts, artifact)
}
func printArtifact(opts *ViewOptions, a *api.Artifact) error {
out := opts.IO.Out
fmt.Fprintf(out, "%s %s\n", orDash(a.Name), sizeLabel(a.SizeBytes))
fmt.Fprintf(out, " artifact id: %s\n", orDash(a.ID))
fmt.Fprintf(out, " name: %s\n", orDash(a.Name))
fmt.Fprintf(out, " size: %s\n", sizeLabel(a.SizeBytes))
fmt.Fprintf(out, " digest: %s\n", orDash(a.Digest))
fmt.Fprintf(out, " workflow id: %s\n", orDash(a.WorkflowID))
fmt.Fprintf(out, " workflow run id: %s\n", orDash(a.WorkflowRunID))
fmt.Fprintf(out, " created: %s\n", formatMsTimeString(a.CreatedAt))
fmt.Fprintf(out, " updated: %s\n", formatMsTimeString(a.UpdatedAt))
fmt.Fprintf(out, " expires: %s\n", formatMsTimeString(a.ExpiresAt))
return nil
}
func orDash(s string) string {
if strings.TrimSpace(s) == "" {
return "-"
}
return s
}
func sizeLabel(bytes int64) string {
const (
kiB = 1024
miB = 1024 * 1024
giB = 1024 * 1024 * 1024
)
switch {
case bytes >= giB:
return fmt.Sprintf("%.1f GiB", float64(bytes)/float64(giB))
case bytes >= miB:
return fmt.Sprintf("%.1f MiB", float64(bytes)/float64(miB))
case bytes >= kiB:
return fmt.Sprintf("%.1f KiB", float64(bytes)/float64(kiB))
default:
return fmt.Sprintf("%d B", bytes)
}
}
func formatMsTimeString(s string) string {
if s == "" {
return "-"
}
t, err := strconv.ParseInt(s, 10, 64)
if err != nil || t <= 0 {
return "-"
}
secs := t
if t >= msTimestampThreshold {
secs = t / 1000
}
return time.Unix(secs, 0).UTC().Format(time.RFC3339)
}