diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets index d14cea18509d..1042c438908f 100644 --- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets +++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets @@ -14,6 +14,7 @@ Copyright (c) .NET Foundation. All rights reserved. + @@ -225,7 +226,7 @@ Copyright (c) .NET Foundation. All rights reserved. @@ -276,6 +277,27 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + + + + + + + + + + + (); + + var candidatesByIdentity = candidates.ToDictionary(asset => asset.Identity, OSPath.PathComparer); + + foreach (var candidate in candidates) + { + if (HasCompressionExtension(candidate.RelativePath) && + // We only care about assets that are not already considered compressed + !IsCompressedAsset(candidate) && + // The candidate doesn't already have a related asset + string.IsNullOrEmpty(candidate.RelatedAsset)) + { + Log.LogMessage( + MessageImportance.Low, + "The asset '{0}' was detected as compressed but it didn't specify a related asset.", + candidate.Identity); + var relatedAsset = FindRelatedAsset(candidate, candidatesByIdentity); + if (relatedAsset is null) + { + Log.LogMessage( + MessageImportance.Low, + "The asset '{0}' was detected as compressed but the related asset with relative path '{1}' was not found.", + candidate.Identity, + Path.GetFileNameWithoutExtension(candidate.RelativePath)); + continue; + } + + Log.LogMessage( + "The asset '{0}' was detected as compressed and the related asset '{1}' was found.", + candidate.Identity, + relatedAsset.Identity); + UpdateCompressedAsset(candidate, relatedAsset); + assetsToUpdate.Add(candidate.ToTaskItem()); + } + } + + DiscoveredCompressedAssets = [.. assetsToUpdate]; + + return !Log.HasLoggedErrors; + } + + private StaticWebAsset FindRelatedAsset(StaticWebAsset candidate, IDictionary candidates) + { + // The only pattern that we support is a related asset that lives in the same directory, with the same name, + // but without the compression extension. In any other case we are not going to consider the assets related + // and an error will occur. + var identityWithoutExtension = candidate.Identity.Substring(0, candidate.Identity.Length - 3); // We take advantage we know the extension is .br or .gz. + return candidates.TryGetValue(identityWithoutExtension, out var relatedAsset) ? relatedAsset : null; + } + + private bool HasCompressionExtension(string relativePath) + { + return relativePath.EndsWith(".gz", StringComparison.OrdinalIgnoreCase) || + relativePath.EndsWith(".br", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsCompressedAsset(StaticWebAsset asset) + => string.Equals("Content-Encoding", asset.AssetTraitName, StringComparison.Ordinal); + + private void UpdateCompressedAsset(StaticWebAsset asset, StaticWebAsset relatedAsset) + { + string fileExtension; + string assetTraitValue; + + if (!asset.RelativePath.EndsWith(".gz", StringComparison.OrdinalIgnoreCase)) + { + fileExtension = ".br"; + assetTraitValue = BrotliAssetTraitValue; + } + else + { + fileExtension = ".gz"; + assetTraitValue = GzipAssetTraitValue; + } + + var originalItemSpec = asset.OriginalItemSpec; + var relativePath = relatedAsset.EmbedTokens(relatedAsset.RelativePath); + + asset.RelativePath = $"{relativePath}{fileExtension}"; + asset.OriginalItemSpec = relatedAsset.Identity; + asset.RelatedAsset = relatedAsset.Identity; + asset.AssetRole = "Alternative"; + asset.AssetTraitName = "Content-Encoding"; + asset.AssetTraitValue = assetTraitValue; + } +} diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs new file mode 100644 index 000000000000..e4019c5b1da9 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.StaticWebAssets.Tasks; +using Microsoft.Build.Framework; +using Moq; + +namespace Microsoft.NET.Sdk.Razor.Tests; + +public class DiscoverPrecompressedAssetsTest +{ + public string ItemSpec { get; } + + public string OriginalItemSpec { get; } + + public string OutputBasePath { get; } + + public DiscoverPrecompressedAssetsTest() + { + OutputBasePath = Path.Combine(TestContext.Current.TestExecutionDirectory, nameof(ResolveCompressedAssetsTest)); + ItemSpec = Path.Combine(OutputBasePath, Guid.NewGuid().ToString("N") + ".tmp"); + OriginalItemSpec = Path.Combine(OutputBasePath, Guid.NewGuid().ToString("N") + ".tmp"); + } + + [Fact] + public void DiscoversPrecompressedAssetsCorrectly() + { + var errorMessages = new List(); + var buildEngine = new Mock(); + buildEngine.Setup(e => e.LogErrorEvent(It.IsAny())) + .Callback(args => errorMessages.Add(args.Message)); + + var uncompressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js"), + RelativePath = "js/site#[.{fingerprint}]?.js", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + AssetMergeSource = string.Empty, + SourceId = "Test", + CopyToOutputDirectory = StaticWebAsset.AssetCopyOptions.Never, + Fingerprint = "uncompressed", + RelatedAsset = string.Empty, + ContentRoot = Path.Combine(Environment.CurrentDirectory,"wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + Integrity = "uncompressed-integrity", + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js"), + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest + }; + + var compressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js.gz"), + RelativePath = "js/site.js#[.{fingerprint}]?.gz", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + AssetMergeSource = string.Empty, + SourceId = "Test", + CopyToOutputDirectory = StaticWebAsset.AssetCopyOptions.Never, + Fingerprint = "compressed", + RelatedAsset = string.Empty, + ContentRoot = Path.Combine(Environment.CurrentDirectory, "wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + Integrity = "compressed-integrity", + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js.gz"), + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest + }; + + var task = new DiscoverPrecompressedAssets + { + CandidateAssets = [uncompressedCandidate.ToTaskItem(), compressedCandidate.ToTaskItem()], + BuildEngine = buildEngine.Object + }; + + var result = task.Execute(); + + result.Should().BeTrue(); + task.DiscoveredCompressedAssets.Should().ContainSingle(); + var asset = task.DiscoveredCompressedAssets[0]; + asset.ItemSpec.Should().Be(compressedCandidate.Identity); + asset.GetMetadata("RelatedAsset").Should().Be(uncompressedCandidate.Identity); + asset.GetMetadata("OriginalItemSpec").Should().Be(uncompressedCandidate.Identity); + asset.GetMetadata("RelativePath").Should().Be("js/site#[.{fingerprint=uncompressed}]?.js.gz"); + asset.GetMetadata("AssetRole").Should().Be("Alternative"); + asset.GetMetadata("AssetTraitName").Should().Be("Content-Encoding"); + asset.GetMetadata("AssetTraitValue").Should().Be("gzip"); + asset.GetMetadata("Fingerprint").Should().Be("compressed"); + asset.GetMetadata("Integrity").Should().Be("compressed-integrity"); + asset.GetMetadata("CopyToPublishDirectory").Should().Be("PreserveNewest"); + asset.GetMetadata("CopyToOutputDirectory").Should().Be("Never"); + asset.GetMetadata("AssetMergeSource").Should().Be(string.Empty); + asset.GetMetadata("AssetMergeBehavior").Should().Be(string.Empty); + asset.GetMetadata("AssetKind").Should().Be("All"); + asset.GetMetadata("AssetMode").Should().Be("All"); + asset.GetMetadata("SourceId").Should().Be("Test"); + asset.GetMetadata("SourceType").Should().Be("Discovered"); + asset.GetMetadata("ContentRoot").Should().Be(Path.Combine(Environment.CurrentDirectory, $"wwwroot{Path.DirectorySeparatorChar}")); + } +} diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ResolveCompressedAssetsTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ResolveCompressedAssetsTest.cs index bd164de346d6..67878b28dce1 100644 --- a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ResolveCompressedAssetsTest.cs +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ResolveCompressedAssetsTest.cs @@ -1,11 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.Metrics; using Microsoft.AspNetCore.StaticWebAssets.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Moq; using NuGet.ContentModel; +using NuGet.Packaging.Core; namespace Microsoft.NET.Sdk.Razor.Tests; @@ -70,6 +72,74 @@ public void ResolvesExplicitlyProvidedAssets() task.AssetsToCompress[1].ItemSpec.Should().EndWith(".br"); } + [Fact] + public void InfersPreCompressedAssetsCorrectly() + { + var errorMessages = new List(); + var buildEngine = new Mock(); + buildEngine.Setup(e => e.LogErrorEvent(It.IsAny())) + .Callback(args => errorMessages.Add(args.Message)); + + var uncompressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js"), + RelativePath = "js/site#[.{fingerprint}]?.js", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + AssetMergeSource = string.Empty, + SourceId = "Test", + CopyToOutputDirectory = StaticWebAsset.AssetCopyOptions.Never, + Fingerprint = "xtxxf3hu2r", + RelatedAsset = string.Empty, + ContentRoot = Path.Combine(Environment.CurrentDirectory,"wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + Integrity = "hRQyftXiu1lLX2P9Ly9xa4gHJgLeR1uGN5qegUobtGo=", + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js"), + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest + }; + + var compressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js.gz"), + RelativePath = "js/site.js#[.{fingerprint}]?.gz", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + AssetMergeSource = string.Empty, + SourceId = "Test", + CopyToOutputDirectory = StaticWebAsset.AssetCopyOptions.Never, + Fingerprint = "es13vhk42b", + RelatedAsset = string.Empty, + ContentRoot = Path.Combine(Environment.CurrentDirectory, "wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + Integrity = "zs5Fd3XI6+g9f4N1SFLVdgghuiqdvq+nETAjTbvVxx4=", + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js.gz"), + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest + }; + + var task = new ResolveCompressedAssets + { + OutputPath = OutputBasePath, + CandidateAssets = [uncompressedCandidate.ToTaskItem(), compressedCandidate.ToTaskItem()], + Formats = "gzip", + BuildEngine = buildEngine.Object + }; + + var result = task.Execute(); + + result.Should().BeTrue(); + task.AssetsToCompress.Should().HaveCount(0); + } + [Fact] public void ResolvesAssetsMatchingIncludePattern() { diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.files.json b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.files.json new file mode 100644 index 000000000000..817adc62eec6 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.files.json @@ -0,0 +1,4 @@ +[ + "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css" +] \ No newline at end of file diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.staticwebassets.json new file mode 100644 index 000000000000..d228706df30c --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/Build_Detects_PrecompressedAssets.Build.staticwebassets.json @@ -0,0 +1,1209 @@ +{ + "Version": 1, + "Hash": "__hash__", + "Source": "AppWithP2PReference", + "BasePath": "_content/AppWithP2PReference", + "Mode": "Default", + "ManifestType": "Build", + "ReferencedProjectsConfiguration": [ + { + "Identity": "${ProjectPath}\\ClassLibrary\\ClassLibrary.csproj", + "Version": 2, + "Source": "ClassLibrary", + "GetPublishAssetsTargets": "ComputeReferencedStaticWebAssetsPublishManifest;GetCurrentProjectPublishStaticWebAssetItems", + "AdditionalPublishProperties": "", + "AdditionalPublishPropertiesToRemove": "WebPublishProfileFile;TargetFramework;RuntimeIdentifier;SelfContained", + "GetBuildAssetsTargets": "GetCurrentProjectBuildStaticWebAssetItems", + "AdditionalBuildProperties": "", + "AdditionalBuildPropertiesToRemove": "WebPublishProfileFile;TargetFramework;RuntimeIdentifier;SelfContained" + } + ], + "DiscoveryPatterns": [ + { + "Name": "ClassLibrary\\wwwroot", + "Source": "ClassLibrary", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "Pattern": "**" + } + ], + "Assets": [ + { + "Identity": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "SourceId": "AppWithP2PReference", + "SourceType": "Computed", + "ContentRoot": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/AppWithP2PReference", + "RelativePath": "AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "AssetKind": "All", + "AssetMode": "CurrentProject", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz" + }, + { + "Identity": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "SourceId": "AppWithP2PReference", + "SourceType": "Computed", + "ContentRoot": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\", + "BasePath": "_content/AppWithP2PReference", + "RelativePath": "AppWithP2PReference#[.{fingerprint}]?.styles.css", + "AssetKind": "All", + "AssetMode": "CurrentProject", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "ScopedCss", + "AssetTraitValue": "ApplicationBundle", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "AssetKind": "All", + "AssetMode": "Reference", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.v4.js.gz", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "ClassLibrary#[.{fingerprint}]!.bundle.scp.css", + "AssetKind": "All", + "AssetMode": "Reference", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "ScopedCss", + "AssetTraitValue": "ProjectBundle", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js.br", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "br", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js.gz", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "", + "AssetTraitValue": "", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.v4.js", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "", + "AssetTraitValue": "", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js" + } + ], + "Endpoints": [ + { + "Route": "AppWithP2PReference.styles.css.gz", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css.gz", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css.gz" + } + ] + }, + { + "Route": "AppWithP2PReference.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css" + } + ] + }, + { + "Route": "AppWithP2PReference.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css.gz" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js.br", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "br" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "br", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "br" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.files.json b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.files.json new file mode 100644 index 000000000000..4658dfcbf916 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.files.json @@ -0,0 +1,18 @@ +[ + "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "${OutputPath}\\wwwroot\\AppWithP2PReference.styles.css", + "${OutputPath}\\wwwroot\\AppWithP2PReference.styles.css.br", + "${OutputPath}\\wwwroot\\AppWithP2PReference.styles.css.gz", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\ClassLibrary.__fingerprint__.bundle.scp.css", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\ClassLibrary.__fingerprint__.bundle.scp.css.br", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\ClassLibrary.__fingerprint__.bundle.scp.css.gz", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.js", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.br", + "${OutputPath}\\wwwroot\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "${OutputPath}\\wwwroot\\js\\project-transitive-dep.js.br", + "${OutputPath}\\wwwroot\\js\\project-transitive-dep.js.gz" +] \ No newline at end of file diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.staticwebassets.json b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.staticwebassets.json new file mode 100644 index 000000000000..d228706df30c --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsBaselines/PublishWorks_With_PrecompressedAssets.Build.staticwebassets.json @@ -0,0 +1,1209 @@ +{ + "Version": 1, + "Hash": "__hash__", + "Source": "AppWithP2PReference", + "BasePath": "_content/AppWithP2PReference", + "Mode": "Default", + "ManifestType": "Build", + "ReferencedProjectsConfiguration": [ + { + "Identity": "${ProjectPath}\\ClassLibrary\\ClassLibrary.csproj", + "Version": 2, + "Source": "ClassLibrary", + "GetPublishAssetsTargets": "ComputeReferencedStaticWebAssetsPublishManifest;GetCurrentProjectPublishStaticWebAssetItems", + "AdditionalPublishProperties": "", + "AdditionalPublishPropertiesToRemove": "WebPublishProfileFile;TargetFramework;RuntimeIdentifier;SelfContained", + "GetBuildAssetsTargets": "GetCurrentProjectBuildStaticWebAssetItems", + "AdditionalBuildProperties": "", + "AdditionalBuildPropertiesToRemove": "WebPublishProfileFile;TargetFramework;RuntimeIdentifier;SelfContained" + } + ], + "DiscoveryPatterns": [ + { + "Name": "ClassLibrary\\wwwroot", + "Source": "ClassLibrary", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "Pattern": "**" + } + ], + "Assets": [ + { + "Identity": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "SourceId": "AppWithP2PReference", + "SourceType": "Computed", + "ContentRoot": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/AppWithP2PReference", + "RelativePath": "AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "AssetKind": "All", + "AssetMode": "CurrentProject", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz" + }, + { + "Identity": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "SourceId": "AppWithP2PReference", + "SourceType": "Computed", + "ContentRoot": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\", + "BasePath": "_content/AppWithP2PReference", + "RelativePath": "AppWithP2PReference#[.{fingerprint}]?.styles.css", + "AssetKind": "All", + "AssetMode": "CurrentProject", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "ScopedCss", + "AssetTraitValue": "ApplicationBundle", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "AssetKind": "All", + "AssetMode": "Reference", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.v4.js.gz", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "ClassLibrary#[.{fingerprint}]!.bundle.scp.css", + "AssetKind": "All", + "AssetMode": "Reference", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "ScopedCss", + "AssetTraitValue": "ProjectBundle", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js.br", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "br", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js.gz", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Alternative", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "AssetTraitName": "Content-Encoding", + "AssetTraitValue": "gzip", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.js", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "", + "AssetTraitValue": "", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js" + }, + { + "Identity": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "SourceId": "ClassLibrary", + "SourceType": "Project", + "ContentRoot": "${ProjectPath}\\ClassLibrary\\wwwroot\\", + "BasePath": "_content/ClassLibrary", + "RelativePath": "js/project-transitive-dep.v4.js", + "AssetKind": "All", + "AssetMode": "All", + "AssetRole": "Primary", + "AssetMergeBehavior": "", + "AssetMergeSource": "", + "RelatedAsset": "", + "AssetTraitName": "", + "AssetTraitValue": "", + "Fingerprint": "__fingerprint__", + "Integrity": "__integrity__", + "CopyToOutputDirectory": "Never", + "CopyToPublishDirectory": "PreserveNewest", + "OriginalItemSpec": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js" + } + ], + "Endpoints": [ + { + "Route": "AppWithP2PReference.styles.css.gz", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css.gz", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css.gz" + } + ] + }, + { + "Route": "AppWithP2PReference.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\compressed\\_content\\AppWithP2PReference\\AppWithP2PReference#[.{fingerprint=__fingerprint__}]?.styles.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css" + } + ] + }, + { + "Route": "AppWithP2PReference.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "AppWithP2PReference.__fingerprint__.styles.css", + "AssetFile": "${ProjectPath}\\AppWithP2PReference\\obj\\Debug\\${Tfm}\\scopedcss\\bundle\\AppWithP2PReference.styles.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Link", + "Value": "\u003C_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css\u003E; rel=\u0022preload\u0022; as=\u0022style\u0022" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "AppWithP2PReference.styles.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css.gz" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\ClassLibrary#[.{fingerprint=__fingerprint__}]!.bundle.scp.css.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\compressed\\_content\\ClassLibrary\\js\\project-transitive-dep.v4.js.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/ClassLibrary.__fingerprint__.bundle.scp.css", + "AssetFile": "${ProjectPath}\\ClassLibrary\\obj\\Debug\\${Tfm}\\scopedcss\\projectbundle\\ClassLibrary.bundle.scp.css", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "max-age=31536000, immutable" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/css" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "fingerprint", + "Value": "__fingerprint__" + }, + { + "Name": "integrity", + "Value": "__integrity__" + }, + { + "Name": "label", + "Value": "_content/ClassLibrary/ClassLibrary.bundle.scp.css" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js.br", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "br" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.br", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "br", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "br" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js.gz", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\_content\\ClassLibrary\\js\\project-transitive-dep.js.gz", + "Selectors": [ + { + "Name": "Content-Encoding", + "Value": "gzip", + "Quality": "__quality__" + } + ], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Encoding", + "Value": "gzip" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + }, + { + "Name": "Vary", + "Value": "Content-Encoding" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.js", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + }, + { + "Route": "_content/ClassLibrary/js/project-transitive-dep.v4.js", + "AssetFile": "${ProjectPath}\\ClassLibrary\\wwwroot\\js\\project-transitive-dep.v4.js", + "Selectors": [], + "ResponseHeaders": [ + { + "Name": "Accept-Ranges", + "Value": "bytes" + }, + { + "Name": "Cache-Control", + "Value": "no-cache" + }, + { + "Name": "Content-Length", + "Value": "__content-length__" + }, + { + "Name": "Content-Type", + "Value": "text/javascript" + }, + { + "Name": "ETag", + "Value": "__etag__" + }, + { + "Name": "Last-Modified", + "Value": "__last-modified__" + } + ], + "EndpointProperties": [ + { + "Name": "integrity", + "Value": "__integrity__" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsCompressionIntegrationTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsCompressionIntegrationTest.cs new file mode 100644 index 000000000000..94de9a5784dc --- /dev/null +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsCompressionIntegrationTest.cs @@ -0,0 +1,187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO.Compression; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.StaticWebAssets.Tasks; + +namespace Microsoft.NET.Sdk.Razor.Tests +{ + public class StaticWebAssetsCompressionIntegrationTest : AspNetSdkBaselineTest + { + public StaticWebAssetsCompressionIntegrationTest(ITestOutputHelper log) : base(log, GenerateBaselines) { } + + [Fact] + public void Build_Detects_PrecompressedAssets() + { + var expectedManifest = LoadBuildManifest(); + var testAsset = "RazorAppWithP2PReference"; + ProjectDirectory = CreateAspNetSdkTestAsset(testAsset); + + var file = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js"); + var gzipFile = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js.gz"); + var brotliFile = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js.br"); + + // Compress file into gzip and brotli + using (var gzipStream = new GZipStream(File.Create(gzipFile), CompressionLevel.NoCompression)) + { + using var stream = File.OpenRead(file); + stream.CopyTo(gzipStream); + } + + using (var brotliStream = new BrotliStream(File.Create(brotliFile), CompressionLevel.NoCompression)) + { + using var stream = File.OpenRead(file); + stream.CopyTo(brotliStream); + } + + var build = CreateBuildCommand(ProjectDirectory, "AppWithP2PReference"); + ExecuteCommand(build).Should().Pass(); + + var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString(); + var outputPath = build.GetOutputDirectory(DefaultTfm, "Debug").ToString(); + + // GenerateStaticWebAssetsManifest should generate the manifest file. + var path = Path.Combine(intermediateOutputPath, "staticwebassets.build.json"); + new FileInfo(path).Should().Exist(); + var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path)); + AssertManifest(manifest, expectedManifest); + + // GenerateStaticWebAssetsManifest should copy the file to the output folder. + var finalPath = Path.Combine(outputPath, "AppWithP2PReference.staticwebassets.runtime.json"); + new FileInfo(finalPath).Should().Exist(); + + var manifest1 = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(Path.Combine(intermediateOutputPath, "staticwebassets.build.json"))); + AssertManifest(manifest1, expectedManifest); + AssertBuildAssets(manifest1, outputPath, intermediateOutputPath); + + var manifest2 = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(Path.Combine(intermediateOutputPath, "staticwebassets.build.json"))); + + var standardEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, file, StringComparison.Ordinal)).ToArray(); + var gzipEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, gzipFile, StringComparison.Ordinal)).ToArray(); + var brotliEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, brotliFile, StringComparison.Ordinal)).ToArray(); + + var gzipAsset = manifest2.Assets.Single(a => string.Equals(a.Identity, gzipFile, StringComparison.Ordinal)); + var brotliAsset = manifest2.Assets.Single(a => string.Equals(a.Identity, brotliFile, StringComparison.Ordinal)); + + standardEndpoints.Should().HaveCount(1); + gzipEndpoints.Should().HaveCount(2); + brotliEndpoints.Should().HaveCount(2); + + var expectedWeakEndpointEtag = new EntityTagHeaderValue( + EntityTagHeaderValue.Parse(standardEndpoints.First().ResponseHeaders.Single(h => h.Name == "ETag").Value).Tag, + isWeak: true); + + foreach (var endpoint in gzipEndpoints) + { + endpoint.ResponseHeaders.Where(e => e.Name == "Content-Encoding").Select(e => e.Value).Single().Should().Be("gzip"); + + var etags = endpoint.ResponseHeaders.Where(e => e.Name == "ETag").Select(e => EntityTagHeaderValue.Parse(e.Value)); + etags.Where(e=> !e.IsWeak).Select(e => e.Tag).Single().Should().BeEquivalentTo($"\"{gzipAsset.Integrity}\""); + if (endpoint.Route.EndsWith(".gz")) + { + continue; + } + etags.Should().Contain(expectedWeakEndpointEtag); + } + + foreach (var endpoint in brotliEndpoints) + { + endpoint.ResponseHeaders.Where(e => e.Name == "Content-Encoding").Select(e => e.Value).Single().Should().Be("br"); + + var etags = endpoint.ResponseHeaders.Where(e => e.Name == "ETag").Select(e => EntityTagHeaderValue.Parse(e.Value)); + etags.Where(e => !e.IsWeak).Select(e => e.Tag).Single().Should().BeEquivalentTo($"\"{brotliAsset.Integrity}\""); + if (endpoint.Route.EndsWith(".br")) + { + continue; + } + etags.Should().Contain(expectedWeakEndpointEtag); + } + } + + [Fact] + public void PublishWorks_With_PrecompressedAssets() + { + var expectedManifest = LoadBuildManifest(); + var testAsset = "RazorAppWithP2PReference"; + ProjectDirectory = CreateAspNetSdkTestAsset(testAsset); + + var file = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js"); + var gzipFile = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js.gz"); + var brotliFile = Path.Combine(ProjectDirectory.Path, "ClassLibrary", "wwwroot", "js", "project-transitive-dep.js.br"); + + // Compress file into gzip and brotli + using (var gzipStream = new GZipStream(File.Create(gzipFile), CompressionLevel.NoCompression)) + { + using var stream = File.OpenRead(file); + stream.CopyTo(gzipStream); + } + + using (var brotliStream = new BrotliStream(File.Create(brotliFile), CompressionLevel.NoCompression)) + { + using var stream = File.OpenRead(file); + stream.CopyTo(brotliStream); + } + + var build = CreatePublishCommand(ProjectDirectory, "AppWithP2PReference"); + ExecuteCommand(build).Should().Pass(); + + var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString(); + var outputPath = build.GetOutputDirectory(DefaultTfm, "Debug").ToString(); + + // GenerateStaticWebAssetsManifest should generate the manifest file. + var path = Path.Combine(intermediateOutputPath, "staticwebassets.build.json"); + new FileInfo(path).Should().Exist(); + var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path)); + AssertManifest(manifest, expectedManifest); + + + var manifest1 = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(Path.Combine(intermediateOutputPath, "staticwebassets.build.json"))); + AssertManifest(manifest1, expectedManifest); + AssertBuildAssets(manifest1, outputPath, intermediateOutputPath); + + var manifest2 = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(Path.Combine(intermediateOutputPath, "staticwebassets.publish.json"))); + + var standardEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, file, StringComparison.Ordinal)).ToArray(); + var gzipEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, gzipFile, StringComparison.Ordinal)).ToArray(); + var brotliEndpoints = manifest2.Endpoints.Where(e => string.Equals(e.AssetFile, brotliFile, StringComparison.Ordinal)).ToArray(); + + var gzipAsset = manifest2.Assets.Single(a => string.Equals(a.Identity, gzipFile, StringComparison.Ordinal)); + var brotliAsset = manifest2.Assets.Single(a => string.Equals(a.Identity, brotliFile, StringComparison.Ordinal)); + + standardEndpoints.Should().HaveCount(1); + gzipEndpoints.Should().HaveCount(2); + brotliEndpoints.Should().HaveCount(2); + + var expectedWeakEndpointEtag = new EntityTagHeaderValue( + EntityTagHeaderValue.Parse(standardEndpoints.First().ResponseHeaders.Single(h => h.Name == "ETag").Value).Tag, + isWeak: true); + + foreach (var endpoint in gzipEndpoints) + { + endpoint.ResponseHeaders.Where(e => e.Name == "Content-Encoding").Select(e => e.Value).Single().Should().Be("gzip"); + + var etags = endpoint.ResponseHeaders.Where(e => e.Name == "ETag").Select(e => EntityTagHeaderValue.Parse(e.Value)); + etags.Where(e => !e.IsWeak).Select(e => e.Tag).Single().Should().BeEquivalentTo($"\"{gzipAsset.Integrity}\""); + if (endpoint.Route.EndsWith(".gz")) + { + continue; + } + etags.Should().Contain(expectedWeakEndpointEtag); + } + + foreach (var endpoint in brotliEndpoints) + { + endpoint.ResponseHeaders.Where(e => e.Name == "Content-Encoding").Select(e => e.Value).Single().Should().Be("br"); + + var etags = endpoint.ResponseHeaders.Where(e => e.Name == "ETag").Select(e => EntityTagHeaderValue.Parse(e.Value)); + etags.Where(e => !e.IsWeak).Select(e => e.Tag).Single().Should().BeEquivalentTo($"\"{brotliAsset.Integrity}\""); + if (endpoint.Route.EndsWith(".br")) + { + continue; + } + etags.Should().Contain(expectedWeakEndpointEtag); + } + } + } +}