@@ -26,13 +26,67 @@ import (
2626 "os"
2727 "os/exec"
2828 "path/filepath"
29+ "regexp"
30+ "runtime"
2931 "strings"
3032 "time"
3133
3234 "github.com/apache/skywalking-eyes/pkg/license"
3335 "github.com/apache/skywalking-eyes/pkg/logger"
3436)
3537
38+ // Constants for architecture names to avoid string duplication
39+ const (
40+ archAMD64 = "amd64"
41+ archARM64 = "arm64"
42+ archARM = "arm"
43+ )
44+
45+ // Cross-platform package pattern recognition (for precise matching)
46+ // These patterns work for both scoped (@scope/package-platform-arch) and
47+ // non-scoped (package-platform-arch) npm packages, as the platform/arch
48+ // suffix always appears at the end of the package name.
49+ // Examples:
50+ // - Scoped: @scope/foo-linux-x64
51+ // - Non-scoped: foo-linux-x64
52+ //
53+ // regex: matches package names ending with a specific string (e.g., "-linux-x64")
54+ // os: target operating system (e.g., "linux", "darwin", "windows")
55+ // arch: target CPU architecture (e.g., "x64", "arm64")
56+ var platformPatterns = []struct {
57+ regex * regexp.Regexp
58+ os string
59+ arch string
60+ }{
61+ // Android
62+ {regexp .MustCompile (`-android-arm64$` ), "android" , archARM64 },
63+ {regexp .MustCompile (`-android-arm$` ), "android" , archARM },
64+ {regexp .MustCompile (`-android-x64$` ), "android" , "x64" },
65+
66+ // Darwin (macOS)
67+ {regexp .MustCompile (`-darwin-arm64$` ), "darwin" , archARM64 },
68+ {regexp .MustCompile (`-darwin-x64$` ), "darwin" , "x64" },
69+
70+ // Linux
71+ {regexp .MustCompile (`-linux-arm64-glibc$` ), "linux" , archARM64 },
72+ {regexp .MustCompile (`-linux-arm64-musl$` ), "linux" , archARM64 },
73+ {regexp .MustCompile (`-linux-arm-glibc$` ), "linux" , archARM },
74+ {regexp .MustCompile (`-linux-arm-musl$` ), "linux" , archARM },
75+ {regexp .MustCompile (`-linux-x64-glibc$` ), "linux" , "x64" },
76+ {regexp .MustCompile (`-linux-x64-musl$` ), "linux" , "x64" },
77+ {regexp .MustCompile (`-linux-x64$` ), "linux" , "x64" },
78+ {regexp .MustCompile (`-linux-arm64$` ), "linux" , archARM64 },
79+ {regexp .MustCompile (`-linux-arm$` ), "linux" , archARM },
80+
81+ // Windows
82+ {regexp .MustCompile (`-win32-arm64$` ), "windows" , archARM64 },
83+ {regexp .MustCompile (`-win32-ia32$` ), "windows" , "ia32" },
84+ {regexp .MustCompile (`-win32-x64$` ), "windows" , "x64" },
85+
86+ // FreeBSD
87+ {regexp .MustCompile (`-freebsd-x64$` ), "freebsd" , "x64" },
88+ }
89+
3690type NpmResolver struct {
3791 Resolver
3892}
@@ -87,6 +141,8 @@ func (resolver *NpmResolver) Resolve(pkgFile string, config *ConfigDeps, report
87141 for _ , pkg := range pkgs {
88142 if result := resolver .ResolvePackageLicense (pkg .Name , pkg .Path , config ); result .LicenseSpdxID != "" {
89143 report .Resolve (result )
144+ } else if result .IsCrossPlatform {
145+ logger .Log .Warnf ("Skipping cross-platform package %s (not for current platform %s %s)" , pkg .Name , runtime .GOOS , runtime .GOARCH )
90146 } else {
91147 result .LicenseSpdxID = Unknown
92148 report .Skip (result )
@@ -198,6 +254,12 @@ func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string, conf
198254 result := & Result {
199255 Dependency : pkgName ,
200256 }
257+
258+ if ! resolver .isForCurrentPlatform (pkgName ) {
259+ result .IsCrossPlatform = true
260+ return result
261+ }
262+
201263 // resolve from the package.json file
202264 if err := resolver .ResolvePkgFile (result , pkgPath , config ); err != nil {
203265 result .ResolveErrors = append (result .ResolveErrors , err )
@@ -318,3 +380,82 @@ func (resolver *NpmResolver) ParsePkgFile(pkgFile string) (*Package, error) {
318380 }
319381 return & packageInfo , nil
320382}
383+
384+ // normalizeArch converts various architecture aliases into Go's canonical naming.
385+ func normalizeArch (arch string ) string {
386+ // Convert to lowercase to handle case variations (e.g., "AMD64").
387+ arch = strings .ToLower (arch )
388+ switch arch {
389+ // x86-64 family (64-bit Intel/AMD)
390+ case "x64" , "x86_64" , "amd64" , "x86-64" :
391+ return archAMD64
392+ // x86 32-bit family (legacy)
393+ case "ia32" , "x86" , "386" , "i386" , "i686" :
394+ return "386"
395+ // ARM 64-bit
396+ case "arm64" , "aarch64" :
397+ return archARM64
398+ // ARM 32-bit
399+ case "arm" , "armv7" , "armhf" , "armv7l" , "armel" :
400+ return archARM
401+ // Unknown architecture: return as-is (alternatively, could return empty to indicate incompatibility).
402+ default :
403+ return arch
404+ }
405+ }
406+
407+ // analyzePackagePlatform extracts the target OS and architecture from a package name.
408+ func (resolver * NpmResolver ) analyzePackagePlatform (pkgName string ) (pkgOS , pkgArch string , partial bool ) {
409+ for _ , pattern := range platformPatterns {
410+ if pattern .regex .MatchString (pkgName ) {
411+ return pattern .os , pattern .arch , false
412+ }
413+ }
414+
415+ // Detect OS-only suffixes like "-linux", "-darwin", "-win32"
416+ osOnlyPatterns := []string {
417+ "-linux" ,
418+ "-darwin" ,
419+ "-win32" ,
420+ "-windows" ,
421+ "-android" ,
422+ "-freebsd" ,
423+ }
424+ for _ , osSuffix := range osOnlyPatterns {
425+ if strings .HasSuffix (pkgName , osSuffix ) {
426+ return strings .TrimPrefix (osSuffix , "-" ), "" , true
427+ }
428+ }
429+
430+ return "" , "" , false
431+ }
432+
433+ // isForCurrentPlatform checks whether the package is intended for the current OS and architecture.
434+ func (resolver * NpmResolver ) isForCurrentPlatform (pkgName string ) bool {
435+ pkgOS , pkgArch , partial := resolver .analyzePackagePlatform (pkgName )
436+
437+ // OS-only package: explicitly reject with friendly error
438+ if partial {
439+ logger .Log .Warnf (
440+ "Package %q declares a platform without architecture. " +
441+ "Please use a full platform-arch suffix (e.g. -linux-x64, -darwin-arm64)." ,
442+ pkgName ,
443+ )
444+ return false
445+ }
446+
447+ // Universal package
448+ if pkgOS == "" && pkgArch == "" {
449+ return true
450+ }
451+
452+ currentOS := runtime .GOOS
453+ currentArch := runtime .GOARCH
454+
455+ return pkgOS == currentOS && resolver .isArchCompatible (pkgArch , currentArch )
456+ }
457+
458+ // isArchCompatible determines whether the package's architecture is compatible with the current machine's architecture.
459+ func (resolver * NpmResolver ) isArchCompatible (pkgArch , currentArch string ) bool {
460+ return normalizeArch (pkgArch ) == normalizeArch (currentArch )
461+ }
0 commit comments