@@ -3,6 +3,7 @@ package cmd
33import (
44 "codacy/cli-v2/config"
55 "codacy/cli-v2/domain"
6+ "codacy/cli-v2/plugins"
67 "codacy/cli-v2/tools"
78 "codacy/cli-v2/tools/lizard"
89 "codacy/cli-v2/tools/pylint"
@@ -15,10 +16,12 @@ import (
1516 "net/http"
1617 "os"
1718 "path/filepath"
19+ "sort"
1820 "strings"
1921 "time"
2022
2123 "github.com/spf13/cobra"
24+ "gopkg.in/yaml.v3"
2225)
2326
2427const CodacyApiBase = "https://app.codacy.com"
@@ -131,16 +134,69 @@ func createConfigurationFiles(tools []tools.Tool, cliLocalMode bool) error {
131134 return nil
132135}
133136
137+ // Map tool UUIDs to their names
138+ var toolNameMap = map [string ]string {
139+ ESLint : "eslint" ,
140+ Trivy : "trivy" ,
141+ PyLint : "pylint" ,
142+ PMD : "pmd" ,
143+ DartAnalyzer : "dartanalyzer" ,
144+ Semgrep : "semgrep" ,
145+ Lizard : "lizard" ,
146+ }
147+
148+ // RuntimePluginConfig holds the structure of the runtime plugin.yaml file
149+ type RuntimePluginConfig struct {
150+ Name string `yaml:"name"`
151+ Description string `yaml:"description"`
152+ DefaultVersion string `yaml:"default_version"`
153+ }
154+
155+ func getRuntimeVersions () map [string ]string {
156+ versions := make (map [string ]string )
157+
158+ // Read the runtimes directory
159+ entries , err := os .ReadDir ("plugins/runtimes" )
160+ if err != nil {
161+ log .Printf ("Warning: Could not read runtimes directory: %v" , err )
162+ return versions
163+ }
164+
165+ // Process each runtime directory
166+ for _ , entry := range entries {
167+ if ! entry .IsDir () {
168+ continue
169+ }
170+
171+ runtime := entry .Name ()
172+ pluginPath := fmt .Sprintf ("plugins/runtimes/%s/plugin.yaml" , runtime )
173+ data , err := os .ReadFile (pluginPath )
174+ if err != nil {
175+ log .Printf ("Warning: Could not read plugin file for runtime %s: %v" , runtime , err )
176+ continue
177+ }
178+
179+ var config RuntimePluginConfig
180+ if err := yaml .Unmarshal (data , & config ); err != nil {
181+ log .Printf ("Warning: Could not parse plugin file for runtime %s: %v" , runtime , err )
182+ continue
183+ }
184+
185+ if config .DefaultVersion != "" {
186+ versions [runtime ] = config .DefaultVersion
187+ }
188+ }
189+
190+ return versions
191+ }
192+
134193func configFileTemplate (tools []tools.Tool ) string {
135194 // Maps to track which tools are enabled
136195 toolsMap := make (map [string ]bool )
137196 toolVersions := make (map [string ]string )
138197
139198 // Track needed runtimes
140- needsNode := false
141- needsPython := false
142- needsDart := false
143- needsJava := false
199+ neededRuntimes := make (map [string ]bool )
144200
145201 // Default versions
146202 defaultVersions := map [string ]string {
@@ -153,6 +209,9 @@ func configFileTemplate(tools []tools.Tool) string {
153209 Lizard : "1.17.19" ,
154210 }
155211
212+ // Get runtime versions all at once
213+ runtimeVersions := getRuntimeVersions ()
214+
156215 // Build map of enabled tools with their versions
157216 for _ , tool := range tools {
158217 toolsMap [tool .Uuid ] = true
@@ -161,16 +220,48 @@ func configFileTemplate(tools []tools.Tool) string {
161220 } else {
162221 toolVersions [tool .Uuid ] = defaultVersions [tool .Uuid ]
163222 }
223+ }
224+
225+ // Convert tools to ToolConfig format
226+ var toolConfigs []plugins.ToolConfig
227+ for _ , tool := range tools {
228+ toolName := toolNameMap [tool .Uuid ]
229+ if toolName == "" {
230+ log .Printf ("Warning: Unknown tool UUID %s" , tool .Uuid )
231+ continue
232+ }
233+
234+ version := tool .Version
235+ if version == "" {
236+ version = defaultVersions [tool .Uuid ]
237+ }
238+
239+ toolConfigs = append (toolConfigs , plugins.ToolConfig {
240+ Name : toolName ,
241+ Version : version ,
242+ })
243+ }
244+
245+ // Process tools to get their configurations
246+ runtimeInfos := make (map [string ]* plugins.RuntimeInfo )
247+ for runtime , version := range runtimeVersions {
248+ runtimeInfo , err := processRuntime (runtime , version )
249+ if err != nil {
250+ log .Printf ("Warning: Failed to process runtime %s: %v" , runtime , err )
251+ continue
252+ }
253+ runtimeInfos [runtime ] = runtimeInfo
254+ }
164255
165- // Check if tool needs a runtime
166- if tool . Uuid == ESLint {
167- needsNode = true
168- } else if tool . Uuid == PyLint || tool . Uuid == Lizard {
169- needsPython = true
170- } else if tool . Uuid == DartAnalyzer {
171- needsDart = true
172- } else if tool . Uuid == PMD {
173- needsJava = true
256+ toolInfos , err := plugins . ProcessTools ( toolConfigs , os . TempDir (), runtimeInfos )
257+ if err != nil {
258+ log . Printf ( "Warning: Failed to process tool configurations: %v" , err )
259+ } else {
260+ // Get required runtimes from tool configurations
261+ for _ , info := range toolInfos {
262+ if info . Runtime != "" {
263+ neededRuntimes [ info . Runtime ] = true
264+ }
174265 }
175266 }
176267
@@ -180,60 +271,100 @@ func configFileTemplate(tools []tools.Tool) string {
180271
181272 // Only include runtimes needed by the enabled tools
182273 if len (tools ) > 0 {
183- if needsNode {
184- sb .WriteString (" - node@22.2.0\n " )
185- }
186- if needsPython {
187- sb .WriteString (" - python@3.11.11\n " )
188- }
189- if needsDart {
190- sb .WriteString (" - dart@3.7.2\n " )
274+ // Create a sorted slice of runtimes
275+ var sortedRuntimes []string
276+ for runtime := range runtimeVersions {
277+ if neededRuntimes [runtime ] {
278+ sortedRuntimes = append (sortedRuntimes , runtime )
279+ }
191280 }
192- if needsJava {
193- sb .WriteString (" - java@17.0.10\n " )
281+ sort .Strings (sortedRuntimes )
282+
283+ // Write sorted runtimes
284+ for _ , runtime := range sortedRuntimes {
285+ sb .WriteString (fmt .Sprintf (" - %s@%s\n " , runtime , runtimeVersions [runtime ]))
194286 }
195287 } else {
196288 // In local mode with no tools specified, include all runtimes
197- sb .WriteString (" - node@22.2.0\n " )
198- sb .WriteString (" - python@3.11.11\n " )
199- sb .WriteString (" - dart@3.7.2\n " )
200- sb .WriteString (" - java@17.0.10\n " )
289+ var sortedRuntimes []string
290+ for runtime := range runtimeVersions {
291+ sortedRuntimes = append (sortedRuntimes , runtime )
292+ }
293+ sort .Strings (sortedRuntimes )
294+
295+ // Write sorted runtimes
296+ for _ , runtime := range sortedRuntimes {
297+ sb .WriteString (fmt .Sprintf (" - %s@%s\n " , runtime , runtimeVersions [runtime ]))
298+ }
201299 }
202300
203301 sb .WriteString ("tools:\n " )
204302
205303 // If we have tools from the API (enabled tools), use only those
206304 if len (tools ) > 0 {
207- // Add only the tools that are in the API response (enabled tools)
208- uuidToName := map [string ]string {
209- ESLint : "eslint" ,
210- Trivy : "trivy" ,
211- PyLint : "pylint" ,
212- PMD : "pmd" ,
213- DartAnalyzer : "dartanalyzer" ,
214- Semgrep : "semgrep" ,
215- Lizard : "lizard" ,
216- }
217-
218- for uuid , name := range uuidToName {
305+ // Create a sorted slice of tool names
306+ var sortedTools []string
307+ for uuid , name := range toolNameMap {
219308 if toolsMap [uuid ] {
220- sb .WriteString (fmt .Sprintf (" - %s@%s\n " , name , toolVersions [uuid ]))
309+ sortedTools = append (sortedTools , name )
310+ }
311+ }
312+ sort .Strings (sortedTools )
313+
314+ // Write sorted tools
315+ for _ , name := range sortedTools {
316+ // Find the UUID for this tool name to get its version
317+ for uuid , toolName := range toolNameMap {
318+ if toolName == name && toolsMap [uuid ] {
319+ sb .WriteString (fmt .Sprintf (" - %s@%s\n " , name , toolVersions [uuid ]))
320+ break
321+ }
221322 }
222323 }
223324 } else {
224- // If no tools were specified (local mode), include all defaults
225- sb .WriteString (fmt .Sprintf (" - eslint@%s\n " , defaultVersions [ESLint ]))
226- sb .WriteString (fmt .Sprintf (" - trivy@%s\n " , defaultVersions [Trivy ]))
227- sb .WriteString (fmt .Sprintf (" - pylint@%s\n " , defaultVersions [PyLint ]))
228- sb .WriteString (fmt .Sprintf (" - pmd@%s\n " , defaultVersions [PMD ]))
229- sb .WriteString (fmt .Sprintf (" - dartanalyzer@%s\n " , defaultVersions [DartAnalyzer ]))
230- sb .WriteString (fmt .Sprintf (" - semgrep@%s\n " , defaultVersions [Semgrep ]))
231- sb .WriteString (fmt .Sprintf (" - lizard@%s\n " , defaultVersions [Lizard ]))
325+ // If no tools were specified (local mode), include all defaults in sorted order
326+ tools := []struct {
327+ name string
328+ uuid string
329+ }{
330+ {"dartanalyzer" , DartAnalyzer },
331+ {"eslint" , ESLint },
332+ {"lizard" , Lizard },
333+ {"pmd" , PMD },
334+ {"pylint" , PyLint },
335+ {"semgrep" , Semgrep },
336+ {"trivy" , Trivy },
337+ }
338+
339+ for _ , tool := range tools {
340+ sb .WriteString (fmt .Sprintf (" - %s@%s\n " , tool .name , defaultVersions [tool .uuid ]))
341+ }
232342 }
233343
234344 return sb .String ()
235345}
236346
347+ func processRuntime (name , version string ) (* plugins.RuntimeInfo , error ) {
348+ configs := []plugins.RuntimeConfig {
349+ {
350+ Name : name ,
351+ Version : version ,
352+ },
353+ }
354+
355+ runtimeInfos , err := plugins .ProcessRuntimes (configs , os .TempDir ())
356+ if err != nil {
357+ return nil , err
358+ }
359+
360+ info , ok := runtimeInfos [name ]
361+ if ! ok {
362+ return nil , fmt .Errorf ("runtime %s not found in processed runtimes" , name )
363+ }
364+
365+ return info , nil
366+ }
367+
237368func cliConfigFileTemplate (cliLocalMode bool ) string {
238369 var cliModeString string
239370
0 commit comments