Files
vstask/tasks/settings.go

190 lines
4.9 KiB
Go

package tasks
import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/chenasraf/vstask/utils"
)
// -----------------------------
// VS Code settings model
// -----------------------------
type VSCodeSettings struct {
// Matches the VS Code setting: "npm.packageManager"
// Valid values: "npm", "yarn", "pnpm", "bun"
NPMPackageManager string `json:"npm.packageManager"`
}
// -----------------------------
// Public entry point
// -----------------------------
// detectPackageManagerFromSettings tries to read the package manager preference
// from (1) workspace .vscode/settings.json, then (2) user settings.json.
// If found and valid, returns (exe, true). Otherwise ("", false).
func detectPackageManagerFromSettings(cwd string) (string, bool) {
// 1) Workspace settings
if exe, ok := readWorkspacePackageManager(cwd); ok {
return exe, true
}
// 2) User settings
if exe, ok := readUserPackageManager(); ok {
return exe, true
}
return "", false
}
// -----------------------------
// Workspace settings
// -----------------------------
func readWorkspacePackageManager(cwd string) (string, bool) {
path := filepath.Join(cwd, ".vscode", "settings.json")
return readPackageManagerFromFile(path)
}
// -----------------------------
// User settings (Code / Insiders / VSCodium)
// -----------------------------
func readUserPackageManager() (string, bool) {
candidates := userSettingsCandidates()
for _, p := range candidates {
if exe, ok := readPackageManagerFromFile(p); ok {
return exe, true
}
}
return "", false
}
func userSettingsCandidates() []string {
var dirs []string
switch runtime.GOOS {
case "darwin":
home, _ := os.UserHomeDir()
appSupport := filepath.Join(home, "Library", "Application Support")
if home != "" {
dirs = append(dirs,
filepath.Join(appSupport, "Code", "User", "settings.json"),
filepath.Join(appSupport, "Code - Insiders", "User", "settings.json"),
filepath.Join(appSupport, "VSCodium", "User", "settings.json"),
)
}
case "linux":
home, _ := os.UserHomeDir()
xdg := os.Getenv("XDG_CONFIG_HOME")
if xdg != "" {
dirs = append(dirs,
filepath.Join(xdg, "Code", "User", "settings.json"),
filepath.Join(xdg, "Code - Insiders", "User", "settings.json"),
filepath.Join(xdg, "VSCodium", "User", "settings.json"),
)
}
if home != "" {
dirs = append(dirs,
filepath.Join(home, ".config", "Code", "User", "settings.json"),
filepath.Join(home, ".config", "Code - Insiders", "User", "settings.json"),
filepath.Join(home, ".config", "VSCodium", "User", "settings.json"),
)
}
case "windows":
appData := os.Getenv("APPDATA")
if appData != "" {
dirs = append(dirs,
filepath.Join(appData, "Code", "User", "settings.json"),
filepath.Join(appData, "Code - Insiders", "User", "settings.json"),
filepath.Join(appData, "VSCodium", "User", "settings.json"),
)
}
default:
// Fallback: try common Linux layout
home, _ := os.UserHomeDir()
if home != "" {
dirs = append(dirs, filepath.Join(home, ".config", "Code", "User", "settings.json"))
}
}
return dirs
}
// -----------------------------
// File loader
// -----------------------------
func readPackageManagerFromFile(path string) (string, bool) {
b, err := os.ReadFile(path)
if err != nil {
return "", false
}
clean := utils.ConvertJsoncToJson(b)
var s VSCodeSettings
if err := json.Unmarshal([]byte(clean), &s); err != nil {
return "", false
}
exe, ok := normalizePM(s.NPMPackageManager)
if !ok {
return "", false
}
return exe, true
}
func normalizePM(v string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(v)) {
case "npm":
return "npm", true
case "yarn":
return "yarn", true
case "pnpm":
return "pnpm", true
case "bun":
return "bun", true
default:
return "", false
}
}
// detectPackageManagerFromPackageJSON reads the "packageManager" field from
// package.json (the corepack standard, e.g. "pnpm@8.15.0") and returns the
// executable name if recognized.
func detectPackageManagerFromPackageJSON(cwd string) (string, bool) {
path := filepath.Join(cwd, "package.json")
b, err := os.ReadFile(path)
if err != nil {
return "", false
}
var pkg struct {
PackageManager string `json:"packageManager"`
}
if err := json.Unmarshal(b, &pkg); err != nil {
return "", false
}
// The field value is "name@version", e.g. "pnpm@8.15.0" or just "pnpm".
name := strings.TrimSpace(pkg.PackageManager)
if i := strings.IndexByte(name, '@'); i > 0 {
name = name[:i]
}
return normalizePM(name)
}
func ResolvePackageManagerExecutable(cwd string, defaultExe string) string {
// 1) VS Code settings take highest priority (explicit user preference).
if exe, ok := detectPackageManagerFromSettings(cwd); ok {
return exe
}
// 2) package.json "packageManager" field (corepack standard).
if exe, ok := detectPackageManagerFromPackageJSON(cwd); ok {
return exe
}
if defaultExe == "" {
defaultExe = "npm"
}
return defaultExe
}