Skip to content

Commit cef0757

Browse files
indacoclaude
andauthored
feat: add discover command with dependency-check workflow (#181)
* refactor: extract shared parser package from dependencycheck * feat: add discovery service for version sources * feat: add top-level discover command * refactor: move shared CLI flags to cliflags package * refactor!: remove modules command, replace with discover * feat: enhance init command with dependency-check suggestions * feat(discover): add --depth flag for manifest discover * feat(discover): auto-init with dependency-check plugin on confirmation Co-Authored-By: Claude Opus 4.5 <[email protected]> * feat(discovery): recursive manifest scan and module sync candidates * refactor(config): rename MaxDepth to ModuleMaxDepth for clarity * chore(lint): add gocritic style tag and configure exclusions * refactor(core): use octal literal style (0o prefix) * refactor: use filesystem permission constants instead of hardcoded values * test(discover): improve test coverage Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 77228f1 commit cef0757

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+8210
-2665
lines changed

.golangci.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ linters:
2626
enabled-tags:
2727
- diagnostic
2828
- performance
29+
- style
2930
disabled-checks:
3031
- hugeParam
3132
- filepathJoin
3233
- commentedOutCode
34+
- unnamedResult
35+
- ifElseChain
36+
- paramTypeCombine
37+
- importShadow
38+
- emptyStringTest
39+
- regexpSimplify
3340
gosec:
3441
excludes:
3542
- G304
@@ -76,6 +83,11 @@ linters:
7683
linters:
7784
- gocritic
7885
text: "stringXbytes"
86+
# Test files: allow hardcoded octal permissions for testing edge cases
87+
- path: _test\.go
88+
linters:
89+
- gocritic
90+
text: "octalLiteral"
7991
# Prealloc is often too noisy for small slices
8092
- path: \.go$
8193
linters:

internal/cli/cli.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66

77
"github.com/indaco/sley/internal/commands/bump"
88
"github.com/indaco/sley/internal/commands/changelog"
9+
"github.com/indaco/sley/internal/commands/discover"
910
"github.com/indaco/sley/internal/commands/doctor"
1011
"github.com/indaco/sley/internal/commands/extension"
1112
"github.com/indaco/sley/internal/commands/initialize"
12-
"github.com/indaco/sley/internal/commands/modules"
1313
"github.com/indaco/sley/internal/commands/pre"
1414
"github.com/indaco/sley/internal/commands/set"
1515
"github.com/indaco/sley/internal/commands/show"
@@ -58,15 +58,15 @@ func New(cfg *config.Config, registry *plugins.PluginRegistry) *urfavecli.Comman
5858
},
5959
Commands: []*urfavecli.Command{
6060
initialize.Run(),
61+
discover.Run(cfg),
6162
show.Run(cfg),
6263
set.Run(cfg),
6364
bump.Run(cfg, registry),
6465
pre.Run(cfg),
6566
doctor.Run(cfg),
66-
changelog.Run(cfg),
6767
tag.Run(cfg),
68+
changelog.Run(cfg),
6869
extension.Run(),
69-
modules.Run(),
7070
},
7171
}
7272
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package flags
1+
package cliflags
22

33
import "github.com/urfave/cli/v3"
44

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package flags
1+
package cliflags
22

33
import (
44
"slices"

internal/clix/execution_context_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func TestGetMultiModuleContext_DiscoverModulesError(t *testing.T) {
123123
Discovery: &config.DiscoveryConfig{
124124
Enabled: func() *bool { b := true; return &b }(),
125125
// Set max depth to 0 to limit discovery
126-
MaxDepth: func() *int { d := 0; return &d }(),
126+
ModuleMaxDepth: func() *int { d := 0; return &d }(),
127127
},
128128
},
129129
}
@@ -302,9 +302,9 @@ func TestGetExecutionContext_MultiModuleWithNonInteractive(t *testing.T) {
302302
Path: ".version",
303303
Workspace: &config.WorkspaceConfig{
304304
Discovery: &config.DiscoveryConfig{
305-
Enabled: &enabled,
306-
Recursive: &recursive,
307-
MaxDepth: &maxDepth,
305+
Enabled: &enabled,
306+
Recursive: &recursive,
307+
ModuleMaxDepth: &maxDepth,
308308
},
309309
},
310310
}
@@ -507,9 +507,9 @@ func TestGetExecutionContext_SingleModuleDetection(t *testing.T) {
507507
Path: versionFile,
508508
Workspace: &config.WorkspaceConfig{
509509
Discovery: &config.DiscoveryConfig{
510-
Enabled: &enabled,
511-
Recursive: &recursive,
512-
MaxDepth: &maxDepth,
510+
Enabled: &enabled,
511+
Recursive: &recursive,
512+
ModuleMaxDepth: &maxDepth,
513513
},
514514
},
515515
}

internal/commands/bump/bumpcmd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package bump
22

