Files
goclaw/internal/skills/npm_executable_aliases.go

114 lines
2.6 KiB
Go

package skills
import (
"encoding/json"
"os"
"path/filepath"
"strings"
)
type npmPackageBinManifest struct {
Name string `json:"name"`
Bin json.RawMessage `json:"bin"`
}
func findNpmPackageExecutableAlias(name string) (string, bool) {
packageDirs, err := npmGlobalPackageDirs()
if err != nil {
return "", false
}
for _, packageDir := range packageDirs {
raw, err := os.ReadFile(filepath.Join(packageDir, "package.json"))
if err != nil {
continue
}
var manifest npmPackageBinManifest
if err := json.Unmarshal(raw, &manifest); err != nil {
continue
}
if !npmPackageNameMatchesExecutableAlias(manifest.Name, name) {
continue
}
binName, ok := singleNpmBinName(manifest)
if !ok {
continue
}
path := filepath.Join(npmGlobalBinDir(), binName)
if IsExecutableFile(path) {
return path, true
}
}
return "", false
}
func npmGlobalPackageDirs() ([]string, error) {
root := filepath.Join(npmGlobalPrefix(), "lib", "node_modules")
entries, err := os.ReadDir(root)
if err != nil {
return nil, err
}
dirs := make([]string, 0, len(entries))
for _, entry := range entries {
if !entry.IsDir() {
continue
}
path := filepath.Join(root, entry.Name())
if strings.HasPrefix(entry.Name(), "@") {
scoped, err := os.ReadDir(path)
if err != nil {
continue
}
for _, scopedEntry := range scoped {
if scopedEntry.IsDir() {
dirs = append(dirs, filepath.Join(path, scopedEntry.Name()))
}
}
continue
}
dirs = append(dirs, path)
}
return dirs, nil
}
func npmPackageNameMatchesExecutableAlias(packageName, executableName string) bool {
base := npmPackageBaseName(packageName)
if base == "" {
return false
}
if base == executableName {
return true
}
return strings.TrimSuffix(base, "-cli") == executableName
}
func npmPackageBaseName(packageName string) string {
packageName = strings.TrimSpace(packageName)
if packageName == "" {
return ""
}
if slash := strings.LastIndexByte(packageName, '/'); slash >= 0 {
return packageName[slash+1:]
}
return packageName
}
func singleNpmBinName(manifest npmPackageBinManifest) (string, bool) {
if len(manifest.Bin) == 0 || string(manifest.Bin) == "null" {
return "", false
}
var binPath string
if err := json.Unmarshal(manifest.Bin, &binPath); err == nil {
return npmPackageBaseName(manifest.Name), strings.TrimSpace(binPath) != ""
}
var bins map[string]string
if err := json.Unmarshal(manifest.Bin, &bins); err != nil || len(bins) != 1 {
return "", false
}
for name, path := range bins {
if strings.TrimSpace(name) != "" && strings.TrimSpace(path) != "" {
return name, true
}
}
return "", false
}