@@ -12,64 +12,119 @@ namespace Microsoft.PowerShell.PSResourceGet.UtilClasses
1212 /// <summary>
1313 /// Helper class for parsing package runtime assets and filtering during extraction.
1414 /// Provides functionality to filter runtime-specific assets based on the current platform's RID.
15+ /// Detects root-level RID folders (e.g., win-x64/native.dll) used by PowerShell modules
16+ /// with platform-specific native dependencies.
1517 /// </summary>
1618 internal static class RuntimePackageHelper
1719 {
1820 #region Constants
1921
2022 /// <summary>
21- /// The name of the runtimes folder in NuGet packages .
23+ /// Path separator used in zip archives .
2224 /// </summary>
23- private const string RuntimesFolderName = "runtimes" ;
25+ private const char ZipPathSeparator = '/' ;
2426
2527 /// <summary>
26- /// Path separator used in zip archives .
28+ /// Known OS prefixes used in .NET Runtime Identifiers .
2729 /// </summary>
28- private const char ZipPathSeparator = '/' ;
30+ private static readonly string [ ] s_knownOsPrefixes = new [ ]
31+ {
32+ "win" , "linux" , "osx" , "unix" , "maccatalyst" , "browser"
33+ } ;
34+
35+ /// <summary>
36+ /// Known architectures used in .NET Runtime Identifiers.
37+ /// </summary>
38+ private static readonly string [ ] s_knownArchitectures = new [ ]
39+ {
40+ "loongarch64" , "ppc64le" , "mips64" , "s390x" , "arm64" , "armel" , "wasm" , "arm" , "x64" , "x86"
41+ } ;
2942
3043 #endregion
3144
3245 #region Public Methods
3346
3447 /// <summary>
35- /// Checks if a zip entry path is within the runtimes folder.
48+ /// Checks if a folder name looks like a .NET Runtime Identifier.
49+ /// Matches patterns like: win-x64, linux-arm64, osx-arm64, linux-musl-x64, etc.
50+ /// </summary>
51+ /// <param name="folderName">The folder name to check.</param>
52+ /// <returns>True if the folder name matches a RID pattern; otherwise, false.</returns>
53+ public static bool IsRidFolder ( string folderName )
54+ {
55+ if ( string . IsNullOrEmpty ( folderName ) || ! folderName . Contains ( "-" ) )
56+ {
57+ return false ;
58+ }
59+
60+ // Must start with a known OS prefix
61+ bool startsWithKnownOs = false ;
62+ foreach ( string prefix in s_knownOsPrefixes )
63+ {
64+ if ( folderName . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
65+ {
66+ startsWithKnownOs = true ;
67+ break ;
68+ }
69+ }
70+
71+ if ( ! startsWithKnownOs )
72+ {
73+ return false ;
74+ }
75+
76+ // Must end with a known architecture
77+ string [ ] parts = folderName . Split ( '-' ) ;
78+ string lastPart = parts [ parts . Length - 1 ] ;
79+ foreach ( string arch in s_knownArchitectures )
80+ {
81+ if ( string . Equals ( lastPart , arch , StringComparison . OrdinalIgnoreCase ) )
82+ {
83+ return true ;
84+ }
85+ }
86+
87+ return false ;
88+ }
89+
90+ /// <summary>
91+ /// Checks if a zip entry path is under a root-level RID folder.
92+ /// Detects entries like win-x64/native.dll, linux-arm64/libfoo.so, etc.
3693 /// </summary>
3794 /// <param name="entryFullName">The full path of the zip entry.</param>
38- /// <returns>True if the entry is in the runtimes folder; otherwise, false.</returns>
95+ /// <returns>True if the entry is under a RID folder; otherwise, false.</returns>
3996 public static bool IsRuntimesEntry ( string entryFullName )
4097 {
4198 if ( string . IsNullOrEmpty ( entryFullName ) )
4299 {
43100 return false ;
44101 }
45102
46- // Normalize path separators for comparison
47103 string normalizedPath = entryFullName . Replace ( '\\ ' , ZipPathSeparator ) ;
48-
49- return normalizedPath . StartsWith ( RuntimesFolderName + ZipPathSeparator , StringComparison . OrdinalIgnoreCase ) ;
104+ string [ ] segments = normalizedPath . Split ( ZipPathSeparator ) ;
105+
106+ // Pattern: {rid}/... (root-level RID folders like win-x64/native.dll)
107+ return segments . Length >= 2 && IsRidFolder ( segments [ 0 ] ) ;
50108 }
51109
52110 /// <summary>
53- /// Extracts the RID from a runtimes folder entry path.
111+ /// Extracts the RID from a root-level RID folder entry path.
54112 /// </summary>
55- /// <param name="entryFullName">The full path of the zip entry (e.g., "runtimes/ win-x64/native/file .dll").</param>
56- /// <returns>The RID (e.g., "win-x64"), or null if not a runtimes entry .</returns>
113+ /// <param name="entryFullName">The full path of the zip entry (e.g., "win-x64/native.dll").</param>
114+ /// <returns>The RID (e.g., "win-x64"), or null if not under a RID folder .</returns>
57115 public static string GetRidFromRuntimesEntry ( string entryFullName )
58116 {
59- if ( ! IsRuntimesEntry ( entryFullName ) )
117+ if ( string . IsNullOrEmpty ( entryFullName ) )
60118 {
61119 return null ;
62120 }
63121
64- // Normalize path separators
65122 string normalizedPath = entryFullName . Replace ( '\\ ' , ZipPathSeparator ) ;
66-
67- // Path format: runtimes/{rid}/...
68123 string [ ] parts = normalizedPath . Split ( ZipPathSeparator ) ;
69-
70- if ( parts . Length >= 2 )
124+
125+ if ( parts . Length >= 2 && IsRidFolder ( parts [ 0 ] ) )
71126 {
72- return parts [ 1 ] ; // The RID is the second segment
127+ return parts [ 0 ] ;
73128 }
74129
75130 return null ;
@@ -82,21 +137,18 @@ public static string GetRidFromRuntimesEntry(string entryFullName)
82137 /// <returns>True if the entry should be included; otherwise, false.</returns>
83138 public static bool ShouldIncludeEntry ( string entryFullName )
84139 {
85- // Non-runtimes entries are always included
86140 if ( ! IsRuntimesEntry ( entryFullName ) )
87141 {
88142 return true ;
89143 }
90144
91145 string entryRid = GetRidFromRuntimesEntry ( entryFullName ) ;
92-
146+
93147 if ( string . IsNullOrEmpty ( entryRid ) )
94148 {
95- // If we can't determine the RID, include the entry to be safe
96149 return true ;
97150 }
98151
99- // Check if this RID is compatible with the current platform
100152 return RuntimeIdentifierHelper . IsCompatibleRid ( entryRid ) ;
101153 }
102154
@@ -109,25 +161,24 @@ public static bool ShouldIncludeEntry(string entryFullName)
109161 /// <returns>True if the entry should be included; otherwise, false.</returns>
110162 public static bool ShouldIncludeEntry ( string entryFullName , string targetRid )
111163 {
112- // Non-runtimes entries are always included
113164 if ( ! IsRuntimesEntry ( entryFullName ) )
114165 {
115166 return true ;
116167 }
117168
118169 string entryRid = GetRidFromRuntimesEntry ( entryFullName ) ;
119-
170+
120171 if ( string . IsNullOrEmpty ( entryRid ) )
121172 {
122173 return true ;
123174 }
124175
125- // Check if this RID is compatible with the specified target
126176 return RuntimeIdentifierHelper . IsCompatibleRid ( entryRid , targetRid ) ;
127177 }
128178
129179 /// <summary>
130- /// Gets a list of all unique RIDs present in a zip archive's runtimes folder.
180+ /// Gets a list of all unique RIDs present in a zip archive.
181+ /// Detects both runtimes/{rid}/ and root-level {rid}/ patterns.
131182 /// </summary>
132183 /// <param name="archive">The zip archive to scan.</param>
133184 /// <returns>A list of unique RIDs found in the archive.</returns>
@@ -153,7 +204,7 @@ public static IReadOnlyList<string> GetAvailableRidsFromArchive(ZipArchive archi
153204 }
154205
155206 /// <summary>
156- /// Gets a list of all unique RIDs present in a zip file's runtimes folder .
207+ /// Gets a list of all unique RIDs present in a zip file.
157208 /// </summary>
158209 /// <param name="zipPath">The path to the zip file.</param>
159210 /// <returns>A list of unique RIDs found in the archive.</returns>
0 commit comments