Skip to content

Commit a0ed5e9

Browse files
committed
Fix IDE CLI commands: clean help output, hide legacy aliases, and ensure modular command registration
1 parent 1644e2e commit a0ed5e9

File tree

4 files changed

+151
-76
lines changed

4 files changed

+151
-76
lines changed

artifactory/cli/ide/jetbrains/cli.go

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,104 @@ import (
55
"net/url"
66
"strings"
77

8+
"github.com/jfrog/gofrog/log"
89
"github.com/jfrog/jfrog-cli-artifactory/artifactory/cli/ide"
910
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/ide/jetbrains"
1011
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
1112
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
1213
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
1314
)
1415

16+
const (
17+
repoKeyFlag = "repo-key"
18+
urlSuffixFlag = "url-suffix"
19+
)
20+
1521
func GetCommands() []components.Command {
1622
return []components.Command{
1723
{
1824
Name: "jetbrains-config",
19-
Aliases: []string{"jetbrains"},
25+
Aliases: []string{"jb"},
26+
Hidden: true,
27+
Flags: getFlags(),
28+
Arguments: getArguments(),
2029
Action: jetbrainsConfigCmd,
2130
Description: ide.JetbrainsConfigDescription,
2231
},
2332
}
2433
}
2534

26-
func jetbrainsConfigCmd(c *components.Context) error {
27-
repositoryURL, err := ide.ValidateSingleNonEmptyArg(c, "jf jetbrains-config <repository-url>")
28-
if err != nil {
29-
return err
35+
func getFlags() []components.Flag {
36+
return []components.Flag{
37+
components.NewStringFlag(repoKeyFlag, "Repository key for the JetBrains plugins repo. [Required if no URL is given]", components.SetMandatoryFalse()),
38+
components.NewStringFlag(urlSuffixFlag, "Suffix for the JetBrains plugins repository URL. Default: (empty)", components.SetMandatoryFalse()),
39+
// Server configuration flags
40+
components.NewStringFlag("url", "JFrog Artifactory URL. (example: https://acme.jfrog.io/artifactory)", components.SetMandatoryFalse()),
41+
components.NewStringFlag("user", "JFrog username.", components.SetMandatoryFalse()),
42+
components.NewStringFlag("password", "JFrog password.", components.SetMandatoryFalse()),
43+
components.NewStringFlag("access-token", "JFrog access token.", components.SetMandatoryFalse()),
44+
components.NewStringFlag("server-id", "Server ID configured using the 'jf config' command.", components.SetMandatoryFalse()),
3045
}
46+
}
3147

32-
// Extract repo key from repository URL for potential validation
33-
repoKey := extractRepoKeyFromRepositoryURL(repositoryURL)
48+
func getArguments() []components.Argument {
49+
return []components.Argument{
50+
{
51+
Name: "repository-url",
52+
Description: "The Artifactory JetBrains plugins repository URL (optional when using --repo-key)",
53+
Optional: true,
54+
},
55+
}
56+
}
3457

35-
// Create server details only if server configuration flags are provided
36-
// This makes server configuration optional for basic JetBrains setup
37-
var rtDetails *config.ServerDetails
58+
func jetbrainsConfigCmd(c *components.Context) error {
59+
var repositoryURL, repoKey string
60+
var err error
3861

62+
if c.GetNumberOfArgs() > 0 && isValidUrl(c.GetArgumentAt(0)) {
63+
repositoryURL = c.GetArgumentAt(0)
64+
repoKey = extractRepoKeyFromRepositoryURL(repositoryURL)
65+
} else {
66+
repoKey = c.GetStringFlagValue(repoKeyFlag)
67+
if repoKey == "" {
68+
return fmt.Errorf("You must provide either a repository URL as the first argument or --repo-key flag.")
69+
}
70+
// Get Artifactory URL from server details (flags or default)
71+
var artDetails *config.ServerDetails
72+
if ide.HasServerConfigFlags(c) {
73+
artDetails, err = pluginsCommon.CreateArtifactoryDetailsByFlags(c)
74+
if err != nil {
75+
return fmt.Errorf("Failed to get Artifactory server details: %w", err)
76+
}
77+
} else {
78+
artDetails, err = config.GetDefaultServerConf()
79+
if err != nil {
80+
return fmt.Errorf("Failed to get default Artifactory server details: %w", err)
81+
}
82+
}
83+
baseUrl := strings.TrimRight(artDetails.Url, "/")
84+
urlSuffix := c.GetStringFlagValue(urlSuffixFlag)
85+
if urlSuffix != "" {
86+
urlSuffix = "/" + strings.TrimLeft(urlSuffix, "/")
87+
}
88+
repositoryURL = baseUrl + "/artifactory/api/jetbrainsplugins/" + repoKey + urlSuffix
89+
}
90+
91+
// Create server details for validation
92+
var rtDetails *config.ServerDetails
3993
if ide.HasServerConfigFlags(c) {
94+
// Use explicit server configuration flags
4095
rtDetails, err = pluginsCommon.CreateArtifactoryDetailsByFlags(c)
4196
if err != nil {
4297
return fmt.Errorf("failed to create server configuration: %w", err)
4398
}
99+
} else {
100+
// Use default server configuration for validation when no explicit flags provided
101+
rtDetails, err = config.GetDefaultServerConf()
102+
if err != nil {
103+
// If no default server, that's okay - we'll just skip validation
104+
log.Debug("No default server configuration found, skipping repository validation")
105+
}
44106
}
45107

46108
jetbrainsCmd := jetbrains.NewJetbrainsCommand(repositoryURL, repoKey)
@@ -51,29 +113,28 @@ func jetbrainsConfigCmd(c *components.Context) error {
51113
return jetbrainsCmd.Run()
52114
}
53115

116+
func isValidUrl(s string) bool {
117+
u, err := url.Parse(s)
118+
return err == nil && u.Scheme != "" && u.Host != ""
119+
}
120+
54121
// extractRepoKeyFromRepositoryURL extracts the repository key from a JetBrains repository URL
55122
// Expected format: https://<server>/artifactory/api/jetbrainsplugins/<repo-key>
56123
func extractRepoKeyFromRepositoryURL(repositoryURL string) string {
57124
if repositoryURL == "" {
58125
return ""
59126
}
60127

61-
// Parse the URL to extract the repository key
62128
parsedURL, err := url.Parse(repositoryURL)
63129
if err != nil {
64130
return ""
65131
}
66132

67-
// Split the path to find the repository key
68-
// Expected path: /artifactory/api/jetbrainsplugins/<repo-key>
69133
pathParts := strings.Split(strings.TrimPrefix(parsedURL.Path, "/"), "/")
70-
71-
// Look for the jetbrainsplugins API path
72134
for i, part := range pathParts {
73135
if part == "api" && i+1 < len(pathParts) && pathParts[i+1] == "jetbrainsplugins" && i+2 < len(pathParts) {
74136
return pathParts[i+2]
75137
}
76138
}
77-
78139
return ""
79140
}

artifactory/cli/ide/vscode/cli.go

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66
"strings"
77

8+
"github.com/jfrog/gofrog/log"
89
"github.com/jfrog/jfrog-cli-artifactory/artifactory/cli/ide"
910
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/ide/vscode"
1011
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
@@ -14,14 +15,18 @@ import (
1415

1516
const (
1617
productJsonPath = "product-json-path"
18+
repoKeyFlag = "repo-key"
19+
urlSuffixFlag = "url-suffix"
1720
)
1821

1922
func GetCommands() []components.Command {
2023
return []components.Command{
2124
{
2225
Name: "vscode-config",
23-
Aliases: []string{"vscode"},
26+
Aliases: []string{"vscode", "code"},
27+
Hidden: true,
2428
Flags: getFlags(),
29+
Arguments: getArguments(),
2530
Action: vscodeConfigCmd,
2631
Description: ide.VscodeConfigDescription,
2732
},
@@ -30,30 +35,78 @@ func GetCommands() []components.Command {
3035

3136
func getFlags() []components.Flag {
3237
return []components.Flag{
33-
components.NewStringFlag(productJsonPath, "[Optional] Path to VSCode product.json file. If not provided, auto-detects VSCode installation.", components.SetMandatoryFalse()),
38+
components.NewStringFlag(productJsonPath, "Path to VSCode product.json file. If not provided, auto-detects VSCode installation.", components.SetMandatoryFalse()),
39+
components.NewStringFlag(repoKeyFlag, "Repository key for the VSCode extensions repo. [Required if no URL is given]", components.SetMandatoryFalse()),
40+
components.NewStringFlag(urlSuffixFlag, "Suffix for the VSCode extensions service URL. Default: _apis/public/gallery", components.SetMandatoryFalse()),
41+
// Server configuration flags
42+
components.NewStringFlag("url", "JFrog Artifactory URL. (example: https://acme.jfrog.io/artifactory)", components.SetMandatoryFalse()),
43+
components.NewStringFlag("user", "JFrog username.", components.SetMandatoryFalse()),
44+
components.NewStringFlag("password", "JFrog password.", components.SetMandatoryFalse()),
45+
components.NewStringFlag("access-token", "JFrog access token.", components.SetMandatoryFalse()),
46+
components.NewStringFlag("server-id", "Server ID configured using the 'jf config' command.", components.SetMandatoryFalse()),
47+
}
48+
}
49+
50+
func getArguments() []components.Argument {
51+
return []components.Argument{
52+
{
53+
Name: "service-url",
54+
Description: "The Artifactory VSCode extensions service URL (optional when using --repo-key)",
55+
Optional: true,
56+
},
3457
}
3558
}
3659

3760
func vscodeConfigCmd(c *components.Context) error {
38-
serviceURL, err := ide.ValidateSingleNonEmptyArg(c, "jf vscode-config <service-url>")
39-
if err != nil {
40-
return err
61+
var serviceURL, repoKey string
62+
var err error
63+
64+
if c.GetNumberOfArgs() > 0 && isValidUrl(c.GetArgumentAt(0)) {
65+
serviceURL = c.GetArgumentAt(0)
66+
repoKey = extractRepoKeyFromServiceURL(serviceURL)
67+
} else {
68+
repoKey = c.GetStringFlagValue(repoKeyFlag)
69+
if repoKey == "" {
70+
return fmt.Errorf("You must provide either a service URL as the first argument or --repo-key flag.")
71+
}
72+
// Get Artifactory URL from server details (flags or default)
73+
var artDetails *config.ServerDetails
74+
if ide.HasServerConfigFlags(c) {
75+
artDetails, err = pluginsCommon.CreateArtifactoryDetailsByFlags(c)
76+
if err != nil {
77+
return fmt.Errorf("Failed to get Artifactory server details: %w", err)
78+
}
79+
} else {
80+
artDetails, err = config.GetDefaultServerConf()
81+
if err != nil {
82+
return fmt.Errorf("Failed to get default Artifactory server details: %w", err)
83+
}
84+
}
85+
baseUrl := strings.TrimRight(artDetails.Url, "/")
86+
urlSuffix := c.GetStringFlagValue(urlSuffixFlag)
87+
if urlSuffix == "" {
88+
urlSuffix = "_apis/public/gallery"
89+
}
90+
serviceURL = baseUrl + "/artifactory/api/vscodeextensions/" + repoKey + "/" + strings.TrimLeft(urlSuffix, "/")
4191
}
4292

4393
productPath := c.GetStringFlagValue(productJsonPath)
4494

45-
// Extract repo key from service URL for potential validation
46-
repoKey := extractRepoKeyFromServiceURL(serviceURL)
47-
48-
// Create server details only if server configuration flags are provided
49-
// This makes server configuration optional for basic VS Code setup
95+
// Create server details for validation
5096
var rtDetails *config.ServerDetails
51-
5297
if ide.HasServerConfigFlags(c) {
98+
// Use explicit server configuration flags
5399
rtDetails, err = pluginsCommon.CreateArtifactoryDetailsByFlags(c)
54100
if err != nil {
55101
return fmt.Errorf("failed to create server configuration: %w", err)
56102
}
103+
} else {
104+
// Use default server configuration for validation when no explicit flags provided
105+
rtDetails, err = config.GetDefaultServerConf()
106+
if err != nil {
107+
// If no default server, that's okay - we'll just skip validation
108+
log.Debug("No default server configuration found, skipping repository validation")
109+
}
57110
}
58111

59112
vscodeCmd := vscode.NewVscodeCommand(serviceURL, productPath, repoKey)
@@ -64,29 +117,28 @@ func vscodeConfigCmd(c *components.Context) error {
64117
return vscodeCmd.Run()
65118
}
66119

120+
func isValidUrl(s string) bool {
121+
u, err := url.Parse(s)
122+
return err == nil && u.Scheme != "" && u.Host != ""
123+
}
124+
67125
// extractRepoKeyFromServiceURL extracts the repository key from a VSCode service URL
68126
// Expected format: https://<server>/artifactory/api/vscodeextensions/<repo-key>/_apis/public/gallery
69127
func extractRepoKeyFromServiceURL(serviceURL string) string {
70128
if serviceURL == "" {
71129
return ""
72130
}
73131

74-
// Parse the URL to extract the repository key
75132
parsedURL, err := url.Parse(serviceURL)
76133
if err != nil {
77134
return ""
78135
}
79136

80-
// Split the path to find the repository key
81-
// Expected path: /artifactory/api/vscodeextensions/<repo-key>/_apis/public/gallery
82137
pathParts := strings.Split(strings.TrimPrefix(parsedURL.Path, "/"), "/")
83-
84-
// Look for the vscodeextensions API path
85138
for i, part := range pathParts {
86139
if part == "api" && i+1 < len(pathParts) && pathParts[i+1] == "vscodeextensions" && i+2 < len(pathParts) {
87140
return pathParts[i+2]
88141
}
89142
}
90-
91143
return ""
92144
}

cli/cli.go

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
artifactoryCLI "github.com/jfrog/jfrog-cli-artifactory/artifactory/cli"
5+
"github.com/jfrog/jfrog-cli-artifactory/artifactory/cli/ide"
56
"github.com/jfrog/jfrog-cli-artifactory/artifactory/cli/ide/jetbrains"
67
"github.com/jfrog/jfrog-cli-artifactory/artifactory/cli/ide/vscode"
78
distributionCLI "github.com/jfrog/jfrog-cli-artifactory/distribution/cli"
@@ -48,53 +49,14 @@ func getTopLevelIDECommands() []components.Command {
4849
vscodeCommands := vscode.GetCommands()
4950
jetbrainsCommands := jetbrains.GetCommands()
5051

51-
// Modify VSCode command to add 'code' alias and update description
52+
// Use centralized descriptions
5253
if len(vscodeCommands) > 0 {
53-
vscodeCommands[0].Aliases = append(vscodeCommands[0].Aliases, "code")
54-
vscodeCommands[0].Description = `Configure VSCode to use JFrog Artifactory for extensions.
55-
56-
The service URL should be in the format:
57-
https://<artifactory-url>/artifactory/api/vscodeextensions/<repo-key>/_apis/public/gallery
58-
59-
Examples:
60-
jf vscode-config https://mycompany.jfrog.io/artifactory/api/vscodeextensions/vscode-extensions/_apis/public/gallery
61-
jf code https://mycompany.jfrog.io/artifactory/api/vscodeextensions/vscode-extensions/_apis/public/gallery
62-
63-
This command will:
64-
- Modify the VSCode product.json file to change the extensions gallery URL
65-
- Create an automatic backup before making changes
66-
- Require VSCode to be restarted to apply changes
67-
68-
Optional: Provide server configuration flags (--url, --user, --password, --access-token, or --server-id)
69-
to enable repository validation. Without these flags, the command will only modify the local VSCode configuration.
70-
71-
Note: On macOS/Linux, you may need to run with sudo for system-installed VSCode.`
54+
vscodeCommands[0].Description = ide.VscodeConfigDescription
7255
}
73-
74-
// Modify JetBrains command to add 'jb' alias and update description
7556
if len(jetbrainsCommands) > 0 {
57+
jetbrainsCommands[0].Description = ide.JetbrainsConfigDescription
7658
jetbrainsCommands[0].Aliases = append(jetbrainsCommands[0].Aliases, "jb")
77-
jetbrainsCommands[0].Description = `Configure JetBrains IDEs to use JFrog Artifactory for plugins.
78-
79-
The repository URL should be in the format:
80-
https://<artifactory-url>/artifactory/api/jetbrainsplugins/<repo-key>
81-
82-
Examples:
83-
jf jetbrains-config https://mycompany.jfrog.io/artifactory/api/jetbrainsplugins/jetbrains-plugins
84-
jf jb https://mycompany.jfrog.io/artifactory/api/jetbrainsplugins/jetbrains-plugins
85-
86-
This command will:
87-
- Detect all installed JetBrains IDEs
88-
- Modify each IDE's idea.properties file to add the plugins repository URL
89-
- Create automatic backups before making changes
90-
- Require IDEs to be restarted to apply changes
91-
92-
Optional: Provide server configuration flags (--url, --user, --password, --access-token, or --server-id)
93-
to enable repository validation. Without these flags, the command will only modify the local IDE configuration.
94-
95-
Supported IDEs: IntelliJ IDEA, PyCharm, WebStorm, PhpStorm, RubyMine, CLion, DataGrip, GoLand, Rider, Android Studio, AppCode, RustRover, Aqua`
9659
}
9760

98-
// Return both modified commands
9961
return append(vscodeCommands, jetbrainsCommands...)
10062
}

cliutils/flagkit/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ var flagsMap = map[string]components.Flag{
10601060
lcDryRun: components.NewBoolFlag(dryRun, "Set to true to only simulate the distribution of the release bundle.", components.WithBoolDefaultValueFalse()),
10611061
lcIncludeRepos: components.NewStringFlag(IncludeRepos, "List of semicolon-separated(;) repositories to include in the promotion. If this property is left undefined, all repositories (except those specifically excluded) are included in the promotion. "+
10621062
"If one or more repositories are specifically included, all other repositories are excluded.` `", components.SetMandatoryFalse()),
1063-
lcExcludeRepos: components.NewStringFlag(ExcludeRepos, "List of semicolon-separated(;) repositories to exclude from the promotion.` `", components.SetMandatoryFalse()),
1063+
lcExcludeRepos: components.NewStringFlag(ExcludeRepos, "List of semicolon-seperated(;) repositories to exclude from the promotion.` `", components.SetMandatoryFalse()),
10641064
platformUrl: components.NewStringFlag(url, "JFrog platform URL. (example: https://acme.jfrog.io)` `", components.SetMandatoryFalse()),
10651065
PromotionType: components.NewStringFlag(PromotionType, "The promotion type. Can be one of 'copy' or 'move'.", components.WithStrDefaultValue("copy")),
10661066
lcTag: components.NewStringFlag(Tag, "Tag to put on Release Bundle version.", components.SetMandatoryFalse()),

0 commit comments

Comments
 (0)