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 f08bfd6  fix(agents): find npm agents installed into an nvm-managed 
Node
f08bfd6 is described below

commit f08bfd6c503f1b4b4638a42690f5c293a1917b9b
Author: Yang Luo <[email protected]>
AuthorDate: Fri Sep 4 21:33:13 2026 +0800

    fix(agents): find npm agents installed into an nvm-managed Node
---
 agent/fingerprints/dsh.json |   1 +
 agent/scan_windows.go       |   3 +
 agent/scan_windows_node.go  | 138 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 142 insertions(+)

diff --git a/agent/fingerprints/dsh.json b/agent/fingerprints/dsh.json
index 663fe44..5255ae7 100644
--- a/agent/fingerprints/dsh.json
+++ b/agent/fingerprints/dsh.json
@@ -4,5 +4,6 @@
   "installUrl": "https://github.com/deepseek-ai/deepseek-harness";,
   "execName": "dsh",
   "launchArgs": ["web"],
+  "stateDir": "dsh",
   "npmPackage": "@deepseek-ai/dsh"
 }
diff --git a/agent/scan_windows.go b/agent/scan_windows.go
index e61609e..f40d24d 100644
--- a/agent/scan_windows.go
+++ b/agent/scan_windows.go
@@ -38,6 +38,8 @@ type homeDir struct {
 func scan(ctx context.Context) []Installation {
        homes := windowsHomes(ctx)
        paths := newPathIndex()
+       nodeRoots := windowsNodeRoots()
+       nodeOwner := currentOwner()
 
        var installations []Installation
        for i := range fingerprints {
@@ -59,6 +61,7 @@ func scan(ctx context.Context) []Installation {
                installations = append(installations, scanWindowsDesktop(ctx, 
fingerprint, homes)...)
                installations = append(installations, scanMachineWinget(ctx, 
fingerprint)...)
                installations = append(installations, scanMachinePrograms(ctx, 
fingerprint)...)
+               installations = append(installations, scanMachineNpm(ctx, 
fingerprint, nodeRoots, nodeOwner)...)
                installations = append(installations, scanPathDirs(fingerprint, 
paths, installations[mark:])...)
                stampAgentId(installations, mark, fingerprint.ID)
                installations = append(installations, 
scanStateDirs(fingerprint, homes, installations[mark:])...)
diff --git a/agent/scan_windows_node.go b/agent/scan_windows_node.go
new file mode 100644
index 0000000..72a3e74
--- /dev/null
+++ b/agent/scan_windows_node.go
@@ -0,0 +1,138 @@
+// Copyright 2026 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build windows
+
+package agent
+
+import (
+       "context"
+       "os"
+       "path/filepath"
+       "strings"
+)
+
+// maxNvmSettingsSize limits reads from an nvm settings file.
+const maxNvmSettingsSize = 64 * 1024
+
+// scanMachineNpm reports a package installed into a Node that lives outside
+// every profile, which the per-home npm layouts never reach.
+func scanMachineNpm(ctx context.Context, fingerprint *Fingerprint, roots 
[]string, owner string) []Installation {
+       pkg := fingerprint.npmPackagePath()
+       patterns := make([]string, 0, len(roots))
+       for _, root := range roots {
+               patterns = append(patterns, filepath.Join(root, "node_modules", 
pkg, "package.json"))
+       }
+       return scanNpmPatterns(ctx, fingerprint, patterns, owner, nil)
+}
+
+// windowsNodeRoots are the global package directories of every Node installed
+// outside a user profile. nvm-windows is why they exist: it keeps one 
directory
+// per version it manages, current installers put those under ProgramData, and 
a
+// global npm install lands in whichever version is in use rather than in any
+// AppData prefix.
+func windowsNodeRoots() []string {
+       nvmDirs := []string{os.Getenv("NVM_HOME")}
+       for _, base := range []string{
+               os.Getenv("ProgramData"), os.Getenv("ALLUSERSPROFILE"),
+               os.Getenv("ProgramFiles"), os.Getenv("ProgramFiles(x86)"),
+               os.Getenv("LOCALAPPDATA"), os.Getenv("APPDATA"),
+       } {
+               if base != "" {
+                       nvmDirs = append(nvmDirs, filepath.Join(base, "nvm"))
+               }
+       }
+
+       roots := []string{os.Getenv("NVM_SYMLINK")}
+       for _, dir := range pathDirs() {
+               // A version manager on PATH is on it as its own root, and a 
Node there
+               // is one no layout above describes: unpacked by hand, or put 
there by a
+               // manager Gateway does not know.
+               if isFile(filepath.Join(dir, "nvm.exe")) {
+                       nvmDirs = append(nvmDirs, dir)
+               }
+               if isFile(filepath.Join(dir, "node.exe")) {
+                       roots = append(roots, dir)
+               }
+       }
+       for _, dir := range nvmDirs {
+               roots = append(roots, nvmNodeDirs(dir)...)
+       }
+       return existingDirs(roots)
+}
+
+// nvmNodeDirs are the Node directories one nvm-windows install holds: one per
+// version, plus the link the version in use is exposed through. A root the
+// install was moved to is recorded in the settings file beside it.
+func nvmNodeDirs(dir string) []string {
+       if dir == "" {
+               return nil
+       }
+       root, symlink := nvmSettings(filepath.Join(dir, "settings.txt"))
+       if root == "" {
+               root = dir
+       }
+       versions, _ := filepath.Glob(filepath.Join(root, "*"))
+       return append(versions, symlink)
+}
+
+// nvmSettings reads the root and the symlink an nvm-windows install records.
+func nvmSettings(path string) (root, symlink string) {
+       info, err := os.Stat(path)
+       if err != nil || !info.Mode().IsRegular() || info.Size() > 
maxNvmSettingsSize {
+               return "", ""
+       }
+       data, err := os.ReadFile(path)
+       if err != nil {
+               return "", ""
+       }
+       for _, line := range strings.Split(string(data), "\n") {
+               key, value, ok := strings.Cut(line, ":")
+               if !ok {
+                       continue
+               }
+               switch strings.ToLower(strings.TrimSpace(key)) {
+               case "root":
+                       root = strings.TrimSpace(value)
+               case "path":
+                       symlink = strings.TrimSpace(value)
+               }
+       }
+       return root, symlink
+}
+
+// existingDirs keeps the absolute directories of a candidate list, once each.
+func existingDirs(paths []string) []string {
+       seen := map[string]bool{}
+       var result []string
+       for _, path := range paths {
+               path = strings.Trim(strings.TrimSpace(path), `"`)
+               if path == "" {
+                       continue
+               }
+               path = filepath.Clean(path)
+               if !filepath.IsAbs(path) {
+                       continue
+               }
+               key := strings.ToLower(path)
+               if seen[key] {
+                       continue
+               }
+               seen[key] = true
+               if info, err := os.Stat(path); err == nil && info.IsDir() {
+                       result = append(result, path)
+               }
+       }
+       return result
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to