Skip to content

Commit 096db1b

Browse files
Feature/improve ux (#50)
* feature: improve starting from zero UX * ux: update API token instructions in welcome message * chore: update GitHub Actions workflow to initialize cli-v2 and remove Windows support * feature: installation progress bar * feature: enhance installation checks and user feedback * refactor: update configuration handling and directory creation
1 parent 8bc9720 commit 096db1b

File tree

11 files changed

+335
-49
lines changed

11 files changed

+335
-49
lines changed

.codacy/codacy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ runtimes:
44
tools:
55
66
7-
87
8+

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
runs-on: ${{ matrix.os }}
5555
strategy:
5656
matrix:
57-
os: [ubuntu-latest, windows-latest, macos-latest]
57+
os: [ubuntu-latest, macos-latest]
5858
steps:
5959
- name: Checkout
6060
uses: actions/checkout@v4

cli-v2.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,34 @@ package main
33
import (
44
"codacy/cli-v2/cmd"
55
"codacy/cli-v2/config"
6-
cfg "codacy/cli-v2/config-file"
6+
config_file "codacy/cli-v2/config-file"
77
"fmt"
88
"os"
99
)
1010

1111
func main() {
12-
fmt.Println("Running original CLI functionality...")
13-
// Original functionality
12+
// Initialize config global object
1413
config.Init()
1514

16-
configErr := cfg.ReadConfigFile(config.Config.ProjectConfigFile())
17-
// whenever there is no configuration file, the only command allowed to run is the 'init'
18-
if configErr != nil && len(os.Args) > 1 && os.Args[1] != "init" {
15+
// This also setup the config global !
16+
configErr := config_file.ReadConfigFile(config.Config.ProjectConfigFile())
17+
18+
// Show help if any argument contains help
19+
for _, arg := range os.Args {
20+
if arg == "--help" || arg == "-h" || arg == "help" {
21+
cmd.Execute()
22+
return
23+
}
24+
}
25+
26+
// Check if command is init
27+
if len(os.Args) > 1 && os.Args[1] == "init" {
28+
cmd.Execute()
29+
return
30+
}
31+
32+
// All other commands require a configuration file
33+
if configErr != nil && len(os.Args) > 1 {
1934
fmt.Println("No configuration file was found, execute init command first.")
2035
return
2136
}

cmd/init.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ var initCmd = &cobra.Command{
2929
Short: "Bootstraps project configuration",
3030
Long: "Bootstraps project configuration, creates codacy configuration file",
3131
Run: func(cmd *cobra.Command, args []string) {
32+
33+
config.Config.CreateLocalCodacyDir()
34+
3235
if len(codacyRepositoryToken) == 0 {
33-
fmt.Println("No project token was specified, skipping fetch configurations ")
36+
fmt.Println()
37+
fmt.Println("ℹ️ No project token was specified, skipping fetch configurations")
3438
noTools := []tools.Tool{}
3539
err := createConfigurationFile(noTools)
3640
if err != nil {
@@ -50,7 +54,13 @@ var initCmd = &cobra.Command{
5054
log.Fatal(err)
5155
}
5256
}
53-
fmt.Println("Run install command to install dependencies.")
57+
fmt.Println()
58+
fmt.Println("✅ Successfully initialized Codacy configuration!")
59+
fmt.Println()
60+
fmt.Println("🔧 Next steps:")
61+
fmt.Println(" 1. Run 'codacy-cli install' to install all dependencies")
62+
fmt.Println(" 2. Run 'codacy-cli analyze' to start analyzing your code")
63+
fmt.Println()
5464
},
5565
}
5666

@@ -76,7 +86,7 @@ func configFileTemplate(tools []tools.Tool) string {
7686
eslintVersion := "9.3.0"
7787
trivyVersion := "0.59.1" // Latest stable version
7888
pylintVersion := "3.3.6"
79-
89+
pmdVersion := "7.12.0"
8090
for _, tool := range tools {
8191
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
8292
eslintVersion = tool.Version
@@ -96,7 +106,8 @@ tools:
96106
- eslint@%s
97107
- trivy@%s
98108
- pylint@%s
99-
`, eslintVersion, trivyVersion, pylintVersion)
109+
- pmd@%s
110+
`, eslintVersion, trivyVersion, pylintVersion, pmdVersion)
100111
}
101112

102113
func buildRepositoryConfigurationFiles(token string) error {

cmd/install.go

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package cmd
22

33
import (
4+
"codacy/cli-v2/config"
45
cfg "codacy/cli-v2/config"
6+
config_file "codacy/cli-v2/config-file"
7+
"fmt"
8+
"io"
59
"log"
10+
"os"
11+
"time"
612

13+
"github.com/fatih/color"
14+
"github.com/schollz/progressbar/v3"
715
"github.com/spf13/cobra"
816
)
917

@@ -19,8 +27,154 @@ var installCmd = &cobra.Command{
1927
Short: "Installs the tools specified in the project's config-file.",
2028
Long: "Installs all runtimes and tools specified in the project's config-file file.",
2129
Run: func(cmd *cobra.Command, args []string) {
22-
installRuntimes(&cfg.Config)
23-
installTools(&cfg.Config)
30+
bold := color.New(color.Bold)
31+
green := color.New(color.FgGreen)
32+
33+
// Create necessary directories
34+
if err := config.Config.CreateCodacyDirs(); err != nil {
35+
log.Fatal(err)
36+
}
37+
38+
// Load config file
39+
if err := config_file.ReadConfigFile(cfg.Config.ProjectConfigFile()); err != nil {
40+
fmt.Println()
41+
color.Red("⚠️ Warning: Could not find configuration file!")
42+
fmt.Println("Please run 'codacy-cli init' first to create a configuration file.")
43+
fmt.Println()
44+
os.Exit(1)
45+
}
46+
47+
// Check if anything needs to be installed
48+
needsInstallation := false
49+
for name, runtime := range cfg.Config.Runtimes() {
50+
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
51+
needsInstallation = true
52+
break
53+
}
54+
}
55+
if !needsInstallation {
56+
for name, tool := range cfg.Config.Tools() {
57+
if !cfg.Config.IsToolInstalled(name, tool) {
58+
needsInstallation = true
59+
break
60+
}
61+
}
62+
}
63+
64+
if !needsInstallation {
65+
fmt.Println()
66+
bold.Println("✅ All components are already installed!")
67+
return
68+
}
69+
70+
fmt.Println()
71+
bold.Println("🚀 Starting installation process...")
72+
fmt.Println()
73+
74+
// Calculate total items to install
75+
totalItems := 0
76+
for name, runtime := range cfg.Config.Runtimes() {
77+
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
78+
totalItems++
79+
}
80+
}
81+
for name, tool := range cfg.Config.Tools() {
82+
if !cfg.Config.IsToolInstalled(name, tool) {
83+
totalItems++
84+
}
85+
}
86+
87+
if totalItems == 0 {
88+
fmt.Println()
89+
bold.Println("✅ All components are already installed!")
90+
return
91+
}
92+
93+
// Print list of items to install
94+
fmt.Println("📦 Items to install:")
95+
for name, runtime := range cfg.Config.Runtimes() {
96+
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
97+
fmt.Printf(" • Runtime: %s v%s\n", name, runtime.Version)
98+
}
99+
}
100+
for name, tool := range cfg.Config.Tools() {
101+
if !cfg.Config.IsToolInstalled(name, tool) {
102+
fmt.Printf(" • Tool: %s v%s\n", name, tool.Version)
103+
}
104+
}
105+
fmt.Println()
106+
107+
// Create a single progress bar for the entire installation
108+
progressBar := progressbar.NewOptions(totalItems,
109+
progressbar.OptionSetDescription("Installing components..."),
110+
progressbar.OptionSetTheme(progressbar.Theme{
111+
Saucer: "█",
112+
SaucerHead: "█",
113+
SaucerPadding: "░",
114+
BarStart: "│",
115+
BarEnd: "│",
116+
}),
117+
progressbar.OptionShowCount(),
118+
progressbar.OptionShowIts(),
119+
progressbar.OptionSetWidth(50),
120+
progressbar.OptionThrottle(100*time.Millisecond),
121+
progressbar.OptionSpinnerType(14),
122+
progressbar.OptionFullWidth(),
123+
progressbar.OptionSetRenderBlankState(true),
124+
progressbar.OptionOnCompletion(func() {
125+
fmt.Println()
126+
}),
127+
)
128+
129+
// Redirect all output to /dev/null during installation
130+
oldStdout := os.Stdout
131+
devNull, _ := os.Open(os.DevNull)
132+
os.Stdout = devNull
133+
log.SetOutput(io.Discard)
134+
135+
// Install runtimes first
136+
for name, runtime := range cfg.Config.Runtimes() {
137+
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
138+
progressBar.Describe(fmt.Sprintf("Installing runtime: %s v%s...", name, runtime.Version))
139+
err := cfg.InstallRuntime(name, runtime)
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
progressBar.Add(1)
144+
}
145+
}
146+
147+
// Install tools
148+
for name, tool := range cfg.Config.Tools() {
149+
if !cfg.Config.IsToolInstalled(name, tool) {
150+
progressBar.Describe(fmt.Sprintf("Installing tool: %s v%s...", name, tool.Version))
151+
err := cfg.InstallTool(name, tool)
152+
if err != nil {
153+
log.Fatal(err)
154+
}
155+
progressBar.Add(1)
156+
}
157+
}
158+
159+
// Restore output
160+
os.Stdout = oldStdout
161+
devNull.Close()
162+
log.SetOutput(os.Stderr)
163+
164+
// Print completion status
165+
fmt.Println()
166+
for name, runtime := range cfg.Config.Runtimes() {
167+
if !cfg.Config.IsRuntimeInstalled(name, runtime) {
168+
green.Printf(" ✓ Runtime: %s v%s\n", name, runtime.Version)
169+
}
170+
}
171+
for name, tool := range cfg.Config.Tools() {
172+
if !cfg.Config.IsToolInstalled(name, tool) {
173+
green.Printf(" ✓ Tool: %s v%s\n", name, tool.Version)
174+
}
175+
}
176+
fmt.Println()
177+
bold.Println("✅ Installation completed successfully!")
24178
},
25179
}
26180

@@ -37,4 +191,4 @@ func installTools(config *cfg.ConfigType) {
37191
if err != nil {
38192
log.Fatal(err)
39193
}
40-
}
194+
}

