@@ -2,11 +2,15 @@ package analysis
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
7+ "io"
8+ "os"
69 "strings"
710 "time"
811
912 "github.com/MakeNowJust/heredoc"
13+ "github.com/deepsourcelabs/cli/command/cmddeps"
1014 "github.com/deepsourcelabs/cli/config"
1115 "github.com/deepsourcelabs/cli/deepsource"
1216 "github.com/deepsourcelabs/cli/deepsource/runs"
@@ -19,14 +23,28 @@ import (
1923)
2024
2125type AnalysisOptions struct {
22- RepoArg string
23- LimitArg int
24- commitOid string
26+ RepoArg string
27+ LimitArg int
28+ OutputFormat string
29+ commitOid string
30+ deps * cmddeps.Deps
31+ }
32+
33+ func (opts * AnalysisOptions ) stdout () io.Writer {
34+ if opts .deps != nil && opts .deps .Stdout != nil {
35+ return opts .deps .Stdout
36+ }
37+ return os .Stdout
2538}
2639
2740func NewCmdAnalysis () * cobra.Command {
41+ return NewCmdAnalysisWithDeps (nil )
42+ }
43+
44+ func NewCmdAnalysisWithDeps (deps * cmddeps.Deps ) * cobra.Command {
2845 opts := AnalysisOptions {
2946 LimitArg : 20 ,
47+ deps : deps ,
3048 }
3149
3250 doc := heredoc .Docf (`
@@ -63,17 +81,29 @@ func NewCmdAnalysis() *cobra.Command {
6381 cmd .Flags ().StringVarP (& opts .RepoArg , "repo" , "r" , "" , "List history for the specified repository" )
6482 cmd .Flags ().IntVarP (& opts .LimitArg , "limit" , "l" , 20 , "Number of analysis runs to fetch" )
6583 cmd .Flags ().StringVar (& opts .commitOid , "commit" , "" , "Show metadata and issues summary for a specific commit" )
84+ cmd .Flags ().StringVarP (& opts .OutputFormat , "output" , "o" , "pretty" , "Output format: pretty, json" )
6685
6786 _ = cmd .RegisterFlagCompletionFunc ("repo" , func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
6887 return completion .RepoCompletionCandidates (), cobra .ShellCompDirectiveNoFileComp
6988 })
89+ _ = cmd .RegisterFlagCompletionFunc ("output" , func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
90+ return []string {
91+ "pretty\t Pretty-printed output" ,
92+ "json\t JSON output" ,
93+ }, cobra .ShellCompDirectiveNoFileComp
94+ })
7095
7196 return cmd
7297}
7398
7499// runList fetches and displays a table of recent analysis runs.
75100func (opts * AnalysisOptions ) runList () error {
76- cfgMgr := config .DefaultManager ()
101+ var cfgMgr * config.Manager
102+ if opts .deps != nil && opts .deps .ConfigMgr != nil {
103+ cfgMgr = opts .deps .ConfigMgr
104+ } else {
105+ cfgMgr = config .DefaultManager ()
106+ }
77107 cfg , err := cfgMgr .Load ()
78108 if err != nil {
79109 return clierrors .NewCLIError (clierrors .ErrInvalidConfig , "Error reading DeepSource CLI config" , err )
@@ -87,13 +117,18 @@ func (opts *AnalysisOptions) runList() error {
87117 return err
88118 }
89119
90- client , err := deepsource .New (deepsource.ClientOpts {
91- Token : cfg .Token ,
92- HostName : cfg .Host ,
93- OnTokenRefreshed : cfgMgr .TokenRefreshCallback (),
94- })
95- if err != nil {
96- return err
120+ var client * deepsource.Client
121+ if opts .deps != nil && opts .deps .Client != nil {
122+ client = opts .deps .Client
123+ } else {
124+ client , err = deepsource .New (deepsource.ClientOpts {
125+ Token : cfg .Token ,
126+ HostName : cfg .Host ,
127+ OnTokenRefreshed : cfgMgr .TokenRefreshCallback (),
128+ })
129+ if err != nil {
130+ return err
131+ }
97132 }
98133
99134 ctx := context .Background ()
@@ -107,13 +142,22 @@ func (opts *AnalysisOptions) runList() error {
107142 return nil
108143 }
109144
145+ if opts .OutputFormat == "json" {
146+ return opts .outputRunsJSON (analysisRuns )
147+ }
148+
110149 showRunsTable (analysisRuns )
111150 return nil
112151}
113152
114153// runDetail fetches and displays metadata + issues summary for a single commit.
115154func (opts * AnalysisOptions ) runDetail (ctx context.Context ) error {
116- cfgMgr := config .DefaultManager ()
155+ var cfgMgr * config.Manager
156+ if opts .deps != nil && opts .deps .ConfigMgr != nil {
157+ cfgMgr = opts .deps .ConfigMgr
158+ } else {
159+ cfgMgr = config .DefaultManager ()
160+ }
117161 cfg , err := cfgMgr .Load ()
118162 if err != nil {
119163 return clierrors .NewCLIError (clierrors .ErrInvalidConfig , "Error reading DeepSource CLI config" , err )
@@ -122,13 +166,18 @@ func (opts *AnalysisOptions) runDetail(ctx context.Context) error {
122166 return err
123167 }
124168
125- client , err := deepsource .New (deepsource.ClientOpts {
126- Token : cfg .Token ,
127- HostName : cfg .Host ,
128- OnTokenRefreshed : cfgMgr .TokenRefreshCallback (),
129- })
130- if err != nil {
131- return err
169+ var client * deepsource.Client
170+ if opts .deps != nil && opts .deps .Client != nil {
171+ client = opts .deps .Client
172+ } else {
173+ client , err = deepsource .New (deepsource.ClientOpts {
174+ Token : cfg .Token ,
175+ HostName : cfg .Host ,
176+ OnTokenRefreshed : cfgMgr .TokenRefreshCallback (),
177+ })
178+ if err != nil {
179+ return err
180+ }
132181 }
133182
134183 commitOid := opts .commitOid
@@ -137,6 +186,10 @@ func (opts *AnalysisOptions) runDetail(ctx context.Context) error {
137186 return clierrors .NewCLIError (clierrors .ErrAPIError , "Failed to fetch run details" , err )
138187 }
139188
189+ if opts .OutputFormat == "json" {
190+ return opts .outputRunDetailJSON (runWithIssues )
191+ }
192+
140193 commitShort := commitOid
141194 if len (commitShort ) > 8 {
142195 commitShort = commitShort [:8 ]
@@ -162,6 +215,79 @@ func (opts *AnalysisOptions) runDetail(ctx context.Context) error {
162215 return nil
163216}
164217
218+ // --- JSON output ---
219+
220+ type AnalysisRunJSON struct {
221+ CommitOid string `json:"commit_oid"`
222+ BranchName string `json:"branch_name"`
223+ Status string `json:"status"`
224+ OccurrencesIntroduced int `json:"occurrences_introduced"`
225+ OccurrencesResolved int `json:"occurrences_resolved"`
226+ OccurrencesSuppressed int `json:"occurrences_suppressed"`
227+ FinishedAt * time.Time `json:"finished_at"`
228+ }
229+
230+ type RunDetailJSON struct {
231+ CommitOid string `json:"commit_oid"`
232+ BranchName string `json:"branch_name"`
233+ Status string `json:"status"`
234+ Issues []RunIssueJSON `json:"issues"`
235+ }
236+
237+ type RunIssueJSON struct {
238+ Path string `json:"path"`
239+ Title string `json:"title"`
240+ Code string `json:"code"`
241+ Category string `json:"category"`
242+ Severity string `json:"severity"`
243+ }
244+
245+ func (opts * AnalysisOptions ) outputRunsJSON (analysisRuns []runs.AnalysisRun ) error {
246+ result := make ([]AnalysisRunJSON , 0 , len (analysisRuns ))
247+ for _ , run := range analysisRuns {
248+ result = append (result , AnalysisRunJSON {
249+ CommitOid : run .CommitOid ,
250+ BranchName : run .BranchName ,
251+ Status : run .Status ,
252+ OccurrencesIntroduced : run .OccurrencesIntroduced ,
253+ OccurrencesResolved : run .OccurrencesResolved ,
254+ OccurrencesSuppressed : run .OccurrencesSuppressed ,
255+ FinishedAt : run .FinishedAt ,
256+ })
257+ }
258+ data , err := json .MarshalIndent (result , "" , " " )
259+ if err != nil {
260+ return clierrors .NewCLIError (clierrors .ErrAPIError , "Failed to format JSON output" , err )
261+ }
262+ fmt .Fprintln (opts .stdout (), string (data ))
263+ return nil
264+ }
265+
266+ func (opts * AnalysisOptions ) outputRunDetailJSON (runWithIssues * runs.RunWithIssues ) error {
267+ issuesJSON := make ([]RunIssueJSON , 0 , len (runWithIssues .Issues ))
268+ for _ , issue := range runWithIssues .Issues {
269+ issuesJSON = append (issuesJSON , RunIssueJSON {
270+ Path : issue .Path ,
271+ Title : issue .Title ,
272+ Code : issue .IssueCode ,
273+ Category : issue .Category ,
274+ Severity : issue .Severity ,
275+ })
276+ }
277+ result := RunDetailJSON {
278+ CommitOid : runWithIssues .CommitOid ,
279+ BranchName : runWithIssues .BranchName ,
280+ Status : runWithIssues .Status ,
281+ Issues : issuesJSON ,
282+ }
283+ data , err := json .MarshalIndent (result , "" , " " )
284+ if err != nil {
285+ return clierrors .NewCLIError (clierrors .ErrAPIError , "Failed to format JSON output" , err )
286+ }
287+ fmt .Fprintln (opts .stdout (), string (data ))
288+ return nil
289+ }
290+
165291// --- Display helpers ---
166292
167293func showRunsTable (analysisRuns []runs.AnalysisRun ) {
0 commit comments