Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codacy/codacy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ runtimes:
tools:
- [email protected]
- [email protected]
- [email protected]
- [email protected]
- [email protected]
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
27 changes: 21 additions & 6 deletions cli-v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@ package main
import (
"codacy/cli-v2/cmd"
"codacy/cli-v2/config"
cfg "codacy/cli-v2/config-file"
config_file "codacy/cli-v2/config-file"
"fmt"
"os"
)

func main() {
fmt.Println("Running original CLI functionality...")
// Original functionality
// Initialize config global object
config.Init()

configErr := cfg.ReadConfigFile(config.Config.ProjectConfigFile())
// whenever there is no configuration file, the only command allowed to run is the 'init'
if configErr != nil && len(os.Args) > 1 && os.Args[1] != "init" {
// This also setup the config global !
configErr := config_file.ReadConfigFile(config.Config.ProjectConfigFile())

// Show help if any argument contains help
for _, arg := range os.Args {
if arg == "--help" || arg == "-h" || arg == "help" {
cmd.Execute()
return
}
}

// Check if command is init
if len(os.Args) > 1 && os.Args[1] == "init" {
cmd.Execute()
return
}

// All other commands require a configuration file
if configErr != nil && len(os.Args) > 1 {
fmt.Println("No configuration file was found, execute init command first.")
return
}
Expand Down
19 changes: 15 additions & 4 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ var initCmd = &cobra.Command{
Short: "Bootstraps project configuration",
Long: "Bootstraps project configuration, creates codacy configuration file",
Run: func(cmd *cobra.Command, args []string) {

config.Config.CreateLocalCodacyDir()

if len(codacyRepositoryToken) == 0 {
fmt.Println("No project token was specified, skipping fetch configurations ")
fmt.Println()
fmt.Println("ℹ️ No project token was specified, skipping fetch configurations")
noTools := []tools.Tool{}
err := createConfigurationFile(noTools)
if err != nil {
Expand All @@ -50,7 +54,13 @@ var initCmd = &cobra.Command{
log.Fatal(err)
}
}
fmt.Println("Run install command to install dependencies.")
fmt.Println()
fmt.Println("βœ… Successfully initialized Codacy configuration!")
fmt.Println()
fmt.Println("πŸ”§ Next steps:")
fmt.Println(" 1. Run 'codacy-cli install' to install all dependencies")
fmt.Println(" 2. Run 'codacy-cli analyze' to start analyzing your code")
fmt.Println()
},
}

Expand All @@ -76,7 +86,7 @@ func configFileTemplate(tools []tools.Tool) string {
eslintVersion := "9.3.0"
trivyVersion := "0.59.1" // Latest stable version
pylintVersion := "3.3.6"

pmdVersion := "7.12.0"
for _, tool := range tools {
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
eslintVersion = tool.Version
Expand All @@ -96,7 +106,8 @@ tools:
- eslint@%s
- trivy@%s
- pylint@%s
`, eslintVersion, trivyVersion, pylintVersion)
- pmd@%s
`, eslintVersion, trivyVersion, pylintVersion, pmdVersion)
}

func buildRepositoryConfigurationFiles(token string) error {
Expand Down
160 changes: 157 additions & 3 deletions cmd/install.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package cmd

import (
"codacy/cli-v2/config"
cfg "codacy/cli-v2/config"
config_file "codacy/cli-v2/config-file"
"fmt"
"io"
"log"
"os"
"time"

"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
"github.com/spf13/cobra"
)

Expand All @@ -19,8 +27,154 @@ var installCmd = &cobra.Command{
Short: "Installs the tools specified in the project's config-file.",
Long: "Installs all runtimes and tools specified in the project's config-file file.",
Run: func(cmd *cobra.Command, args []string) {
installRuntimes(&cfg.Config)
installTools(&cfg.Config)
bold := color.New(color.Bold)
green := color.New(color.FgGreen)

// Create necessary directories
if err := config.Config.CreateCodacyDirs(); err != nil {
log.Fatal(err)
}

// Load config file
if err := config_file.ReadConfigFile(cfg.Config.ProjectConfigFile()); err != nil {
fmt.Println()
color.Red("⚠️ Warning: Could not find configuration file!")
fmt.Println("Please run 'codacy-cli init' first to create a configuration file.")
fmt.Println()
os.Exit(1)
}

// Check if anything needs to be installed
needsInstallation := false
for name, runtime := range cfg.Config.Runtimes() {
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
needsInstallation = true
break
}
}
if !needsInstallation {
for name, tool := range cfg.Config.Tools() {
if !cfg.Config.IsToolInstalled(name, tool) {
needsInstallation = true
break
}
}
}

if !needsInstallation {
fmt.Println()
bold.Println("βœ… All components are already installed!")
return
}

fmt.Println()
bold.Println("πŸš€ Starting installation process...")
fmt.Println()

// Calculate total items to install
totalItems := 0
for name, runtime := range cfg.Config.Runtimes() {
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
totalItems++
}
}
for name, tool := range cfg.Config.Tools() {
if !cfg.Config.IsToolInstalled(name, tool) {
totalItems++
}
}

if totalItems == 0 {
fmt.Println()
bold.Println("βœ… All components are already installed!")
return
}

// Print list of items to install
fmt.Println("πŸ“¦ Items to install:")
for name, runtime := range cfg.Config.Runtimes() {
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
fmt.Printf(" β€’ Runtime: %s v%s\n", name, runtime.Version)
}
}
for name, tool := range cfg.Config.Tools() {
if !cfg.Config.IsToolInstalled(name, tool) {
fmt.Printf(" β€’ Tool: %s v%s\n", name, tool.Version)
}
}
fmt.Println()

// Create a single progress bar for the entire installation
progressBar := progressbar.NewOptions(totalItems,
progressbar.OptionSetDescription("Installing components..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "β–ˆ",
SaucerHead: "β–ˆ",
SaucerPadding: "β–‘",
BarStart: "β”‚",
BarEnd: "β”‚",
}),
progressbar.OptionShowCount(),
progressbar.OptionShowIts(),
progressbar.OptionSetWidth(50),
progressbar.OptionThrottle(100*time.Millisecond),
progressbar.OptionSpinnerType(14),
progressbar.OptionFullWidth(),
progressbar.OptionSetRenderBlankState(true),
progressbar.OptionOnCompletion(func() {
fmt.Println()
}),
)

// Redirect all output to /dev/null during installation
oldStdout := os.Stdout
devNull, _ := os.Open(os.DevNull)
os.Stdout = devNull
log.SetOutput(io.Discard)

// Install runtimes first
for name, runtime := range cfg.Config.Runtimes() {
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
progressBar.Describe(fmt.Sprintf("Installing runtime: %s v%s...", name, runtime.Version))
err := cfg.InstallRuntime(name, runtime)
if err != nil {
log.Fatal(err)
}
progressBar.Add(1)
}
}

// Install tools
for name, tool := range cfg.Config.Tools() {
if !cfg.Config.IsToolInstalled(name, tool) {
progressBar.Describe(fmt.Sprintf("Installing tool: %s v%s...", name, tool.Version))
err := cfg.InstallTool(name, tool)
if err != nil {
log.Fatal(err)
}
progressBar.Add(1)
}
}

// Restore output
os.Stdout = oldStdout
devNull.Close()
log.SetOutput(os.Stderr)

// Print completion status
fmt.Println()
for name, runtime := range cfg.Config.Runtimes() {
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
green.Printf(" βœ“ Runtime: %s v%s\n", name, runtime.Version)
}
}
for name, tool := range cfg.Config.Tools() {
if !cfg.Config.IsToolInstalled(name, tool) {
green.Printf(" βœ“ Tool: %s v%s\n", name, tool.Version)
}
}
fmt.Println()
bold.Println("βœ… Installation completed successfully!")
},
}

