Skip to content

Commit 64c08e9

Browse files
Merge remote-tracking branch 'origin/main' into pylint-config-parser
2 parents 3b64924 + 00aa9c3 commit 64c08e9

File tree

9 files changed

+226
-52
lines changed

9 files changed

+226
-52
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# codacy-cli-v2
22

3-
The `codacy-cli-v2` is a command-line tool for Codacy that supports analyzing code using tools like ESLint and uploading the results in SARIF format to Codacy. It provides two main commands: `analyze` and `upload`.
3+
The Codacy CLI (version 2) is a command-line tool that supports analyzing code using tools like ESLint and uploading the results in SARIF format to Codacy.
4+
The tool is invoked using the `codacy-cli` command, and provides two main commands: analyze and upload.
45

56
### Commands
67

@@ -32,13 +33,13 @@ The `codacy-cli-v2` is a command-line tool for Codacy that supports analyzing co
3233
tools:
3334
3435

35-
- **`codacy-cli-v2 install`**: Command to install the specified node and eslint versions before running analysis.
36+
- **`codacy-cli install`**: Command to install the specified node and eslint versions before running analysis.
3637

3738
## Download
3839

3940
### MacOS (brew)
4041

41-
To install `codacy-cli-v2` using Homebrew:
42+
To install `codacy-cli` using Homebrew:
4243

