Skip to content
1 change: 1 addition & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 3 additions & 3 deletions ide/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

"github.com/jfrog/jfrog-cli-artifactory/ide/commands/aieditorextensions"
"github.com/jfrog/jfrog-cli-artifactory/ide/commands/jetbrains"
"github.com/jfrog/jfrog-cli-artifactory/ide/docs"
"github.com/jfrog/jfrog-cli-artifactory/ide/docs/setup"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
"github.com/jfrog/jfrog-client-go/utils/log"
)
Expand All @@ -28,8 +28,8 @@ func GetCommands() []components.Command {
return []components.Command{
{
Name: "setup",
Description: docs.GetDescription(),
Arguments: docs.GetArguments(),
Description: setup.GetDescription(),
Arguments: setup.GetArguments(),
Flags: getSetupFlags(),
Action: setupCmd,
Aliases: []string{"s"},
Expand Down
26 changes: 8 additions & 18 deletions ide/commands/aieditorextensions/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,15 @@ Manual %s Setup Instructions:
=================================

1. Close %s completely

2. Locate your %s installation directory and find product.json

3. Open the product.json file in a text editor with appropriate permissions:
• macOS: sudo nano "<path-to-app>/Contents/Resources/app/product.json"
• Windows: Run editor as Administrator
• Linux: sudo nano /path/to/resources/app/product.json

4. Find the "extensionsGallery" section and modify the "serviceUrl":
2. Find product.json in your %s installation directory
3. Edit the "extensionsGallery" section:
{
"extensionsGallery": {
"serviceUrl": "%s",
...
}
}

5. Save the file and restart %s
4. Save and restart %s

Service URL: %s
`, ideName, ideName, ideName, serviceURL, ideName, serviceURL)
Expand Down Expand Up @@ -59,12 +51,10 @@ Alternative: Install %s in a user-writable location like ~/Applications/`,
func GetGenericPermissionError(ideName, serviceURL string) string {
return fmt.Sprintf(`insufficient permissions to modify %s configuration.

To fix this, try running the command with elevated privileges:
sudo jf ide setup %s --repo-key <your-repo-key>

Or with direct URL:
sudo jf ide setup %s '%s'
Try running with elevated privileges:
• Linux/macOS: sudo jf ide setup %s '%s'
• Windows: Run PowerShell as Administrator

Or use the manual setup instructions provided in the error output.`,
ideName, ideName, ideName, serviceURL)
Or use the manual setup instructions.`,
ideName, ideName, serviceURL)
}
43 changes: 36 additions & 7 deletions ide/commands/aieditorextensions/vscode_fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,19 @@ func (vc *VSCodeForkCommand) detectInstallation() (string, error) {
paths := vc.forkConfig.GetAllInstallPaths()

for _, path := range paths {
// Expand environment variables and home directory
expandedPath := os.ExpandEnv(path)
if strings.HasPrefix(expandedPath, "~") {
home, err := os.UserHomeDir()
if err == nil {
expandedPath = filepath.Join(home, expandedPath[2:])
expandedPath := expandPath(path)
productJsonPath := filepath.Join(expandedPath, vc.forkConfig.ProductJson)

// Handle glob patterns (e.g., for .vscode-server/bin/*)
if strings.Contains(productJsonPath, "*") {
matches, err := filepath.Glob(productJsonPath)
if err == nil && len(matches) > 0 {
// Use the first match
return matches[0], nil
}
continue
}

productJsonPath := filepath.Join(expandedPath, vc.forkConfig.ProductJson)
if fileutils.IsPathExists(productJsonPath, false) {
return productJsonPath, nil
}
Expand All @@ -134,6 +137,32 @@ func (vc *VSCodeForkCommand) detectInstallation() (string, error) {
return "", fmt.Errorf("%s installation not found in standard locations", vc.forkConfig.DisplayName)
}

// expandPath expands environment variables and home directory in a path
// Handles both Unix-style ($VAR, ~) and Windows-style (%VAR%) variables
func expandPath(path string) string {
// Handle home directory expansion
if strings.HasPrefix(path, "~") {
if home, err := os.UserHomeDir(); err == nil {
path = filepath.Join(home, path[2:])
}
}

// On Windows, manually expand common environment variables for reliability
// os.ExpandEnv() can be unreliable with %VAR% syntax on some Windows systems
if runtime.GOOS == "windows" {
path = strings.ReplaceAll(path, "%LOCALAPPDATA%", os.Getenv("LOCALAPPDATA"))
path = strings.ReplaceAll(path, "%APPDATA%", os.Getenv("APPDATA"))
path = strings.ReplaceAll(path, "%PROGRAMFILES%", os.Getenv("PROGRAMFILES"))
path = strings.ReplaceAll(path, "%PROGRAMFILES(X86)%", os.Getenv("PROGRAMFILES(X86)"))
path = strings.ReplaceAll(path, "%USERPROFILE%", os.Getenv("USERPROFILE"))
}

// Try standard expansion for any remaining variables (Unix-style $VAR)
path = os.ExpandEnv(path)

return filepath.Clean(path)
}

// checkWritePermissions verifies write access to product.json
func (vc *VSCodeForkCommand) checkWritePermissions() error {
file, err := os.OpenFile(vc.productPath, os.O_RDWR, 0644)
Expand Down
11 changes: 10 additions & 1 deletion ide/commands/aieditorextensions/vscode_fork_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@ var VSCodeForks = map[string]*VSCodeForkConfig{
"~/Applications/Visual Studio Code.app/Contents/Resources/app",
},
"windows": {
`C:\Program Files\Microsoft VS Code\resources\app`,
`%LOCALAPPDATA%\Programs\Microsoft VS Code\resources\app`,
`C:\Program Files\Microsoft VS Code\resources\app`,
`C:\Program Files (x86)\Microsoft VS Code\resources\app`,
`%PROGRAMFILES%\Microsoft VS Code\resources\app`,
`%LOCALAPPDATA%\Code\resources\app`,
},
"linux": {
"/usr/share/code/resources/app",
"/opt/visual-studio-code/resources/app",
"/snap/code/current/usr/share/code/resources/app",
"~/.local/share/code/resources/app",
"~/.vscode-server/bin/*/resources/app",
},
},
ProductJson: "product.json",
Expand All @@ -54,7 +59,9 @@ var VSCodeForks = map[string]*VSCodeForkConfig{
},
"windows": {
`%LOCALAPPDATA%\Programs\Cursor\resources\app`,
`%LOCALAPPDATA%\Programs\cursor\resources\app`,
`C:\Program Files\Cursor\resources\app`,
`%LOCALAPPDATA%\Cursor\resources\app`,
},
"linux": {
"/usr/share/cursor/resources/app",
Expand All @@ -74,7 +81,9 @@ var VSCodeForks = map[string]*VSCodeForkConfig{
},
"windows": {
`%LOCALAPPDATA%\Programs\Windsurf\resources\app`,
`%LOCALAPPDATA%\Programs\windsurf\resources\app`,
`C:\Program Files\Windsurf\resources\app`,
`%LOCALAPPDATA%\Windsurf\resources\app`,
},
"linux": {
"/usr/share/windsurf/resources/app",
Expand Down
21 changes: 0 additions & 21 deletions ide/docs/setup.go

This file was deleted.

26 changes: 9 additions & 17 deletions ide/docs/setup/help.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package setup

import (
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
)
import "github.com/jfrog/jfrog-cli-core/v2/plugins/components"

var Usage = []string{
"ide setup <IDE_NAME> [SERVICE_URL]",
"ide s <IDE_NAME> [SERVICE_URL]",
"ide setup <ide-name> [url]",
"ide s <ide-name> [url]",
}

func GetDescription() string {
return `Setup IDE integration with JFrog Artifactory.

Supported Action:
setup Configure your IDE to use JFrog Artifactory

Supported IDEs:
vscode Visual Studio Code
cursor Cursor IDE
Expand All @@ -28,22 +23,19 @@ Examples:
# Setup Cursor
jf ide setup cursor --repo-key=cursor-remote

# Setup Windsurf
jf ide setup windsurf --repo-key=windsurf-remote

# Setup JetBrains
jf ide setup jetbrains --repo-key=jetbrains-remote`
# Setup with direct URL
jf ide setup vscode "https://artifactory.example.com/artifactory/api/aieditorextensions/vscode-repo/_apis/public/gallery"`
}

func GetArguments() []components.Argument {
return []components.Argument{
{
Name: "IDE_NAME",
Description: "The name of the IDE to setup. Supported IDEs are 'vscode', 'cursor', 'windsurf', and 'jetbrains'.",
Name: "ide-name",
Description: "IDE to setup. Supported: vscode, cursor, windsurf, jetbrains",
},
{
Name: "SERVICE_URL",
Description: "(Optional) Direct repository service URL. When provided, --repo-key and server config are not required. Example: https://host/api/aieditorextensions/repo/_apis/public/gallery",
Name: "url",
Description: "[Optional] Direct repository/service URL. When provided, --repo-key and server config are not required.",
Optional: true,
},
}
Expand Down
Loading