| File: GeneratePackageAssetsManifestFile.cs | Web Access |
| Project: src\sdk\src\StaticWebAssetsSdk\Tasks\Microsoft.NET.Sdk.StaticWebAssets.Tasks.csproj (Microsoft.NET.Sdk.StaticWebAssets.Tasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using Microsoft.AspNetCore.StaticWebAssets.Tasks.Utils; using Microsoft.Build.Framework; namespace Microsoft.AspNetCore.StaticWebAssets.Tasks; // Generates a JSON manifest file containing both static web asset and endpoint data // for inclusion in a NuGet package. Replaces the pair of XML .props generators // (GenerateStaticWebAssetsPropsFile + GenerateStaticWebAssetEndpointsPropsFile) for // the new .targets-based packaging path (net11.0+). public class GeneratePackageAssetsManifestFile : Task { [Required] public ITaskItem[] StaticWebAssets { get; set; } [Required] public ITaskItem[] StaticWebAssetEndpoints { get; set; } [Required] public string TargetManifestPath { get; set; } public string PackagePathPrefix { get; set; } = "staticwebassets"; public string FrameworkPattern { get; set; } public override bool Execute() { if (StaticWebAssets.Length == 0) { return !Log.HasLoggedErrors; } var tokenResolver = StaticWebAssetTokenResolver.Instance; var frameworkMatcher = CreateFrameworkMatcher(); var hasFrameworkMatcher = frameworkMatcher != null; var matchContext = hasFrameworkMatcher ? StaticWebAssetGlobMatcher.CreateMatchContext() : default; // Parse all assets once and pre-compute package-relative paths. var parsedAssets = StaticWebAsset.FromTaskItemGroup(StaticWebAssets); var (identityToPackagePath, packagePaths) = ComputePackagePaths(parsedAssets, tokenResolver); var assets = BuildManifestAssets(parsedAssets, packagePaths, identityToPackagePath, frameworkMatcher, matchContext); if (assets == null) { return false; } var manifestEndpoints = BuildManifestEndpoints(identityToPackagePath); if (manifestEndpoints == null) { return false; } var manifest = new StaticWebAssetPackageManifest { Version = StaticWebAssetPackageManifest.CurrentVersion, ManifestType = StaticWebAssetPackageManifest.PackageManifestType, Assets = assets, Endpoints = manifestEndpoints, }; this.PersistFileIfChanged(manifest, TargetManifestPath, StaticWebAssetsJsonSerializerContext.RelaxedEscaping.StaticWebAssetPackageManifest); return !Log.HasLoggedErrors; } private StaticWebAssetGlobMatcher CreateFrameworkMatcher() { if (string.IsNullOrEmpty(FrameworkPattern)) { return null; } var patterns = FrameworkPattern .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .ToArray(); return new StaticWebAssetGlobMatcherBuilder() .AddIncludePatterns(patterns) .Build(); } private (Dictionary<string, string> IdentityToPackagePath, string[] PackagePaths) ComputePackagePaths( StaticWebAsset[] parsedAssets, StaticWebAssetTokenResolver tokenResolver) { var identityToPackagePath = new Dictionary<string, string>(parsedAssets.Length, Utils.OSPath.PathComparer); var packagePaths = new string[parsedAssets.Length]; for (var i = 0; i < parsedAssets.Length; i++) { var packagePath = parsedAssets[i].ComputeTargetPath(PackagePathPrefix, '/', tokenResolver, TokenResolveMode.Pack); identityToPackagePath[parsedAssets[i].Identity] = packagePath; packagePaths[i] = packagePath; } return (identityToPackagePath, packagePaths); } private Dictionary<string, StaticWebAsset> BuildManifestAssets( StaticWebAsset[] parsedAssets, string[] packagePaths, Dictionary<string, string> identityToPackagePath, StaticWebAssetGlobMatcher frameworkMatcher, StaticWebAssetGlobMatcher.MatchContext matchContext) { var hasFrameworkMatcher = frameworkMatcher != null; var assets = new Dictionary<string, StaticWebAsset>(OSPath.PathComparer); // Sort indices by BasePath then RelativePath for deterministic output. var indices = Enumerable.Range(0, parsedAssets.Length) .OrderBy(i => parsedAssets[i].BasePath, StringComparer.OrdinalIgnoreCase) .ThenBy(i => parsedAssets[i].RelativePath, StringComparer.OrdinalIgnoreCase); foreach (var i in indices) { var asset = parsedAssets[i]; var relativePath = asset.RelativePath; var packagePath = packagePaths[i]; var emittedSourceType = StaticWebAsset.SourceTypes.Package; if (hasFrameworkMatcher) { matchContext.SetPathAndReinitialize(relativePath.AsSpan()); var match = frameworkMatcher.Match(matchContext); if (match.IsMatch) { emittedSourceType = StaticWebAsset.SourceTypes.Framework; } } // Remap RelatedAsset from build-time absolute path to package-relative path var relatedAssetValue = asset.RelatedAsset; if (!string.IsNullOrEmpty(relatedAssetValue) && identityToPackagePath.TryGetValue(relatedAssetValue, out var remappedRelatedAsset)) { relatedAssetValue = remappedRelatedAsset; } else if (!string.IsNullOrEmpty(relatedAssetValue)) { Log.LogError( "Asset '{0}' has RelatedAsset '{1}' which could not be mapped to a package-relative path. " + "This indicates a graph inconsistency — the related asset is not part of the package.", asset.Identity, relatedAssetValue); return null; } var manifestAsset = new StaticWebAsset(asset) { Identity = packagePath, SourceType = emittedSourceType, RelatedAsset = relatedAssetValue }; assets[packagePath] = manifestAsset; } return assets; } private StaticWebAssetEndpoint[] BuildManifestEndpoints(Dictionary<string, string> identityToPackagePath) { var endpoints = StaticWebAssetEndpoint.FromItemGroup(StaticWebAssetEndpoints); var manifestEndpoints = new List<StaticWebAssetEndpoint>(); foreach (var endpoint in endpoints.OrderBy(e => e.Route, StringComparer.OrdinalIgnoreCase) .ThenBy(e => e.AssetFile, StringComparer.OrdinalIgnoreCase)) { // Remap AssetFile to package-relative path if (identityToPackagePath.TryGetValue(endpoint.AssetFile, out var packageRelativePath)) { endpoint.AssetFile = packageRelativePath; } else { Log.LogError( "Endpoint '{0}' references AssetFile '{1}' which could not be mapped to a package-relative path. " + "This indicates a graph inconsistency — the referenced asset is not part of the package.", endpoint.Route, endpoint.AssetFile); return null; } manifestEndpoints.Add(endpoint); } return manifestEndpoints.ToArray(); } }