1+ package config
2+
3+ import (
4+ "bytes"
5+ "codacy/cli-v2/plugins"
6+ "fmt"
7+ "log"
8+ "os"
9+ "os/exec"
10+ "path"
11+ "strings"
12+ "text/template"
13+ )
14+
15+ // InstallTools installs all tools defined in the configuration
16+ func InstallTools () error {
17+ for name , toolInfo := range Config .Tools () {
18+ err := InstallTool (name , toolInfo )
19+ if err != nil {
20+ return fmt .Errorf ("failed to install tool %s: %w" , name , err )
21+ }
22+ }
23+ return nil
24+ }
25+
26+ // InstallTool installs a specific tool
27+ func InstallTool (name string , toolInfo * plugins.ToolInfo ) error {
28+ // Check if the tool is already installed
29+ if isToolInstalled (toolInfo ) {
30+ fmt .Printf ("Tool %s v%s is already installed\n " , name , toolInfo .Version )
31+ return nil
32+ }
33+
34+ // Get the runtime for this tool
35+ runtimeInfo , ok := Config .Runtimes ()[toolInfo .Runtime ]
36+ if ! ok {
37+ return fmt .Errorf ("required runtime %s not found for tool %s" , toolInfo .Runtime , name )
38+ }
39+
40+ // Make sure the installation directory exists
41+ err := os .MkdirAll (toolInfo .InstallDir , 0755 )
42+ if err != nil {
43+ return fmt .Errorf ("failed to create installation directory: %w" , err )
44+ }
45+
46+ // Set registry if provided
47+ if toolInfo .RegistryCommand != "" {
48+ regCmd , err := executeToolTemplate (toolInfo .RegistryCommand , map [string ]string {
49+ "InstallDir" : toolInfo .InstallDir ,
50+ "PackageName" : toolInfo .Name ,
51+ "Version" : toolInfo .Version ,
52+ "Registry" : "" , // TODO: Get registry from config
53+ })
54+ if err != nil {
55+ return fmt .Errorf ("failed to prepare registry command: %w" , err )
56+ }
57+
58+ if regCmd != "" {
59+ // For Node.js tools, use npm to set registry
60+ if toolInfo .Runtime == "node" {
61+ registryCmd := exec .Command (runtimeInfo .Binaries ["npm" ], strings .Split (regCmd , " " )... )
62+ if output , err := registryCmd .CombinedOutput (); err != nil {
63+ return fmt .Errorf ("failed to set registry: %s: %w" , string (output ), err )
64+ }
65+ }
66+ }
67+ }
68+
69+ // Execute installation command
70+ installCmd , err := executeToolTemplate (toolInfo .InstallCommand , map [string ]string {
71+ "InstallDir" : toolInfo .InstallDir ,
72+ "PackageName" : toolInfo .Name ,
73+ "Version" : toolInfo .Version ,
74+ "Registry" : "" , // TODO: Get registry from config
75+ })
76+ if err != nil {
77+ return fmt .Errorf ("failed to prepare install command: %w" , err )
78+ }
79+
80+ // For Node.js tools, use npm to install
81+ if toolInfo .Runtime == "node" {
82+ cmd := exec .Command (runtimeInfo .Binaries ["npm" ], strings .Split (installCmd , " " )... )
83+
84+ log .Printf ("Installing %s v%s...\n " , toolInfo .Name , toolInfo .Version )
85+ output , err := cmd .CombinedOutput ()
86+ if err != nil {
87+ return fmt .Errorf ("failed to install tool: %s: %w" , string (output ), err )
88+ }
89+ }
90+
91+ log .Printf ("Successfully installed %s v%s\n " , toolInfo .Name , toolInfo .Version )
92+ return nil
93+ }
94+
95+ // isToolInstalled checks if a tool is already installed by checking for the binary
96+ func isToolInstalled (toolInfo * plugins.ToolInfo ) bool {
97+ // If there are no binaries, check the install directory
98+ if len (toolInfo .Binaries ) == 0 {
99+ _ , err := os .Stat (toolInfo .InstallDir )
100+ return err == nil
101+ }
102+
103+ // Check if at least one binary exists
104+ for _ , binaryPath := range toolInfo .Binaries {
105+ _ , err := os .Stat (binaryPath )
106+ if err == nil {
107+ return true
108+ }
109+ }
110+
111+ return false
112+ }
113+
114+ // executeToolTemplate executes a template with the given data
115+ func executeToolTemplate (tmplStr string , data map [string ]string ) (string , error ) {
116+ tmpl , err := template .New ("command" ).Parse (tmplStr )
117+ if err != nil {
118+ return "" , err
119+ }
120+
121+ var buf bytes.Buffer
122+ err = tmpl .Execute (& buf , data )
123+ if err != nil {
124+ return "" , err
125+ }
126+
127+ return buf .String (), nil
128+ }
129+
130+ // RunTool runs a tool with the specified options
131+ func RunTool (
132+ toolInfo * plugins.ToolInfo ,
133+ workingDir string ,
134+ pathsToCheck []string ,
135+ autoFix bool ,
136+ outputFile string ,
137+ outputFormat string ,
138+ ) error {
139+ // Get the runtime for this tool
140+ runtimeInfo , ok := Config .Runtimes ()[toolInfo .Runtime ]
141+ if ! ok {
142+ return fmt .Errorf ("required runtime %s not found for tool %s" , toolInfo .Runtime , toolInfo .Name )
143+ }
144+
145+ // Get the primary binary for this tool
146+ binary , ok := toolInfo .Binaries [toolInfo .Name ]
147+ if ! ok {
148+ return fmt .Errorf ("%s binary not found in tool configuration" , toolInfo .Name )
149+ }
150+
151+ // Prepare command based on runtime
152+ // For Node.js, we run the binary with the node executable
153+ cmd := exec .Command (runtimeInfo .Binaries ["node" ], binary )
154+
155+ // Add autofix flag if requested
156+ if autoFix && toolInfo .AutofixFlag != "" {
157+ cmd .Args = append (cmd .Args , strings .Split (toolInfo .AutofixFlag , " " )... )
158+ }
159+
160+ // Add output format if specified
161+ if outputFormat != "" {
162+ if formatFlag , ok := toolInfo .Formatters [outputFormat ]; ok {
163+ cmd .Args = append (cmd .Args , strings .Split (formatFlag , " " )... )
164+ }
165+ }
166+
167+ // Add output file if specified
168+ if outputFile != "" && toolInfo .OutputFlag != "" {
169+ cmd .Args = append (cmd .Args , toolInfo .OutputFlag , outputFile )
170+ }
171+
172+ // Add paths to check
173+ if len (pathsToCheck ) > 0 {
174+ cmd .Args = append (cmd .Args , pathsToCheck ... )
175+ } else if toolInfo .DefaultPath != "" {
176+ cmd .Args = append (cmd .Args , toolInfo .DefaultPath )
177+ }
178+
179+ // Set working directory
180+ cmd .Dir = workingDir
181+
182+ // Set environment variables based on runtime
183+ // For Node.js, set NODE_PATH
184+ if toolInfo .Runtime == "node" {
185+ nodeModulesPath := path .Join (toolInfo .InstallDir , "node_modules" )
186+ cmd .Env = append (cmd .Env , "NODE_PATH=" + nodeModulesPath )
187+ }
188+
189+ // Run the command
190+ cmd .Stdout = os .Stdout
191+ cmd .Stderr = os .Stderr
192+
193+ err := cmd .Run ()
194+ if err != nil {
195+ // Handle expected error cases based on the tool
196+ // For ESLint, it returns exit code 1 when it finds issues, which is normal
197+ if toolInfo .Name == "eslint" && strings .Contains (err .Error (), "exit status 1" ) {
198+ return nil
199+ }
200+ return fmt .Errorf ("error running tool: %w" , err )
201+ }
202+
203+ return nil
204+ }
0 commit comments