cmd/root.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@ import (
44
"fmt"
55
"os"
66

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

1011
var rootCmd = &cobra.Command{
1112
Use: "codacy-cli",
12-
Short: "Codacy CLI",
13-
Long: "Code analysis",
13+
Short: "Codacy CLI - A command line interface for Codacy",
14+
Long: ``,
1415
Run: func(cmd *cobra.Command, args []string) {
16+
// Check if .codacy directory exists
17+
if _, err := os.Stat(".codacy"); os.IsNotExist(err) {
18+
// Show welcome message if .codacy doesn't exist
19+
showWelcomeMessage()
20+
return
21+
}
22+
23+
// If .codacy exists, show regular help
24+
cmd.Help()
1525
},
1626
}
1727

@@ -21,3 +31,24 @@ func Execute() {
2131
os.Exit(1)
2232
}
2333
}
34+
35+
func showWelcomeMessage() {
36+
bold := color.New(color.Bold)
37+
cyan := color.New(color.FgCyan)
38+
yellow := color.New(color.FgYellow)
39+
40+
fmt.Println()
41+
bold.Println("👋 Welcome to Codacy CLI!")
42+
fmt.Println()
43+
fmt.Println("This tool helps you analyze and maintain code quality in your projects.")
44+
fmt.Println()
45+
yellow.Println("To get started, you'll need a Codacy API token.")
46+
fmt.Println("You can find your Project API token in Codacy under:")
47+
fmt.Println("Project > Settings > Integrations > Repository API tokens")
48+
fmt.Println()
49+
cyan.Println("Initialize your project with:")
50+
fmt.Println(" codacy-cli init --repository-token YOUR_TOKEN")
51+
fmt.Println()
52+
fmt.Println("Or run without a token to use local configuration:")
53+
fmt.Println(" codacy-cli init")
54+
}

0 commit comments

Comments
 (0)