Skip to content

Commit 2104255

Browse files
Ajit Pratap SinghAjit Pratap Singh
authored andcommitted
refactor: split cmd/gosqlx/cmd/ god package — extract lsp, action, optimize into sub-packages
Extract the most independent subcommands (lsp, action, optimize) from the 13.7K LOC cmd/gosqlx/cmd/ god package into dedicated sub-packages under cmd/gosqlx/internal/: - cmd/gosqlx/internal/lspcmd/ — LSP server command (zero deps on cmd pkg) - cmd/gosqlx/internal/actioncmd/ — GitHub Actions CI command (self-contained) - cmd/gosqlx/internal/optimizecmd/ — SQL optimization command - cmd/gosqlx/internal/cmdutil/ — shared utilities (stdin, input detection, flags) Each sub-package exports a NewCmd() constructor returning *cobra.Command. Root command registers them via rootCmd.AddCommand(). Remaining commands (validate, format, analyze, parse, lint, config, watch) stay in cmd/gosqlx/cmd/ for now due to heavy cross-dependencies between their types (ValidatorOptions, CLIFormatterOptions, etc.). These can be split in follow-up PRs once shared types are extracted to cmdutil. No behavior changes — pure structural refactor.
1 parent 838d0e1 commit 2104255

File tree

6 files changed

+430
-85
lines changed

6 files changed

+430
-85
lines changed

cmd/gosqlx/cmd/root.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package cmd
22

33
import (
44
"github.com/spf13/cobra"
5+
6+
"github.com/ajitpratap0/GoSQLX/cmd/gosqlx/internal/actioncmd"
7+
"github.com/ajitpratap0/GoSQLX/cmd/gosqlx/internal/lspcmd"
8+
"github.com/ajitpratap0/GoSQLX/cmd/gosqlx/internal/optimizecmd"
59
)
610

711
// Version is the current version of gosqlx CLI.
@@ -159,4 +163,9 @@ func init() {
159163
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose output")
160164
rootCmd.PersistentFlags().StringVarP(&outputFile, "output", "o", "", "output file (default: stdout)")
161165
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "auto", "output format: json, yaml, table, tree, auto")
166+
167+
// Register subcommands from internal packages
168+
rootCmd.AddCommand(lspcmd.NewCmd())
169+
rootCmd.AddCommand(actioncmd.NewCmd())
170+
rootCmd.AddCommand(optimizecmd.NewCmd(&outputFile, &format))
162171
}
Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package cmd
1+
// Package actioncmd implements the gosqlx action subcommand for GitHub Actions CI.
2+
package actioncmd
23