Expand All @@ -37,4 +191,4 @@ func installTools(config *cfg.ConfigType) {
if err != nil {
log.Fatal(err)
}
}
}
35 changes: 33 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import (
"fmt"
"os"

"github.com/fatih/color"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "codacy-cli",
Short: "Codacy CLI",
Long: "Code analysis",
Short: "Codacy CLI - A command line interface for Codacy",
Long: ``,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what? An empty line or?

Run: func(cmd *cobra.Command, args []string) {
// Check if .codacy directory exists
if _, err := os.Stat(".codacy"); os.IsNotExist(err) {
// Show welcome message if .codacy doesn't exist
showWelcomeMessage()
return
}

// If .codacy exists, show regular help
cmd.Help()
},
}

Expand All @@ -21,3 +31,24 @@ func Execute() {
os.Exit(1)
}
}

func showWelcomeMessage() {
bold := color.New(color.Bold)
cyan := color.New(color.FgCyan)
yellow := color.New(color.FgYellow)

fmt.Println()
bold.Println("πŸ‘‹ Welcome to Codacy CLI!")
fmt.Println()
fmt.Println("This tool helps you analyze and maintain code quality in your projects.")
fmt.Println()
yellow.Println("To get started, you'll need a Codacy API token.")
fmt.Println("You can find your Project API token in Codacy under:")
fmt.Println("Project > Settings > Integrations > Repository API tokens")
Comment on lines +45 to +47

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use API token instead of Codacy API token. And then also expose the possibility of doing it with account API tokens as well, and specifying the URL directly.

This variant would be faster and more user friendly. To my understanding, it works with both types of API tokens, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I also was thinking just go with "general" API token, not the project one.
Just for simplicity now and create more limited token in Future. I think the IDE plugin is using API token anway....
wdyt @machadoit

Anyway this will be resolved in another PR - this one is more UI specfifc - there is another Task for making setup flow easier

Copy link
Contributor

@machadoit machadoit Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API token instead of Codacy API token

Sorry what? πŸ˜… Now it works with repository-token, and you are talking about the Account API token correct?

Yeah, if it is a requirement for the MCP, we can add a task to add support for it (but agree that is out of scope of this PR)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt.Println()
cyan.Println("Initialize your project with:")
fmt.Println(" codacy-cli init --repository-token YOUR_TOKEN")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --repository-token option is not specified in the README.md. Would this be a part of the scope of this improvement, or will it be treated within https://codacy.atlassian.net/browse/PLUTO-1376 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing missing is not just the --repository-token but the full init command and its options. Can be either here or later, as @andrzej-janczak prefer. (I will add the note on the task anyway)

fmt.Println()
fmt.Println("Or run without a token to use local configuration:")
fmt.Println(" codacy-cli init")
}
Loading