4344
```bash
4445
brew install codacy/codacy-cli-v2/codacy-cli-v2

cmd/analyze.go

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ package cmd
22

33
import (
44
"codacy/cli-v2/config"
5+
"codacy/cli-v2/plugins"
56
"codacy/cli-v2/tools"
67
"encoding/json"
78
"fmt"
89
"io"
910
"log"
1011
"net/http"
1112
"os"
13+
"path/filepath"
14+
15+
"codacy/cli-v2/utils"
1216

1317
"github.com/spf13/cobra"
1418
)
1519

1620
var outputFile string
17-
var toolToAnalyze string
21+
var toolsToAnalyzeParam string
1822
var autoFix bool
1923
var outputFormat string
2024
var sarifPath string
@@ -92,7 +96,7 @@ type Pattern struct {
9296

9397
func init() {
9498
analyzeCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file for analysis results")
95-
analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Which tool to run analysis with")
99+
analyzeCmd.Flags().StringVarP(&toolsToAnalyzeParam, "tool", "t", "", "Which tool to run analysis with. If not specified, all configured tools will be run")
96100
analyzeCmd.Flags().StringVar(&outputFormat, "format", "", "Output format (use 'sarif' for SARIF format)")
97101
analyzeCmd.Flags().BoolVar(&autoFix, "fix", false, "Apply auto fix to your issues when available")
98102
rootCmd.AddCommand(analyzeCmd)
@@ -224,35 +228,91 @@ func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile s
224228

225229
var analyzeCmd = &cobra.Command{
226230
Use: "analyze",
227-
Short: "Runs all linters.",
228-
Long: "Runs all tools for all runtimes.",
231+
Short: "Runs all configured linters.",
232+
Long: "Runs all configured tools for code analysis. Use --tool flag to run a specific tool.",
229233
Run: func(cmd *cobra.Command, args []string) {
230234
workDirectory, err := os.Getwd()
231235
if err != nil {
232236
log.Fatal(err)
233237
}
238+
var toolsToRun map[string]*plugins.ToolInfo
234239

235-
log.Printf("Running %s...\n", toolToAnalyze)
236-
if outputFormat == "sarif" {
237-
log.Println("Output will be in SARIF format")
240+
if toolsToAnalyzeParam != "" {
241+
// If a specific tool is specified, only run that tool
242+
toolsToRun = map[string]*plugins.ToolInfo{
243+
toolsToAnalyzeParam: config.Config.Tools()[toolsToAnalyzeParam],
244+
}
245+
} else {
246+
// Run all configured tools
247+
toolsToRun = config.Config.Tools()
238248
}
239-
if outputFile != "" {
240-
log.Println("Output will be available at", outputFile)
249+
250+
if len(toolsToRun) == 0 {
251+
log.Fatal("No tools configured. Please run 'codacy-cli init' and 'codacy-cli install' first")
241252
}
242253

243-
switch toolToAnalyze {
244-
case "eslint":
245-
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
246-
case "trivy":
247-
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
248-
case "pmd":
249-
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
250-
case "pylint":
251-
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
252-
case "":
253-
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'")
254-
default:
255-
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
254+
log.Println("Running all configured tools...")
255+
256+
if outputFormat == "sarif" {
257+
// Create temporary directory for individual tool outputs
258+
tmpDir, err := os.MkdirTemp("", "codacy-analysis-*")
259+
if err != nil {
260+
log.Fatalf("Failed to create temporary directory: %v", err)
261+
}
262+
defer os.RemoveAll(tmpDir)
263+
264+
var sarifOutputs []string
265+
for toolName := range toolsToRun {
266+
log.Printf("Running %s...\n", toolName)
267+
tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.sarif", toolName))
268+
runTool(workDirectory, toolName, args, tmpFile)
269+
sarifOutputs = append(sarifOutputs, tmpFile)
270+
}
271+
272+
// create output file tmp file
273+
tmpOutputFile := filepath.Join(tmpDir, "merged.sarif")
274+
275+
// Merge all SARIF outputs
276+
if err := utils.MergeSarifOutputs(sarifOutputs, tmpOutputFile); err != nil {
277+
log.Fatalf("Failed to merge SARIF outputs: %v", err)
278+
}
279+
280+
if outputFile != "" {
281+
// copy tmpOutputFile to outputFile
282+
content, err := os.ReadFile(tmpOutputFile)
283+
if err != nil {
284+
log.Fatalf("Failed to read merged SARIF output: %v", err)
285+
}
286+
os.WriteFile(outputFile, content, utils.DefaultRW)
287+
} else {
288+
// println the output file content
289+
content, err := os.ReadFile(tmpOutputFile)
290+
if err != nil {
291+
log.Fatalf("Failed to read merged SARIF output: %v", err)
292+
}
293+
fmt.Println(string(content))
294+
}
295+
} else {
296+
// Run tools without merging outputs
297+
for toolName := range toolsToRun {
298+
log.Printf("Running %s...\n", toolName)
299+
runTool(workDirectory, toolName, args, outputFile)
300+
}
256301
}
257302
},
258303
}
304+
305+
func runTool(workDirectory string, toolName string, args []string, outputFile string) {
306+
switch toolName {
307+
case "eslint":
308+
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
309+
case "trivy":
310+
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
311+
case "pmd":
312+
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
313+
case "pylint":
314+
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
315+
default:
316+
log.Printf("Warning: Unsupported tool: %s\n", toolName)
317+
}
318+
}

cmd/init.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ var initFlags InitFlags
3232

3333
func init() {
3434
initCmd.Flags().StringVar(&initFlags.apiToken, "api-token", "", "optional codacy api token, if defined configurations will be fetched from codacy")
35-
initCmd.Flags().StringVar(&initFlags.provider, "provider", "", "optional provider (gh/bb/gl), if defined configurations will be fetched from codacy")
36-
initCmd.Flags().StringVar(&initFlags.organization, "organization", "", "optional remote organization name, if defined configurations will be fetched from codacy")
37-
initCmd.Flags().StringVar(&initFlags.repository, "repository", "", "optional remote repository name, if defined configurations will be fetched from codacy")
35+
initCmd.Flags().StringVar(&initFlags.provider, "provider", "", "provider (gh/bb/gl) to fetch configurations from codacy, required when api-token is provided")
36+
initCmd.Flags().StringVar(&initFlags.organization, "organization", "", "remote organization name to fetch configurations from codacy, required when api-token is provided")
37+
initCmd.Flags().StringVar(&initFlags.repository, "repository", "", "remote repository name to fetch configurations from codacy, required when api-token is provided")
3838
rootCmd.AddCommand(initCmd)
3939
}
4040

@@ -339,19 +339,20 @@ func createDefaultPylintConfigFile() error {
339339

340340
func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
341341
pmdConfigurationString := tools.CreatePmdConfig(config)
342-
return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultRW)
342+
return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
343343
}
344344

345345
func createDefaultPMDConfigFile(toolsConfigDir string) error {
346346
content := tools.CreatePmdConfig([]domain.PatternConfiguration{})
347-
return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(content), utils.DefaultRW)
347+
return os.WriteFile(filepath.Join(toolsConfigDir, "pmd-ruleset.xml"), []byte(content), utils.DefaultFilePerms)
348348
}
349349

350350
// createTrivyConfigFile creates a trivy.yaml configuration file based on the API configuration
351351
func createTrivyConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
352352
trivyConfigurationString := tools.CreateTrivyConfig(config)
353353

354-
return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(trivyConfigurationString), utils.DefaultRW)
354+
// Write to file
355+
return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(trivyConfigurationString), utils.DefaultFilePerms)
355356
}
356357

357358
// createDefaultTrivyConfigFile creates a default trivy.yaml configuration file
@@ -361,7 +362,7 @@ func createDefaultTrivyConfigFile(toolsConfigDir string) error {
361362
content := tools.CreateTrivyConfig(emptyConfig)
362363

363364
// Write to file
364-
return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(content), utils.DefaultRW)
365+
return os.WriteFile(filepath.Join(toolsConfigDir, "trivy.yaml"), []byte(content), utils.DefaultFilePerms)
365366
}
366367

367368
// createDefaultEslintConfigFile creates a default eslint.config.mjs configuration file
@@ -371,7 +372,7 @@ func createDefaultEslintConfigFile(toolsConfigDir string) error {
371372
content := tools.CreateEslintConfig(emptyConfig)
372373

373374
// Write to file
374-
return os.WriteFile(filepath.Join(toolsConfigDir, "eslint.config.mjs"), []byte(content), utils.DefaultRW)
375+
return os.WriteFile(filepath.Join(toolsConfigDir, "eslint.config.mjs"), []byte(content), utils.DefaultFilePerms)
375376
}
376377

377378
const (

cmd/root.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ package cmd
33
import (
44
"fmt"
55
"os"
6+
"path/filepath"
67

78
"github.com/fatih/color"
89
"github.com/spf13/cobra"
910
)
1011

1112
var rootCmd = &cobra.Command{
12-
Use: "codacy-cli",
13-
Short: "Codacy CLI - A command line interface for Codacy",
14-
Long: ``,
13+
Use: "codacy-cli",
14+
Short: "Codacy CLI - A command line interface for Codacy",
15+
Long: "",
16+
Example: getExampleText(),
1517
Run: func(cmd *cobra.Command, args []string) {
1618
// Check if .codacy directory exists
1719
if _, err := os.Stat(".codacy"); os.IsNotExist(err) {
@@ -52,4 +54,61 @@ func showWelcomeMessage() {
5254
fmt.Println()
5355
fmt.Println("Or run without a token to use local configuration:")
5456
fmt.Println(" codacy-cli init")
57+
fmt.Println()
58+
fmt.Println("For more information about available commands, run:")
59+
fmt.Println(" codacy-cli --help")
60+
}
61+
62+
func getExampleText() string {
63+
return color.New(color.FgCyan).Sprint("Initialize a project:") + "\n" +
64+
color.New(color.FgGreen).Sprint(" codacy-cli init") + "\n\n" +
65+
color.New(color.FgCyan).Sprint("Install required tools:") + "\n" +
66+
color.New(color.FgGreen).Sprint(" codacy-cli install") + "\n\n" +
67+
color.New(color.FgCyan).Sprint("Run analysis with ESLint:") + "\n" +
68+
color.New(color.FgGreen).Sprint(" codacy-cli analyze --tool eslint") + "\n\n" +
69+
color.New(color.FgCyan).Sprint("Run analysis and output in SARIF format:") + "\n" +
70+
color.New(color.FgGreen).Sprint(" codacy-cli analyze --tool eslint --format sarif") + "\n\n" +
71+
color.New(color.FgCyan).Sprint("Upload results to Codacy:") + "\n" +
72+
color.New(color.FgGreen).Sprint(" codacy-cli upload -s results.sarif -c <commit-uuid> -t <project-token>")
73+
}
74+
75+
func init() {
76+
// Add global flags here
77+
rootCmd.PersistentFlags().StringP("config", "c", filepath.Join(".codacy", "codacy.yaml"), "config file")
78+
79+
// Customize help template
80+
rootCmd.SetUsageTemplate(`
81+
` + color.New(color.FgCyan).Sprint("Usage:") + `
82+
{{.UseLine}}{{if .HasAvailableSubCommands}}
83+
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
84+
85+
` + color.New(color.FgCyan).Sprint("Aliases:") + `
86+
{{.NameAndAliases}}{{end}}{{if .HasExample}}
87+
88+
` + color.New(color.FgCyan).Sprint("Examples:") + `
89+
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
90+
91+
` + color.New(color.FgCyan).Sprint("Available Commands:") + `{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
92+
` + "{{$cmd := .Name}}" + color.New(color.FgGreen).Sprintf("{{rpad .Name .NamePadding}}") + ` {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
93+
94+
` + color.New(color.FgCyan).Sprint("Flags:") + `
95+
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
96+
97+
` + color.New(color.FgCyan).Sprint("Global Flags:") + `
98+
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
99+
100+
` + color.New(color.FgCyan).Sprint("Additional help topics:") + `{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
101+
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
102+
103+
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
104+
105+
` + color.New(color.FgCyan).Sprint("Configuration Example") + ` (.codacy/codacy.yaml):
106+
runtimes:
107+
108+
tools:
109+
110+
111+
` + color.New(color.FgCyan).Sprint("For more information and examples, visit:") + `
112+
https://github.com/codacy/codacy-cli-v2
113+
`)
55114
}

config/config.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88

99
"codacy/cli-v2/plugins"
10+
"codacy/cli-v2/utils"
1011
)
1112

1213
type ConfigType struct {
@@ -109,22 +110,22 @@ func (c *ConfigType) setupCodacyPaths() {
109110
}
110111

111112
func (c *ConfigType) CreateCodacyDirs() error {
112-
if err := os.MkdirAll(c.globalCacheDirectory, 0777); err != nil {
113+
if err := os.MkdirAll(c.globalCacheDirectory, utils.DefaultDirPerms); err != nil {
113114
return fmt.Errorf("failed to create codacy directory: %w", err)
114115
}
115116

116-
if err := os.MkdirAll(c.runtimesDirectory, 0777); err != nil {
117+
if err := os.MkdirAll(c.runtimesDirectory, utils.DefaultDirPerms); err != nil {
117118
return fmt.Errorf("failed to create runtimes directory: %w", err)
118119
}
119120

120-
if err := os.MkdirAll(c.toolsDirectory, 0777); err != nil {
121+
if err := os.MkdirAll(c.toolsDirectory, utils.DefaultDirPerms); err != nil {
121122
return fmt.Errorf("failed to create tools directory: %w", err)
122123
}
123124
return nil
124125
}
125126

126127
func (c *ConfigType) CreateLocalCodacyDir() error {
127-
if err := os.MkdirAll(c.localCodacyDirectory, 0777); err != nil {
128+
if err := os.MkdirAll(c.localCodacyDirectory, utils.DefaultDirPerms); err != nil {
128129
return fmt.Errorf("failed to create local codacy directory: %w", err)
129130
}
130131
return nil

config/runtimes-installer_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"codacy/cli-v2/plugins"
5+
"codacy/cli-v2/utils"
56
"os"
67
"path/filepath"
78
"testing"
@@ -26,7 +27,7 @@ func TestIsRuntimeInstalled(t *testing.T) {
2627
assert.False(t, isRuntimeInstalled(runtimeInfoNoBinaries))
2728

2829
// Create the install directory
29-
err = os.MkdirAll(runtimeInfoNoBinaries.InstallDir, 0755)
30+
err = os.MkdirAll(runtimeInfoNoBinaries.InstallDir, utils.DefaultDirPerms)
3031
assert.NoError(t, err)
3132

3233
// Test when the install directory exists
@@ -52,4 +53,4 @@ func TestIsRuntimeInstalled(t *testing.T) {
5253

5354
// Test when the binary exists
5455
assert.True(t, isRuntimeInstalled(runtimeInfoWithBinaries))
55-
}
56+
}

0 commit comments

Comments
 (0)