34
import (
45
"fmt"
@@ -26,12 +27,12 @@ var (
2627
actionTimeout int
2728
)
2829

29-
// actionCmd implements the GitHub Actions entrypoint as a Go subcommand.
30-
// It finds SQL files, runs lint + validate, and outputs GitHub Actions annotations.
31-
var actionCmd = &cobra.Command{
32-
Use: "action",
33-
Short: "Run GoSQLX checks for GitHub Actions CI",
34-
Long: `Run SQL validation and linting with GitHub Actions annotation output.
30+
// NewCmd returns the action cobra.Command.
31+
func NewCmd() *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: "action",
34+
Short: "Run GoSQLX checks for GitHub Actions CI",
35+
Long: `Run SQL validation and linting with GitHub Actions annotation output.
3536
3637
This command replaces the shell-based entrypoint for the GoSQLX GitHub Action.
3738
It finds SQL files matching a glob pattern, runs validation and linting on each,
@@ -43,20 +44,18 @@ Environment variables (also settable via flags):
4344
SEVERITY - threshold: error, warning, info (default: warning)
4445
CONFIG - path to .gosqlx.yml config file
4546
TIMEOUT - per-file timeout in seconds (default: 600)`,
46-
RunE: runAction,
47-
}
47+
RunE: runAction,
48+
}
4849

49-
func init() {
50-
actionCmd.Flags().StringVar(&actionFiles, "files", "", "glob pattern for SQL files (env: SQL_FILES)")
51-
actionCmd.Flags().StringVar(&actionRules, "rules", "", "comma-separated lint rules (env: RULES)")
52-
actionCmd.Flags().StringVar(&actionSeverity, "severity", "", "severity threshold: error, warning, info (env: SEVERITY)")
53-
actionCmd.Flags().StringVar(&actionConfig, "config", "", "path to config file (env: CONFIG)")
54-
actionCmd.Flags().IntVar(&actionTimeout, "timeout", 0, "per-file timeout in seconds (env: TIMEOUT)")
50+
cmd.Flags().StringVar(&actionFiles, "files", "", "glob pattern for SQL files (env: SQL_FILES)")
51+
cmd.Flags().StringVar(&actionRules, "rules", "", "comma-separated lint rules (env: RULES)")
52+
cmd.Flags().StringVar(&actionSeverity, "severity", "", "severity threshold: error, warning, info (env: SEVERITY)")
53+
cmd.Flags().StringVar(&actionConfig, "config", "", "path to config file (env: CONFIG)")
54+
cmd.Flags().IntVar(&actionTimeout, "timeout", 0, "per-file timeout in seconds (env: TIMEOUT)")
5555

56-
rootCmd.AddCommand(actionCmd)
56+
return cmd
5757
}
5858

59-
// envDefault returns the flag value if non-empty, otherwise the env var, otherwise the fallback.
6059
func envDefault(flagVal, envKey, fallback string) string {
6160
if flagVal != "" {
6261
return flagVal
@@ -99,7 +98,6 @@ func runAction(_ *cobra.Command, _ []string) error {
9998
}
10099
}
101100

102-
// Find SQL files
103101
files, err := findSQLFiles(pattern)
104102
if err != nil {
105103
return fmt.Errorf("finding SQL files: %w", err)
@@ -110,7 +108,6 @@ func runAction(_ *cobra.Command, _ []string) error {
110108
}
111109
fmt.Printf("Found %d SQL file(s)\n", len(files))
112110

113-
// Parse rules
114111
var ruleList []string
115112
if rules != "" {
116113
for _, r := range strings.Split(rules, ",") {
@@ -134,7 +131,6 @@ func runAction(_ *cobra.Command, _ []string) error {
134131
for _, file := range files {
135132
displayFile := strings.TrimPrefix(file, "./")
136133

137-
// Validate
138134
vErr := validateFileWithTimeout(file, timeout)
139135
if vErr != nil {
140136
validateErrors++
@@ -154,7 +150,6 @@ func runAction(_ *cobra.Command, _ []string) error {
154150
totalValid++
155151
}
156152

157-
// Lint
158153
violations := lintFile(file, ruleList)
159154
for _, v := range violations {
160155
level := "notice"
@@ -169,7 +164,6 @@ func runAction(_ *cobra.Command, _ []string) error {
169164
}
170165
}
171166

172-
// Summary
173167
fmt.Println()
174168
fmt.Println("==============================")
175169
fmt.Println(" GoSQLX Results Summary")
@@ -181,12 +175,10 @@ func runAction(_ *cobra.Command, _ []string) error {
181175
fmt.Printf(" Lint warnings: %d\n", lintWarnings)
182176
fmt.Println("==============================")
183177

184-
// GitHub step summary
185178
if summaryPath := os.Getenv("GITHUB_STEP_SUMMARY"); summaryPath != "" {
186179
writeStepSummary(summaryPath, len(files), totalValid, validateErrors, lintErrors, lintWarnings)
187180
}
188181

189-
// Exit code based on severity
190182
fail := false
191183
switch severity {
192184
case "error":
@@ -201,7 +193,6 @@ func runAction(_ *cobra.Command, _ []string) error {
201193
return nil
202194
}
203195

204-
// ghAnnotation prints a GitHub Actions annotation.
205196
func ghAnnotation(level, file string, line int, msg string) {
206197
params := ""
207198
if file != "" {
@@ -217,7 +208,6 @@ func ghAnnotation(level, file string, line int, msg string) {
217208
}
218209
}
219210

220-
// extractLineNumber extracts a line number from text matching "line N".
221211
func extractLineNumber(re *regexp.Regexp, text string) int {
222212
m := re.FindStringSubmatch(text)
223213
if len(m) >= 2 {
@@ -227,15 +217,14 @@ func extractLineNumber(re *regexp.Regexp, text string) int {
227217
return 0
228218
}
229219

230-
// findSQLFiles locates SQL files matching the given glob pattern.
231220
func findSQLFiles(pattern string) ([]string, error) {
232221
var files []string
233222

234223
switch {
235224
case pattern == "**/*.sql":
236225
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
237226
if err != nil {
238-
return nil // skip errors
227+
return nil
239228
}
240229
if !info.IsDir() && strings.HasSuffix(strings.ToLower(path), ".sql") {
241230
files = append(files, path)
@@ -256,7 +245,6 @@ func findSQLFiles(pattern string) ([]string, error) {
256245
}
257246
}
258247
default:
259-
// Try filepath.Glob first, fall back to Walk with path matching
260248
matched, err := filepath.Glob(pattern)
261249
if err == nil && len(matched) > 0 {
262250
for _, m := range matched {
@@ -266,7 +254,6 @@ func findSQLFiles(pattern string) ([]string, error) {
266254
}
267255
}
268256
} else {
269-
// Walk and match with filepath.Match
270257
err = filepath.Walk(".", func(path string, info os.FileInfo, walkErr error) error {
271258
if walkErr != nil {
272259
return nil
@@ -286,14 +273,12 @@ func findSQLFiles(pattern string) ([]string, error) {
286273
return files, nil
287274
}
288275

289-
// lintViolation represents a single lint finding.
290276
type lintViolation struct {
291277
Line int
292278
Severity string
293279
Message string
294280
}
295281

296-
// defaultLinter creates a linter with standard rules.
297282
func defaultLinter() *linter.Linter {
298283
return linter.New(
299284
whitespace.NewTrailingWhitespaceRule(),
@@ -303,7 +288,6 @@ func defaultLinter() *linter.Linter {
303288
)
304289
}
305290

306-
// lintFile runs the linter on a file and returns violations.
307291
func lintFile(filePath string, _ []string) []lintViolation {
308292
l := defaultLinter()
309293
result := l.LintFile(filePath)
@@ -327,7 +311,6 @@ func lintFile(filePath string, _ []string) []lintViolation {
327311
return violations
328312
}
329313

330-
// validateFileWithTimeout validates a SQL file with a timeout.
331314
func validateFileWithTimeout(filePath string, timeout time.Duration) error {
332315
type result struct {
333316
err error
@@ -351,7 +334,6 @@ func validateFileWithTimeout(filePath string, timeout time.Duration) error {
351334
}
352335
}
353336

354-
// writeStepSummary appends a markdown summary to the GitHub step summary file.
355337
func writeStepSummary(path string, total, valid, valErrors, lintErrors, lintWarnings int) {
356338
f, err := os.OpenFile(filepath.Clean(path), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
357339
if err != nil {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cmdutil
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/pflag"
6+
)
7+
8+
// RootFlags holds pointers to the root command's persistent flag values.
9+
// Sub-packages receive this to access global flags without circular imports.
10+
type RootFlags struct {
11+
Verbose *bool
12+
OutputFile *string
13+
Format *string
14+
}
15+
16+
// Valid output format constants
17+
const (
18+
OutputFormatText = "text"
19+
OutputFormatJSON = "json"
20+
OutputFormatSARIF = "sarif"
21+
)
22+
23+
// ValidOutputFormats lists all supported output formats for validation
24+
var ValidOutputFormats = []string{OutputFormatText, OutputFormatJSON, OutputFormatSARIF}
25+
26+
// TrackChangedFlags returns a map of flag names that were explicitly set on the command line.
27+
// This includes both local flags and parent persistent flags.
28+
func TrackChangedFlags(cmd *cobra.Command) map[string]bool {
29+
flagsChanged := make(map[string]bool)
30+
cmd.Flags().Visit(func(f *pflag.Flag) {
31+
flagsChanged[f.Name] = true
32+
})
33+
if cmd.Parent() != nil && cmd.Parent().PersistentFlags() != nil {
34+
cmd.Parent().PersistentFlags().Visit(func(f *pflag.Flag) {
35+
flagsChanged[f.Name] = true
36+
})
37+
}
38+
return flagsChanged
39+
}

0 commit comments

Comments
 (0)