This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
The following commit(s) were added to refs/heads/master by this push:
new 576f902 fix(agent): find the launcher a package manager keeps outside
the tree
576f902 is described below
commit 576f902801a9a609220e727bbf1a8e20f3dd9737
Author: Yang Luo <[email protected]>
AuthorDate: Sun Aug 23 17:27:52 2026 +0800
fix(agent): find the launcher a package manager keeps outside the tree
---
agent/launch.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++----
agent/scan.go | 2 +-
agent/scan_common.go | 24 ++++++++++++
3 files changed, 118 insertions(+), 9 deletions(-)
diff --git a/agent/launch.go b/agent/launch.go
index 236663d..1ac9e52 100644
--- a/agent/launch.go
+++ b/agent/launch.go
@@ -55,16 +55,23 @@ func executableOf(path, execName string) string {
return ""
}
- root := nodeModulesRoot(path)
- if root == "" {
- return ""
- }
- for _, candidate := range npmShims(root, execName) {
- if isFile(candidate) {
- return candidate
+ if root := nodeModulesRoot(path); root != "" {
+ for _, candidate := range npmShims(root, execName) {
+ if isFile(candidate) {
+ return candidate
+ }
+ }
+ for _, dir := range managerBinDirs(root) {
+ for _, candidate := range shimNames(dir, execName) {
+ if isFile(candidate) {
+ return candidate
+ }
+ }
}
}
- return ""
+ // Last, the program the shim would have run: an upgraded or
half-removed
+ // package manager leaves the tree without its shim.
+ return packagedExecutable(path, execName)
}
func isFile(path string) bool {
@@ -105,3 +112,81 @@ func npmShims(root, execName string) []string {
}
return []string{filepath.Join(prefix, "bin", execName), local}
}
+
+// managerBinDirs are the shim directories of the package managers that keep
one
+// for every global package instead of writing beside the tree: pnpm's home and
+// Volta's bin, both recognisable from the package path itself.
+func managerBinDirs(root string) []string {
+ var dirs []string
+ for dir := filepath.Clean(root); ; {
+ parent := filepath.Dir(dir)
+ if parent == dir {
+ return dirs
+ }
+ switch strings.ToLower(filepath.Base(dir)) {
+ case "pnpm":
+ dirs = append(dirs, dir)
+ case "volta":
+ dirs = append(dirs, filepath.Join(dir, "bin"))
+ }
+ dir = parent
+ }
+}
+
+// shimNames are the launcher file names one directory may hold for an agent.
+func shimNames(dir, execName string) []string {
+ if runtime.GOOS == "windows" {
+ return []string{
+ filepath.Join(dir, execName+".cmd"),
+ filepath.Join(dir, execName+".exe"),
+ filepath.Join(dir, execName+".bat"),
+ }
+ }
+ return []string{filepath.Join(dir, execName)}
+}
+
+// packagedExecutable finds the program inside a package tree: npm packages of
an
+// agent written in a compiled language carry one build per platform, either
+// beside the entry script or under a vendor directory.
+func packagedExecutable(path, execName string) string {
+ var patterns []string
+ for _, dir := range []string{path, filepath.Join(path, "bin"),
+ filepath.Join(path, "vendor", "*"), filepath.Join(path,
"vendor", "*", "*")} {
+ patterns = append(patterns, filepath.Join(dir, execName+"*"))
+ }
+
+ for _, pattern := range patterns {
+ matches, _ := filepath.Glob(pattern)
+ for _, candidate := range matches {
+ if isRunnable(candidate, execName) {
+ return candidate
+ }
+ }
+ }
+ return ""
+}
+
+// isRunnable reports whether a file inside a package tree is the agent itself
+// rather than one of the scripts or data files sitting beside it.
+func isRunnable(path, execName string) bool {
+ name := filepath.Base(path)
+ extension := filepath.Ext(name)
+ stem := strings.ToLower(strings.TrimSuffix(name, extension))
+ if stem != strings.ToLower(execName) && !strings.HasPrefix(stem,
strings.ToLower(execName)+"-") {
+ return false
+ }
+
+ info, err := os.Stat(path)
+ if err != nil || !info.Mode().IsRegular() {
+ return false
+ }
+ extension = strings.ToLower(extension)
+ if runtime.GOOS == "windows" {
+ return extension == ".exe" || extension == ".cmd" || extension
== ".bat"
+ }
+ switch extension {
+ case ".js", ".mjs", ".cjs", ".ts", ".json", ".md":
+ return false
+ }
+ return info.Mode()&0o111 != 0
+}
diff --git a/agent/scan.go b/agent/scan.go
index 944ba6a..6ac0fc1 100644
--- a/agent/scan.go
+++ b/agent/scan.go
@@ -86,7 +86,7 @@ func scanWithTimeout() ([]Installation, error) {
if err := ctx.Err(); err != nil {
return nil, fmt.Errorf("agent scan timed out after %s:
%w", scanTimeout, err)
}
- return installations, nil
+ return dropUnlaunchableTrees(installations), nil
case <-ctx.Done():
return nil, fmt.Errorf("agent scan timed out after %s: %w",
scanTimeout, ctx.Err())
}
diff --git a/agent/scan_common.go b/agent/scan_common.go
index b25c12d..ed7e483 100644
--- a/agent/scan_common.go
+++ b/agent/scan_common.go
@@ -192,6 +192,30 @@ func dedupeInstallations(installations []Installation)
[]Installation {
return result
}
+// dropUnlaunchableTrees removes an installation nothing can start when the
same
+// agent is installed elsewhere with a launcher. A package manager that
upgrades
+// or half-removes itself leaves the package directory behind without the shim
+// that ran it, and that leftover is not an installation the user can use.
+func dropUnlaunchableTrees(installations []Installation) []Installation {
+ launchable := map[string]bool{}
+ resolved := make([]bool, len(installations))
+ for i, installation := range installations {
+ resolved[i] = LaunchOf(installation).Executable != ""
+ if resolved[i] {
+ launchable[installation.AgentId] = true
+ }
+ }
+
+ result := make([]Installation, 0, len(installations))
+ for i, installation := range installations {
+ if !resolved[i] && launchable[installation.AgentId] {
+ continue
+ }
+ result = append(result, installation)
+ }
+ return result
+}
+
func canonicalPath(path string) string {
if resolved, err := filepath.EvalSymlinks(path); err == nil {
return resolved
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]