File: UpdateExternallyDefinedStaticWebAssets.cs
Web Access
Project: ..\..\..\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 System.Text.RegularExpressions;
using Microsoft.AspNetCore.StaticWebAssets.Tasks.Utils;
using Microsoft.Build.Framework;
 
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
 
// Certain project types integrate with the static web asset protocol. As we evolve it and add new features
// either they have to update their SDKs to support the new features or we need to provide a way to update
// the assets from previous versions to the current version.
// For example, the JavaScript Project Tools SDK integrates with the static web asset protocol for SPA applications
// but it doesn't support integrity or fingerprinting, which causes issues when we reference the project and we try
// to further process the assets.
public class UpdateExternallyDefinedStaticWebAssets : Task
{
    [Required]
    public ITaskItem[] Assets { get; set; }
 
    [Required]
    public ITaskItem[] Endpoints { get; set; }
 
    public ITaskItem[] FingerprintInferenceExpressions { get; set; }
 
    [Output]
    public ITaskItem[] UpdatedAssets { get; set; }
 
    [Output]
    public ITaskItem[] UpdatedEndpoints { get; set; }
 
    [Output]
    public ITaskItem[] AssetsWithoutEndpoints { get; set; }
 
    public override bool Execute()
    {
        var assets = Assets.Select(StaticWebAsset.FromV1TaskItem).ToArray();
        var endpoints = StaticWebAssetEndpoint.FromItemGroup(Endpoints);
        var endpointByAsset = endpoints
            .GroupBy(e => e.AssetFile, OSPath.PathComparer)
            .ToDictionary(e => e.Key, e => e.ToArray(), OSPath.PathComparer);
 
        var fingerprintExpressions = CreateFingerprintExpressions(FingerprintInferenceExpressions);
 
        var assetsWithoutEndpoints = new List<StaticWebAsset>();
 
        for (var i = 0; i < assets.Length; i++)
        {
            var asset = assets[i];
            if (!endpointByAsset.TryGetValue(asset.Identity, out var endpoint))
            {
                Log.LogMessage($"Asset {asset.Identity} does not have an associated endpoint defined.");
 
                if (TryInferFingerprint(fingerprintExpressions, asset.RelativePath, out var fingerprint, out var newRelativePath))
                {
                    Log.LogMessage($"Inferred fingerprint {fingerprint} for asset {asset.Identity}. Relative path updated to {newRelativePath}.");
                    asset.RelativePath = newRelativePath;
                    asset.Fingerprint = fingerprint;
                }
 
                assetsWithoutEndpoints.Add(asset);
            }
        }
 
        UpdatedAssets = assets.Select(a => a.ToTaskItem()).ToArray();
        UpdatedEndpoints = endpoints.Select(e => e.ToTaskItem()).ToArray();
        AssetsWithoutEndpoints = assetsWithoutEndpoints.Select(a => a.ToTaskItem()).ToArray();
 
        return !Log.HasLoggedErrors;
    }
 
    private bool TryInferFingerprint(Regex[] fingerprintExpressions, string relativePath, out string fingerprint, out string newRelativePath)
    {
        for (var i = 0; i < fingerprintExpressions.Length; i++)
        {
            var regex = fingerprintExpressions[i];
            var match = regex.Match(relativePath);
            if (match.Success)
            {
                var fingerprintGroup = match.Groups["fingerprint"];
                if (fingerprintGroup == null)
                {
                    Log.LogError($"The regular expression {regex} does not contain a 'fingerprint' group. Provide an expression in the form of (?<fingerprint>...).");
                    fingerprint = null;
                    newRelativePath = null;
                    return false;
                }
 
                fingerprint = fingerprintGroup.Value;
                newRelativePath = relativePath.Replace(fingerprintGroup.Value, "#[{fingerprint}]");
                return true;
            }
        }
 
        fingerprint = null;
        newRelativePath = null;
        return false;
    }
 
    private static Regex[] CreateFingerprintExpressions(ITaskItem[] fingerprintInferenceExpressions)
    {
        if (fingerprintInferenceExpressions == null || fingerprintInferenceExpressions.Length == 0)
        {
            return [];
        }
 
        var regexOptions = (OSPath.PathComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None) |
            RegexOptions.Singleline |
            RegexOptions.CultureInvariant;
 
        var result = new Regex[fingerprintInferenceExpressions.Length];
        for (var i = 0; i < fingerprintInferenceExpressions.Length; i++)
        {
            var fingerprintExpression = fingerprintInferenceExpressions[i];
            var pattern = fingerprintExpression.GetMetadata("Pattern");
            var regex = new Regex(pattern, regexOptions);
            result[i] = regex;
        }
 
        return result;
    }
}