@@ -26,25 +26,60 @@ public class GenerateStaticWebAssetEndpointsManifest : Task
2626
2727 public string CacheFilePath { get ; set ; }
2828
29+ public string ExclusionPatterns { get ; set ; }
30+
31+ public string ExclusionPatternsCacheFilePath { get ; set ; }
32+
2933 public override bool Execute ( )
3034 {
35+ var ( patternString , parsedPatterns ) = ParseAndSortPatterns ( ExclusionPatterns ) ;
36+ var existingPatternString = ! string . IsNullOrEmpty ( ExclusionPatternsCacheFilePath ) && File . Exists ( ExclusionPatternsCacheFilePath )
37+ ? File . ReadAllText ( ExclusionPatternsCacheFilePath )
38+ : null ;
39+ existingPatternString = string . IsNullOrEmpty ( existingPatternString ) ? null : existingPatternString ;
3140 if ( ! string . IsNullOrEmpty ( CacheFilePath ) && File . Exists ( ManifestPath ) && File . GetLastWriteTimeUtc ( ManifestPath ) > File . GetLastWriteTimeUtc ( CacheFilePath ) )
3241 {
33- Log . LogMessage ( MessageImportance . Low , "Skipping manifest generation because manifest file '{0}' is up to date." , ManifestPath ) ;
34- return true ;
42+ // Check if exclusion patterns cache is also up to date
43+ if ( string . Equals ( patternString , existingPatternString , StringComparison . Ordinal ) )
44+ {
45+ Log . LogMessage ( MessageImportance . Low , "Skipping manifest generation because manifest file '{0}' is up to date." , ManifestPath ) ;
46+ return true ;
47+ }
48+ else
49+ {
50+ Log . LogMessage ( MessageImportance . Low , "Generating manifest file '{0}' because exclusion patterns changed from '{1}' to '{2}'." , ManifestPath ,
51+ existingPatternString ?? "no patterns" ,
52+ patternString ?? "no patterns" ) ;
53+ }
54+ }
55+ else
56+ {
57+ Log . LogMessage ( MessageImportance . Low , "Generating manifest file '{0}' because manifest file is missing or out of date." , ManifestPath ) ;
3558 }
3659
3760 try
3861 {
62+ // Update exclusion patterns cache if needed
63+ UpdateExclusionPatternsCache ( existingPatternString , patternString ) ;
64+
3965 // Get the list of the asset that need to be part of the manifest (this is similar to GenerateStaticWebAssetsDevelopmentManifest)
4066 var assets = StaticWebAsset . FromTaskItemGroup ( Assets ) ;
4167 var manifestAssets = ComputeManifestAssets ( assets , ManifestType )
4268 . ToDictionary ( a => a . ResolvedAsset . Identity , a => a , OSPath . PathComparer ) ;
4369
70+ // Build exclusion matcher if patterns are provided
71+ StaticWebAssetGlobMatcher exclusionMatcher = null ;
72+ if ( parsedPatterns . Length > 0 )
73+ {
74+ var builder = new StaticWebAssetGlobMatcherBuilder ( ) ;
75+ builder . AddIncludePatternsList ( parsedPatterns ) ;
76+ exclusionMatcher = builder . Build ( ) ;
77+ }
78+
4479 // Filter out the endpoints to those that point to the assets that are part of the manifest
4580 var endpoints = StaticWebAssetEndpoint . FromItemGroup ( Endpoints ) ;
4681 var filteredEndpoints = new List < StaticWebAssetEndpoint > ( ) ;
47-
82+ var updatedManifest = false ;
4883 foreach ( var endpoint in endpoints )
4984 {
5085 if ( ! manifestAssets . TryGetValue ( endpoint . AssetFile , out var asset ) )
@@ -53,14 +88,35 @@ public override bool Execute()
5388 continue ;
5489 }
5590
91+ // Check if endpoint should be excluded based on patterns
92+ var route = asset . ResolvedAsset . ReplaceTokens ( endpoint . Route , StaticWebAssetTokenResolver . Instance ) ;
93+ if ( exclusionMatcher != null )
94+ {
95+ var match = exclusionMatcher . Match ( route ) ;
96+ if ( match . IsMatch )
97+ {
98+ if ( ! updatedManifest && File . Exists ( ManifestPath ) )
99+ {
100+ updatedManifest = true ;
101+ // Touch the manifest if we are excluding endpoints to ensure we don't keep reporting out of date
102+ // for the excluded endpoints.
103+ // (The SWA manifest we use as cache might get updated, but if we filter out the new endpoints, we won't
104+ // update the endpoints manifest file and on the next build we will re-enter this loop).
105+ Log . LogMessage ( MessageImportance . Low , "Updating manifest timestamp '{0}'." , ManifestPath ) ;
106+ File . SetLastWriteTime ( ManifestPath , DateTime . UtcNow ) ;
107+ }
108+ Log . LogMessage ( MessageImportance . Low , "Excluding endpoint '{0}' based on exclusion patterns" , route ) ;
109+ continue ;
110+ }
111+ }
112+
56113 filteredEndpoints . Add ( endpoint ) ;
57114 // Update the endpoint to use the target path of the asset, this will be relative to the wwwroot
58- var path = endpoint . AssetFile ;
59115
60116 endpoint . AssetFile = asset . ResolvedAsset . ComputeTargetPath ( "" , '/' , StaticWebAssetTokenResolver . Instance ) ;
61- endpoint . Route = asset . ResolvedAsset . ReplaceTokens ( endpoint . Route , StaticWebAssetTokenResolver . Instance ) ;
117+ endpoint . Route = route ;
62118
63- Log . LogMessage ( MessageImportance . Low , "Including endpoint '{0}' for asset '{1}' with final location '{2}'" , endpoint . Route , path , asset . TargetPath ) ;
119+ Log . LogMessage ( MessageImportance . Low , "Including endpoint '{0}' for asset '{1}' with final location '{2}'" , endpoint . Route , endpoint . AssetFile , asset . TargetPath ) ;
64120 }
65121
66122 var manifest = new StaticWebAssetEndpointsManifest ( )
@@ -81,6 +137,44 @@ public override bool Execute()
81137 return ! Log . HasLoggedErrors ;
82138 }
83139
140+ private static ( string , string [ ] ) ParseAndSortPatterns ( string patterns )
141+ {
142+ if ( string . IsNullOrEmpty ( patterns ) )
143+ {
144+ return ( null , [ ] ) ;
145+ }
146+
147+ var parsed = patterns . Split ( [ ';' ] , StringSplitOptions . RemoveEmptyEntries ) ;
148+ Array . Sort ( parsed , StringComparer . OrdinalIgnoreCase ) ;
149+
150+ return ( string . Join ( Environment . NewLine , parsed ) , parsed ) ;
151+ }
152+
153+ private void UpdateExclusionPatternsCache ( string existingPatternString , string patternString )
154+ {
155+ if ( string . IsNullOrEmpty ( ExclusionPatternsCacheFilePath ) )
156+ {
157+ return ;
158+ }
159+
160+ if ( ! File . Exists ( ExclusionPatternsCacheFilePath ) ||
161+ ! string . Equals ( existingPatternString , patternString , StringComparison . Ordinal ) )
162+ {
163+ var directoryName = Path . GetDirectoryName ( ExclusionPatternsCacheFilePath ) ;
164+ if ( directoryName != null )
165+ {
166+ Directory . CreateDirectory ( directoryName ) ;
167+ }
168+ File . WriteAllText ( ExclusionPatternsCacheFilePath , patternString ) ;
169+ // We need to touch the file because otherwise we will keep thinking that is out of date in the future.
170+ // This file might not be rewritten if the results are unchanged.
171+ if ( File . Exists ( ManifestPath ) )
172+ {
173+ File . SetLastWriteTime ( ManifestPath , DateTime . UtcNow ) ;
174+ }
175+ }
176+ }
177+
84178 private IEnumerable < TargetPathAssetPair > ComputeManifestAssets ( IEnumerable < StaticWebAsset > assets , string kind )
85179 {
86180 var assetsByTargetPath = assets
0 commit comments