@@ -26,25 +26,60 @@ public class GenerateStaticWebAssetEndpointsManifest : Task
26
26
27
27
public string CacheFilePath { get ; set ; }
28
28
29
+ public string ExclusionPatterns { get ; set ; }
30
+
31
+ public string ExclusionPatternsCacheFilePath { get ; set ; }
32
+
29
33
public override bool Execute ( )
30
34
{
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 ;
31
40
if ( ! string . IsNullOrEmpty ( CacheFilePath ) && File . Exists ( ManifestPath ) && File . GetLastWriteTimeUtc ( ManifestPath ) > File . GetLastWriteTimeUtc ( CacheFilePath ) )
32
41
{
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 ) ;
35
58
}
36
59
37
60
try
38
61
{
62
+ // Update exclusion patterns cache if needed
63
+ UpdateExclusionPatternsCache ( existingPatternString , patternString ) ;
64
+
39
65
// Get the list of the asset that need to be part of the manifest (this is similar to GenerateStaticWebAssetsDevelopmentManifest)
40
66
var assets = StaticWebAsset . FromTaskItemGroup ( Assets ) ;
41
67
var manifestAssets = ComputeManifestAssets ( assets , ManifestType )
42
68
. ToDictionary ( a => a . ResolvedAsset . Identity , a => a , OSPath . PathComparer ) ;
43
69
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
+
44
79
// Filter out the endpoints to those that point to the assets that are part of the manifest
45
80
var endpoints = StaticWebAssetEndpoint . FromItemGroup ( Endpoints ) ;
46
81
var filteredEndpoints = new List < StaticWebAssetEndpoint > ( ) ;
47
-
82
+ var updatedManifest = false ;
48
83
foreach ( var endpoint in endpoints )
49
84
{
50
85
if ( ! manifestAssets . TryGetValue ( endpoint . AssetFile , out var asset ) )
@@ -53,14 +88,35 @@ public override bool Execute()
53
88
continue ;
54
89
}
55
90
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
+
56
113
filteredEndpoints . Add ( endpoint ) ;
57
114
// Update the endpoint to use the target path of the asset, this will be relative to the wwwroot
58
- var path = endpoint . AssetFile ;
59
115
60
116
endpoint . AssetFile = asset . ResolvedAsset . ComputeTargetPath ( "" , '/' , StaticWebAssetTokenResolver . Instance ) ;
61
- endpoint . Route = asset . ResolvedAsset . ReplaceTokens ( endpoint . Route , StaticWebAssetTokenResolver . Instance ) ;
117
+ endpoint . Route = route ;
62
118
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 ) ;
64
120
}
65
121
66
122
var manifest = new StaticWebAssetEndpointsManifest ( )
@@ -81,6 +137,44 @@ public override bool Execute()
81
137
return ! Log . HasLoggedErrors ;
82
138
}
83
139
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
+
84
178
private IEnumerable < TargetPathAssetPair > ComputeManifestAssets ( IEnumerable < StaticWebAsset > assets , string kind )
85
179
{
86
180
var assetsByTargetPath = assets
0 commit comments