33
import (
4-
"github.com/indaco/sley/internal/cli/flags"
4+
"github.com/indaco/sley/internal/cliflags"
55
"github.com/indaco/sley/internal/config"
66
"github.com/indaco/sley/internal/plugins"
77
"github.com/urfave/cli/v3"
@@ -23,7 +23,7 @@ func Run(cfg *config.Config, registry *plugins.PluginRegistry) *cli.Command {
2323
Usage: "Preserve existing build metadata when bumping",
2424
},
2525
}
26-
cmdFlags = append(cmdFlags, flags.MultiModuleFlags()...)
26+
cmdFlags = append(cmdFlags, cliflags.MultiModuleFlags()...)
2727

2828
return &cli.Command{
2929
Name: "bump",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package discover
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/indaco/sley/internal/config"
9+
"github.com/indaco/sley/internal/core"
10+
"github.com/indaco/sley/internal/discovery"
11+
"github.com/urfave/cli/v3"
12+
)
13+
14+
// Run returns the "discover" command.
15+
func Run(cfg *config.Config) *cli.Command {
16+
return &cli.Command{
17+
Name: "discover",
18+
Aliases: []string{"scan"},
19+
Usage: "Scan for version sources and suggest configuration",
20+
UsageText: `sley discover [options]
21+
22+
Scans the current directory for:
23+
- .version files (sley modules)
24+
- Manifest files (package.json, Cargo.toml, pyproject.toml, etc.)
25+
26+
Shows discovered version sources and suggests dependency-check configuration
27+
for keeping versions in sync.`,
28+
Flags: []cli.Flag{
29+
&cli.IntFlag{
30+
Name: "depth",
31+
Aliases: []string{"d"},
32+
Usage: "Maximum directory depth for manifest discovery",
33+
Value: 3,
34+
},
35+
&cli.StringFlag{
36+
Name: "format",
37+
Aliases: []string{"f"},
38+
Usage: "Output format: text, json, table",
39+
Value: "text",
40+
},
41+
&cli.BoolFlag{
42+
Name: "quiet",
43+
Aliases: []string{"q"},
44+
Usage: "Only show summary",
45+
},
46+
&cli.BoolFlag{
47+
Name: "no-interactive",
48+
Usage: "Skip interactive prompts",
49+
},
50+
},
51+
Action: func(ctx context.Context, cmd *cli.Command) error {
52+
return runDiscoverCmd(ctx, cmd, cfg)
53+
},
54+
}
55+
}
56+
57+
// runDiscoverCmd executes the discover command.
58+
func runDiscoverCmd(ctx context.Context, cmd *cli.Command, cfg *config.Config) error {
59+
// Get current working directory
60+
rootDir, err := os.Getwd()
61+
if err != nil {
62+
return fmt.Errorf("failed to get current directory: %w", err)
63+
}
64+
65+
// Get depth flag
66+
depth := cmd.Int("depth")
67+
68+
// Run discovery with specified depth
69+
fs := core.NewOSFileSystem()
70+
svc := discovery.NewService(fs, cfg)
71+
72+
result, err := svc.DiscoverWithDepth(ctx, rootDir, depth)
73+
if err != nil {
74+
return fmt.Errorf("discovery failed: %w", err)
75+
}
76+
77+
// Format and display results
78+
format := ParseOutputFormat(cmd.String("format"))
79+
quiet := cmd.Bool("quiet")
80+
81+
formatter := NewFormatter(format)
82+
83+
if quiet {
84+
// In quiet mode, only show summary
85+
printQuietSummary(result)
86+
} else {
87+
formatter.PrintResult(result)
88+
}
89+
90+
// Run interactive workflow if not disabled
91+
noInteractive := cmd.Bool("no-interactive")
92+
if !noInteractive && format == FormatText {
93+
prompter := NewPrompter()
94+
workflow := NewWorkflow(prompter, result, rootDir)
95+
if _, err := workflow.Run(ctx); err != nil {
96+
return err
97+
}
98+
}
99+
100+
return nil
101+
}
102+
103+
// printQuietSummary prints a minimal summary of discovery results.
104+
func printQuietSummary(result *discovery.Result) {
105+
moduleCount := len(result.Modules)
106+
manifestCount := len(result.Manifests)
107+
mismatchCount := len(result.Mismatches)
108+
109+
fmt.Printf("Mode: %s | Modules: %d | Manifests: %d", result.Mode, moduleCount, manifestCount)
110+
111+
if mismatchCount > 0 {
112+
fmt.Printf(" | Mismatches: %d", mismatchCount)
113+
}
114+
115+
if result.PrimaryVersion() != "" {
116+
fmt.Printf(" | Version: %s", result.PrimaryVersion())
117+
}
118+
119+
fmt.Println()
120+
}
121+
122+
// DiscoverAndSuggest is a helper function that performs discovery and returns
123+
// suggested dependency-check configuration.
124+
func DiscoverAndSuggest(ctx context.Context, cfg *config.Config, rootDir string) (*discovery.Result, *config.DependencyCheckConfig, error) {
125+
fs := core.NewOSFileSystem()
126+
svc := discovery.NewService(fs, cfg)
127+
128+
result, err := svc.Discover(ctx, rootDir)
129+
if err != nil {
130+
return nil, nil, err
131+
}
132+
133+
suggestion := SuggestDependencyCheckFromDiscovery(result)
134+
135+
return result, suggestion, nil
136+
}

0 commit comments

Comments
 (0)